Network Search widget

The Network search widget can be quickly embedded in your internal application using code provided by a Network administrator, reducing integration and maintenance costs.

Network’s search widget code provides all of the following development tasks that are required for integration:

  • User interface for search queries, search results, and record details.
  • Integration with the Network APIs for searching and retrieving master data.
  • Ongoing maintenance to support new features and data model changes.

How does it work?

The Network search widget is supported for internal applications; for example, expense systems, service applications, ERP applications, and event management applications.

Applications must meet the following requirements:

  • The application is web-based.
  • Your Network instance uses single sign-on (SSO) authentication (every user must have a Network/SSO account).

Adding Network's search widget in your internal application has three steps:

  1. Configure the widget - Network administrators set up the widget in their Network instance. When the configuration is saved, code snippets are generated.
  2. Embed the widget - Web developers embed the generated code snippets into the internal application.
  3. Define optional events:
    • select - Web developers define an event handler so that when users select a record the developer is returned the event entity data, which contains a JSON of the selected record. The developer can manipulate the JSON in order to add the record into their internal system.
    • add-request-presubmit - Web developers can define an event handler so they can retrieve the fields submitted by the widget user from the add request.
    • add-request-submitted - Web developers can define an event handler so they can retrieve the task ID of the submitted add request.

When the first two steps are complete, the search widget is fully functional for business users to search, download records, and create add requests to their Network instance.

When all of these steps are complete, the search widget is embedded in your application and users can search for records, view profiles, and add those records to your internal application.

Embed the widget

Before you can embed a widget in a web application, a Network administrator must configure the widget in a Network instance and generate the code you need to embed.

Load the search widget

The following code loads the search widget, and must be included in your HTML page.

Include this code in your HTML page ensuring that the veeva-network-manifest.js script is loaded first.

<script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
<script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-search-widget-loader.js"></script>

Ensure that the veeva-networkmanifest.js script is loaded first.

Enable the search widget

The veeva-network-search-widget element enables you to use the search widget. You include it within the <body> elements of your HTML page.

<veeva-network-search-widget
    widget-name="ServiceCloudA" 
    auth-domain="my.veevanetwork.com" 
    widget-id="MTAwMDI7OztzZXJ2aWNlY2xvdWRhX19j">
</veeva-network-search-widget>

This code is generated when your Network administrator completes widget configuration in the Network instance.

Required Properties
widget-name
The name that the Network administrator defined during search widget configuration in the Network instance.
auth-domain
The domain of your Network instance.
widget-id
A code generated by Network that applies to the search widget configuration. If you make style changes to the widget in the search widget configuration in the Network instance and click Generate Code again, the widget-id does not change.

Additional properties

Optional Properties
closeable
Boolean (default False). The ability to close the widget. Set this attribute to True to add a close (x) button to the top right corner of the widget header.

When the close button is clicked, the veeva-network:your_widget_name:close-widget event is invoked so you can add your own close event listener.

hold-initialization
Boolean (default False). Set this attribute to True to hold the initialization of the content of this widget. This is useful for preventing the authentication flow that displays a new window for single sign-on when your page is loading, to avoid disruption.

You can start the initialization of the content in the widget later by calling the start() function.

preset-filter
Scope this preset filter to an entity to limit the type of records that viewers can see in the search widget. For example, define a preset filter to limit the records in the search widget to pharmacies only, or active prescribers only. If the attribute is not scoped to an entity, it will be ignored during the search API call.

This preset filter is scoped for the Health Care Organization (HCO) entity using the hco_type__v field. The scope is limited to pharmacies in hospitals (11:2) and other pharmacies (11:98). Administrators can provide the field names and reference codes required for this attribute.

Example:

preset-filter="hcp.hcp_type__v=P&hcp.hcp_status__v=A"
preset-exclude-filter
Apply exclusion filters to preset filters.

Examples:

Exclude HCP records with the first specialty set to A:

preset-exclude-filter="hcp.specialty_1__v=A"

Exclude HCO records with a custom field not equal to a specific value:

preset-exclude-filter="top_institution__c=Y"

Exclude entities that contain one of multiple values in a set:

preset-exclude-filter="specialty_1__v=CPP,PMG,PPM,PDM,EMP,PD,PDN,CHC"

Exclude entities from across a collection of fields using an OR condition:

preset-exclude-filter="specialty=CPP,PMG,PPM,PDM,EMP,PD,PDN,CHC"

Exclude entities using multiple filters using an AND condition:

preset-exclude-filter="hcp.specialty=CPP,PMG,PPM,PDM,EMP,PD,PDN,CHC&hcp.hcp_type__v=P"

Exclude entities using collection fields (specialty, credentials, medical_degree):

preset-exclude-filter="hcp.specialty=CPP,PMG,PPM,PDM,EMP,PD,PDN,CHC"
phco-preset-exclude-filter
Apply parentHCO exclusion filters to preset filters.

Example:

Exclude parent HCO records that contain multiple values for type and a specific status:

phco-preset-exclude-filter="hco.hco_type__v=11:98,11:2&hco.hco_status__v=A"

Exclude parent HCO records from Veeva OpenData that contain a specific type:

phco-preset-exclude-filter="hco_type__v=4:1&is_veeva_master__v=Y"
show-in-dialog
Boolean (default False). Set this attribute to True to display the widget in a default dialog provided by Network. When True, both hold-initialization and closeable are also implicitly True.

When you call the start() function, the dialog with the widget will open. You can add a close() function to close the dialog.

You must provide a button or link on the page for users to access the search widget from a dialog.

enriched-results
Boolean (default False). Set this attribute to True to display label values for reference type fields.
enriched-results-language
When enriched-results is True, this attribute specifies the language using the IETF BCP 47 language standard; for example en for English.
search-local-only
When search-local-only is True, searches are performed against data in the local instance only.
demo-mode
Boolean (default False). Set this attribute to True to test the widget without single sign-on configured in sandbox instances only. Before using this attribute, you must request demo mode access through Veeva Support.
username
When demo-mode is True, this attribute specifies the user name required to access the widget.
password
When demo-mode is True, this attribute specifies the password required to access the widget.

Enable a widget:

<veeva-network-search-widget
    id="searchWidget"
    widget-name="ServiceCloudA" 
    auth-domain="my.veevanetwork.com" 
    widget-id="MTAwMDI7OztzZXJ2aWNlY2xvdWRhX19j"
    preset-filter="hcp.hcp_type__v=P&hcp.hcp_status__v=A"
    enriched-results="true"
    enriched-results-language="en"
    search-local-only="false"
    demo-mode="true"
    username="john.smith@my.veevanetwork.com"
    password="12345">
  </veeva-network-search-widget>

  <!-- Add the search widget -->
  <div class="container">
    <button id="openWidget">Network Search</button>
  </div>

On some platforms the attribute value is required.

JavaScript methods

The following methods are used with search widgets:

Method
Parameters
Description
start()
none
Starts or restarts the widget.
close()
none
Closes the widget.
associateCustomKey(vid__v, custom_key_source_type, custom_key_item_type, custom_key_value, system_name)
  • vid__v (required)
  • custom_key_source_type (required)
  • custom_key_item_type (required)
  • custom_key_value (required)
  • system_name (optional)
Associates a custom key. All parameters except system_name are required.

JavaScript events

The search widget is completely functional for business users to view, download, and add records when the two provided code blocks from Network have been added to your internal application. However, you can customize the behavior of the widget to meet your business needs.

For example, you can create events so users can download records to your internal application or submit add requests (if they cannot find the record they searched for).

Event requirements

Events require the following:

  • an event name
  • event entity data in JSON format

The event name uses the following format:

'veeva-network:ServiceCloudA:select'

where:

  • veeva-network is a predefined value. Do not change.
  • ServiceCloudA is the name of the widget that was defined in the Network search widget configuration for this example. Replace this with the name of your search widget.
  • select is the event name.
Event Trigger
select Produces the selected record in JSON format.
  • Clicking the Select button on a profile page.
  • Successful processing of an add request.
select-failed Failure of a select operation.
close-widget When widget is closed
add-request-submitted Successful submission of add request. The data used for this event is the task ID of the request.
add-request-process-timeout When an add request takes too long to process.
add-request-presubmit Occurs prior to add request submission. The data used for this event is the add request in JSON format.
add-request-create-failed An add request is not able to be created.
{
  "entity": {
    "entityId": "243225052210791424",
    "entityType": "HCP",
    "metaData": {},
    "entity": {
      "gender__v": "M",
      "years_in_progress__v": 0,
      "birth_year__v": 1960,
      "knipper_id__v": "900803886",
      "first_name__v": "John",
      "record_owner_type__v": "VOD",
      "education_level__v": "RESIDENCY",
      "grad_training__v": "Y",
      "npi_num__v": "1142102709",
      "record_delta_id__v": "936635376028909567",
      "record_owner_name__v": "OpenData",
      "grad_trg_end_date__v": "1990-06-30",
      "place_of_employment__v": "30",
      "middle_name__v": "F",
      "kaiser__v": "N",
      "formatted_name__v": "John Johnson",
      "birth_city__v": "CHICAGO",
      "pdrp_optout__v": "N",
      "type_of_practice__v": "4",
      "birth_country__v": "US",
      "is_veeva_master__v": true,
      "hcp_status__v": "A",
      "status_update_time__v": "2020-05-29T14:27:56.000-07:00",
      "URL_1__v": "https://www.beaconhealthsystem.org/provider/russell-f-johnson-md/",
      "modified_date__v": "2020-05-29T21:27:57.000-07:00",
      "record_version__v": 0,
      "vid__v": "243225052210791424",
      "is_externally_mastered__v": false,
      "specialty_1_rank__v": 1,
      "specialty_1__v": "RO",
      "upin__v": "E68860",
      "cms_general_payments__v": 234,
      "hcp_type__v": "P",
      "master_vid__v": "243225052210791424",
      "birth_state__v": "US-IL",
      "last_name__v": "Johnson",
      "sha_id__v": "214399472",
      "medical_degree_1__v": "MD",
      "fellow__v": "N",
      "primary_country__v": "US",
      "created_date__v": "2020-05-29T14:27:56.000-07:00",
      "mpa__v": "30",
      "ama_do_not_contact__v": "N",
      "specialty_2__v": "DR",
      "ams_id__v": "20953318",
      "grad_year__v": 1986,
      "cms_physician_profile_id__v": 391625,
      "email_1__v": "John.Johnson1@ucla.edu",
      "record_state__v": "VALID",
      "specialty_2_rank__v": 2,
      "me_id__v": "3532713306",
      "grad_school__v": "University of Illinois at Chicago College of Medicine",
      "addresses__v": [
        {
          "dpv_confirmed_indicator__v": "X",
          "postal_code_primary__v": "46545",
          "address_line_1__v": "620 W Edison Rd Ste 110",
          "record_owner_type__v": "VOD",
          "premise__v": "620",
          "cbsa__v": "43780",
          "record_delta_id__v": "936635375832694798",
          "record_owner_name__v": "OpenData",
          "locality__v": "Mishawaka",
          "delivery_address__v": "620 W Edison Rd Ste 110",
          "country__v": "US",
          "sub_building_number__v": "110",
          "created_date__v": "2020-05-29T14:27:56.000-07:00",
          "premise_number__v": "620",
          "is_veeva_master__v": true,
          "ISO_3166_3__v": "USA",
          "ISO_3166_n__v": "840",
          "sub_building__v": "Ste 110",
          "thoroughfare__v": "W Edison Rd",
          "status_update_time__v": "2020-05-29T14:27:56.000-07:00",
          "address_type__v": "P",
          "delivery_address_1__v": "620 W Edison Rd Ste 110",
          "sub_administrative_area__v": "St Joseph",
          "entity_type__v": "HCP",
          "address_verification_status__v": "V",
          "address_status__v": "I",
          "modified_date__v": "2020-05-29T14:27:56.000-07:00",
          "record_state__v": "VALID",
          "vid__v": "243386932086703107",
          "entity_vid__v": "243225052210791424",
          "postal_code__v": "46545-2784",
          "is_externally_mastered__v": false,
          "administrative_area__v": "US-IN",
          "formatted_address__v": "620 W Edison Rd Ste 110 Mishawaka Indiana 46545-2784 United States",
          "latitude__v": 41.69543,
          "address_ordinal__v": 6,
          "postal_code_secondary__v": "2784",
          "longitude__v": -86.1911,
          "custom_keys__v": [
            {
              "custom_key_entity_id__v": "243386932086703107",
              "custom_key_item_type__v": "ADDRESS",
              "custom_key_value__v": "243386932086703107",
              "modified_date__v": "2020-05-29T14:27:56.000-07:00",
              "vid__v": "936635375825127072",
              "custom_key_vid_key__v": "MASTER__v:ADDRESS:243386932086703107",
              "custom_key_source_type__v": "MASTER__v",
              "created_date__v": "2020-05-29T14:27:56.000-07:00",
              "custom_key_entity_type__v": "ADDRESS",
              "custom_key_status__v": "A",
              "status_update_time__v": "2020-05-29T14:27:56.000-07:00"
            }
          ]
        }
      ],
      "custom_keys__v": [
        {
          "custom_key_entity_id__v": "243225052210791424",
          "custom_key_item_type__v": "HCP-Select",
          "custom_key_value__v": "243225052210791424",
          "modified_date__v": "2020-05-29T14:27:58.000-07:00",
          "vid__v": "936635375939690047",
          "custom_key_vid_key__v": "change_request-SampleWidget:HCP-Select:243225052210791424",
          "custom_key_source_type__v": "change_request-SampleWidget",
          "created_date__v": "2020-05-29T14:27:58.000-07:00",
          "custom_key_entity_type__v": "HCP",
          "custom_key_status__v": "A",
          "status_update_time__v": "2020-05-29T14:27:58.000-07:00"
        }
      ],
      "licenses__v": [
        {
          "record_owner_type__v": "VOD",
          "best_state_license__v": "N",
          "license_eligibility__v": "U",
          "record_delta_id__v": "936635375832694806",
          "record_owner_name__v": "OpenData",
          "expiration_date__v": "2017-07-31",
          "rxa_eligible__v": "U",
          "license_status_condition__v": "0",
          "dea_business_activity_code__v": "ZZ",
          "created_date__v": "2020-05-29T14:27:56.000-07:00",
          "is_veeva_master__v": true,
          "type_value__v": "IL",
          "license_degree__v": "MD",
          "effective_date__v": "2006-04-28",
          "license_number__v": "336.076770",
          "status_update_time__v": "2020-05-29T14:27:56.000-07:00",
          "license_status__v": "A",
          "entity_type__v": "HCP",
          "body__v": "BOME",
          "modified_date__v": "2020-05-29T14:27:56.000-07:00",
          "initial_board_license_date__v": "2018-03-09",
          "record_state__v": "VALID",
          "license_subtype__v": "C",
          "vid__v": "928543245667729439",
          "entity_vid__v": "243225052210791424",
          "is_externally_mastered__v": false,
          "type__v": "CDS",
          "custom_keys__v": [
            {
              "custom_key_entity_id__v": "928543245667729439",
              "custom_key_item_type__v": "LICENSE",
              "custom_key_value__v": "928543245667729439",
              "modified_date__v": "2020-05-29T14:27:56.000-07:00",
              "vid__v": "936635375827945120",
              "custom_key_vid_key__v": "MASTER__v:LICENSE:928543245667729439",
              "custom_key_source_type__v": "MASTER__v",
              "created_date__v": "2020-05-29T14:27:56.000-07:00",
              "custom_key_entity_type__v": "LICENSE",
              "custom_key_status__v": "A",
              "status_update_time__v": "2020-05-29T14:27:56.000-07:00"
            }
          ]
        },
        {
          "address_vid__v": "928924082756585379",
          "record_owner_type__v": "VOD",
          "best_state_license__v": "N",
          "license_eligibility__v": "U",
          "record_delta_id__v": "936635375832760319",
          "record_owner_name__v": "OpenData",
          "expiration_date__v": "2020-12-31",
          "license_status_condition__v": "0",
          "dea_business_activity_code__v": "C0",
          "created_date__v": "2020-05-29T14:27:56.000-07:00",
          "is_veeva_master__v": true,
          "type_value__v": "DEA",
          "license_number__v": "BJ1302683",
          "status_update_time__v": "2020-05-29T14:27:56.000-07:00",
          "drug_schedule__v": "22N 33N 4 5",
          "license_status__v": "A",
          "entity_type__v": "HCP",
          "modified_date__v": "2020-05-29T14:27:56.000-07:00",
          "record_state__v": "VALID",
          "license_subtype__v": "U",
          "vid__v": "929342375067781025",
          "entity_vid__v": "243225052210791424",
          "is_externally_mastered__v": false,
          "type__v": "ADDRESS",
          "custom_keys__v": [
            {
              "custom_key_entity_id__v": "929342375067781025",
              "custom_key_item_type__v": "LICENSE",
              "custom_key_value__v": "929342375067781025",
              "modified_date__v": "2020-05-29T14:27:56.000-07:00",
              "vid__v": "936635375828010655",
              "custom_key_vid_key__v": "MASTER__v:LICENSE:929342375067781025",
              "custom_key_source_type__v": "MASTER__v",
              "created_date__v": "2020-05-29T14:27:56.000-07:00",
              "custom_key_entity_type__v": "LICENSE",
              "custom_key_status__v": "A",
              "status_update_time__v": "2020-05-29T14:27:56.000-07:00"
            }
          ]
        },
        {
          "record_owner_type__v": "VOD",
          "best_state_license__v": "Y",
          "grace_period__v": "1991-12-31",
          "license_eligibility__v": "I",
          "record_delta_id__v": "936635375832760323",
          "record_owner_name__v": "OpenData",
          "expiration_date__v": "1991-12-31",
          "rxa_eligible__v": "I",
          "license_status_condition__v": "13",
          "dea_business_activity_code__v": "ZZ",
          "created_date__v": "2020-05-29T14:27:56.000-07:00",
          "is_veeva_master__v": true,
          "type_value__v": "MN",
          "license_degree__v": "MD",
          "effective_date__v": "1990-05-05",
          "license_number__v": "33548",
          "status_update_time__v": "2020-05-29T14:27:56.000-07:00",
          "license_status__v": "I",
          "entity_type__v": "HCP",
          "body__v": "BOME",
          "modified_date__v": "2020-05-29T14:27:56.000-07:00",
          "record_state__v": "VALID",
          "license_subtype__v": "U",
          "vid__v": "243386932086703136",
          "entity_vid__v": "243225052210791424",
          "is_externally_mastered__v": false,
          "type__v": "STATE",
          "custom_keys__v": [
            {
              "custom_key_entity_id__v": "243386932086703136",
              "custom_key_item_type__v": "LICENSE",
              "custom_key_value__v": "243386932086703136",
              "modified_date__v": "2020-05-29T14:27:56.000-07:00",
              "vid__v": "936635375828010659",
              "custom_key_vid_key__v": "MASTER__v:LICENSE:243386932086703136",
              "custom_key_source_type__v": "MASTER__v",
              "created_date__v": "2020-05-29T14:27:56.000-07:00",
              "custom_key_entity_type__v": "LICENSE",
              "custom_key_status__v": "A",
              "status_update_time__v": "2020-05-29T14:27:56.000-07:00"
            }
          ]
        }
      ],
      "parent_hcos__v": [
        {
          "parent_hco_vid__v": "830339750903780355",
          "related_entity_type__v": "HCO",
          "record_owner_type__v": "VOD",
          "relationship_type__v": "7356",
          "status_update_time__v": "2020-05-29T14:27:56.000-07:00",
          "record_delta_id__v": "936635375849209855",
          "entity_type__v": "HCP",
          "hierarchy_type__v": "HCP_HCO",
          "record_owner_name__v": "OpenData",
          "modified_date__v": "2020-05-29T14:27:56.000-07:00",
          "record_state__v": "VALID",
          "parent_hco_status__v": "A",
          "is_primary_relationship__v": "N",
          "vid__v": "934448194738260856",
          "entity_vid__v": "243225052210791424",
          "is_externally_mastered__v": false,
          "created_date__v": "2020-05-29T14:27:56.000-07:00",
          "is_veeva_master__v": true,
          "custom_keys__v": [
            {
              "custom_key_entity_id__v": "934448194738260856",
              "custom_key_item_type__v": "PARENTHCO",
              "custom_key_value__v": "934448194738260856",
              "modified_date__v": "2020-05-29T14:27:56.000-07:00",
              "vid__v": "936635375828141732",
              "custom_key_vid_key__v": "MASTER__v:PARENTHCO:934448194738260856",
              "custom_key_source_type__v": "MASTER__v",
              "created_date__v": "2020-05-29T14:27:56.000-07:00",
              "custom_key_entity_type__v": "RELATION",
              "custom_key_status__v": "A",
              "status_update_time__v": "2020-05-29T14:27:56.000-07:00"
            }
          ]
        }
      ]
    }
  }
}

Download events

You can create an event so users can download records from the search widget to your internal application. By default, if the user clicks the Select button, the record is downloaded to their Network instance. Downloading the record to the internal application enables users to complete transactions on the record; for example, adding the HCP to an expense report.

select event

The select event downloads the record to your downstream system.

Sample event name:

'veeva-network:ServiceCloudA:select'

The event entity data is provided in JSON format using the following value:

(ev) => {
          const { entity } = ev.detail.entity;
}

where:

  • ev is the name of the event object. This value can be changed to your event object name.
  • ev.detail.entity provides the JSON of the record that is the same object as the first object in the entities array returned from the Network Retrieve entities API.

For each search result that is downloaded, Network provides a JSON response that includes all of the field values for the entity and the associated child objects (addresses, licenses, parent HCO affiliations).

Use the fields in the JSON response to help you define the requirements for the downloaded record; for example, creating a new record or account in your application.

{
    "entityId": "505659322685034497",
    "entityType": "HCP",
    "metaData": {},
    "entity": {
        "attribute 1": "Value 1",
        "attribute 2": "Value 2",
        " ... ": " ... ",
        "custom_keys__v": [{
            "attribute 1": "Value 1",
            "attribute 2": "Value 2",
            " ... ": " ... "
        }],
        "addresses__v": [{
            "attribute 1": "Value 1",
            "attribute 2": "Value 2",
            " ... ": " ... ",
            "custom_keys__v": [{
                "attribute 1": "Value 1",
                "attribute 2": "Value 2",
                " ... ": " ... "
            }]
        }],
        "licenses__v": [{
            "attribute 1": "Value 1",
            "attribute 2": "Value 2",
            " ... ": " ... ",
            "custom_keys__v": [{
                "attribute 1": "Value 1",
                "attribute 2": "Value 2",
                " ... ": " ... "
            }]
        }],
        "parent_hcos__v": [{
            "attribute 1": "Value 1",
            "attribute 2": "Value 2",
            " ... ": " ... ",
            "custom_keys__v": [{
                "attribute 1": "Value 1",
                "attribute 2": "Value 2",
                " ... ": " ... "
            }]
        }]
    }
}

A sample JSON response is provided in the JSON response section below.

For an example of a custom download event, see the Case Study section below.

Add request events

The search widget can be configured to allow users to submit add requests when they cannot find the record they searched for. Network administrators must enable the add request option in the search widget configuration in the Network UI.

If this option is enabled, you can create events so users can create records and submit add requests. The add request will be routed to data stewards to validate and process, but users will be able to immediately action the record in the internal application. Check with your administrator to see if the search widget supports add requests.

Three events must be created to support add requests:

  • add-request-presubmit - Provides the task data.
  • add-request-submitted - Provides a task ID.
  • select - Provides the JSON for the record to be added to your downstream system.

add-request-presubmit event

The add-request-presubmit event provides the task data for the submitted add request.

Sample event name

'veeva-network:ServiceCloudA:add-request-presubmit'

The event entity data for the add-request-presubmit event uses the following value:

Event entity data

(ev) => {
            const { eventdetails } = ev.detail;
}

where:

  • ev is the name of the event object. This value can be changed to your event object name.
  • ev.detail provides the JSON of the changes to the record.

Network provides a JSON response that includes all of the details for the add request, including the creator, requester notes, and all of the new field values for the record and the associated child objects (addresses, licenses, parent HCO affiliations).

Sample JSON response

{
  "entity_type": "HCP",
  "metadata": {
    "creator": "Jane.Doe@my.veevanetwork.com",
    "system": "ServiceCloud",
    "note": "New HCP",
    "source": "SearchWidget-ServiceCloud"
  },
  "entity": {
    "first_name__v": "John",
    "last_name__v": "Smith",
    "hcp_type__v": "P",
    "hcp_status__v": "A",
    "ama_do_not_contact__v": "N",
    "pdrp_optout__v": "N",
    "fellow__v": "N",
    "grad_training__v": "N",
    "primary_country__v": "US",
    "custom_decimal__c": "1.23"
  }
}

add-request-submitted event

The add-request-submitted event provides the task ID for the submitted add request.

Sample event name

'veeva-network:ServiceCloudA:add-request-submitted'

The event entity data for the add-request-submitted event uses the following value:

Event entity data

(ev) => {
            const { eventdetails } = ev.detail;
}

where:

  • ev is the name of the event object. This value can be changed to your event object name.
  • ev.detail provides the JSON for the task ID. Network provides a JSON response that includes the task ID for the add request.

Sample response

{taskId: "934001685804876191"}

select event

Use the same select event from the Creating download events section above.

JSON response

For each search result that is downloaded, Network provides a JSON response that includes all of the field values for the entity and the associated child objects (addresses, licenses, parent HCO affiliations). The JSON is the first object in the entities array returned from the Network Retrieve Entity API and is available using ev.detail.entity in the download event object.

The field values in this response use the reference aliases of the source system that was chosen during the search widget configuration.

{
 "entityId": "242979976201110533",
 "entityType": "HCP",
 "metaData": {},
 "entity": {
    "gender__v": "Male",
    "specialty_1__v": "Prescriber",
    "hcp_type__v": "N",
    "master_vid__v": "242979976201110533",
    "first_name__v": "John",
    "record_owner_type__v": "VOD",
    "grad_training__v": "N",
    "record_delta_id__v": "930473391097610239",
    "last_name__v": "Jones",
    "sha_id__v": "209784225",
    "record_owner_name__v": "OpenData",
    "medical_degree_1__v": "RN",
    "fellow__v": "N",
    "kaiser__v": "N",
    "formatted_name__v": "John Jones",
    "primary_country__v": "United States",
    "created_date__v": "2017-04-28T16:12:59.000-05:00",
    "pdrp_optout__v": "N",
    "is_veeva_master__v": true,
    "hcp_status__v": "A",
    "ama_do_not_contact__v": "N",
    "status_update_time__v": "2017-04-28T16:12:59.000-05:00",
    "ams_id__v": "15873366",
    "modified_date__v": "2017-05-16T15:39:13.000-05:00",
    "record_state__v": "VALID",
    "verteo_id__c": "VSSW-0GE-TH3",
    "record_version__v": 0,
    "vid__v": "242979976201110533",
    "is_externally_mastered__v": false,
    "specialty_1_rank__v": 1,
    "custom_keys__v": [{
        "custom_key_entity_id__v": "242979976201110533",
        "custom_key_item_type__v": "Account",
        "custom_key_value__v": "001V000000PXmbTIAT",
        "modified_date__v": "2017-04-28T16:13:07.000-05:00",
        "vid__v": "930253893769625631",
        "custom_key_vid_key__v": "VCRM-00DV0000006tHq7MAE:Account:001V000000PXmbTIAT",
        "custom_key_source_type__v": "VCRM-00DV0000006tHq7MAE",
        "created_date__v": "2017-04-28T16:13:07.000-05:00",
        "custom_key_entity_type__v": "HCP",
        "custom_key_status__v": "A",
        "status_update_time__v": "2017-04-28T16:13:07.000-05:00"
    }, {
        "custom_key_entity_id__v": "242979976201110533",
        "custom_key_item_type__v": "Account",
        "custom_key_value__v": "001Q0000016Lp9SIAS",
        "modified_date__v": "2017-05-12T18:00:30.000-05:00",
        "vid__v": "930333588319567903",
        "custom_key_vid_key__v": "VCRM-00DQ000000GKl1iMAD:Account:001Q0000016Lp9SIAS",
        "custom_key_source_type__v": "VCRM-00DQ000000GKl1iMAD",
        "created_date__v": "2017-05-12T18:00:30.000-05:00",
        "custom_key_entity_type__v": "HCP",
        "custom_key_status__v": "A",
        "status_update_time__v": "2017-05-12T18:00:30.000-05:00"
    }, {
        "custom_key_entity_id__v": "242979976201110533",
        "custom_key_item_type__v": "Account",
        "custom_key_value__v": "001W000000RjMBZIA3",
        "modified_date__v": "2017-05-15T20:12:00.000-05:00",
        "vid__v": "930351092316766239",
        "custom_key_vid_key__v": "CRM:Account:001W000000RjMBZIA3",
        "custom_key_source_type__v": "CRM",
        "created_date__v": "2017-05-15T20:12:00.000-05:00",
        "custom_key_entity_type__v": "HCP",
        "custom_key_status__v": "A",
        "status_update_time__v": "2017-05-15T20:12:00.000-05:00"
    }, {
        "custom_key_entity_id__v": "242979976201110533",
        "custom_key_item_type__v": "Account",
        "custom_key_value__v": "001W000000RcjsvIAB",
        "modified_date__v": "2017-05-16T15:39:13.000-05:00",
        "vid__v": "930355682040283167",
        "custom_key_vid_key__v": "VCRM-00DW0000008xGqaMAE:Account:001W000000RcjsvIAB",
        "custom_key_source_type__v": "VCRM-00DW0000008xGqaMAE",
        "created_date__v": "2017-05-16T15:39:13.000-05:00",
        "custom_key_entity_type__v": "HCP",
        "custom_key_status__v": "A",
        "status_update_time__v": "2017-05-16T15:39:13.000-05:00"
    }, {
        "custom_key_entity_id__v": "242979976201110533",
        "custom_key_item_type__v": "HCP",
        "custom_key_value__v": "242979976201110533",
        "modified_date__v": "2017-04-28T16:13:00.000-05:00",
        "vid__v": "930253893256609824",
        "custom_key_vid_key__v": "MASTER__v:HCP:242979976201110533",
        "custom_key_source_type__v": "MASTER__v",
        "created_date__v": "2017-04-28T16:13:00.000-05:00",
        "custom_key_entity_type__v": "HCP",
        "custom_key_status__v": "A",
        "status_update_time__v": "2017-04-28T16:13:00.000-05:00"
    }, {
        "custom_key_entity_id__v": "242979976201110533",
        "custom_key_item_type__v": "Account",
        "custom_key_value__v": "001W000000RU9wdIAD",
        "modified_date__v": "2017-05-15T15:36:08.000-05:00",
        "vid__v": "930350007605657631",
        "custom_key_vid_key__v": "VCRM-00DW0000008xHNsMAM:Account:001W000000RU9wdIAD",
        "custom_key_source_type__v": "VCRM-00DW0000008xHNsMAM",
        "created_date__v": "2017-05-15T15:36:08.000-05:00",
        "custom_key_entity_type__v": "HCP",
        "custom_key_status__v": "A",
        "status_update_time__v": "2017-05-15T15:36:08.000-05:00"
    }],
    "licenses__v": [{
        "record_owner_type__v": "VOD",
        "best_state_license__v": "Y",
        "grace_period__v": "2016-04-30",
        "license_eligibility__v": "I",
        "record_delta_id__v": "930253893279973376",
        "anticipated_expiry_date__v": "2016-10-27",
        "record_owner_name__v": "OpenData",
        "expiration_date__v": "2016-04-30",
        "rxa_eligible__v": "I",
        "license_status_condition__v": "0",
        "dea_business_activity_code__v": "ZZ",
        "created_date__v": "2017-04-28T16:13:00.000-05:00",
        "is_veeva_master__v": true,
        "type_value__v": "PA",
        "license_degree__v": "RN",
        "effective_date__v": "1977-03-21",
        "license_number__v": "RN215307L",
        "status_update_time__v": "2017-04-28T16:13:00.000-05:00",
        "license_status__v": "A",
        "entity_type__v": "HCP",
        "body__v": "BON",
        "modified_date__v": "2017-04-28T16:13:00.000-05:00",
        "initial_board_license_date__v": "2014-06-20",
        "record_state__v": "VALID",
        "license_subtype__v": "U",
        "vid__v": "243244039892108289",
        "entity_vid__v": "242979976201110533",
        "is_externally_mastered__v": false,
        "type__v": "STATE",
        "custom_keys__v": [{
            "custom_key_entity_id__v": "243244039892108289",
            "custom_key_item_type__v": "LICENSE",
            "custom_key_value__v": "243244039892108289",
            "modified_date__v": "2017-04-28T16:13:00.000-05:00",
            "vid__v": "930253893256609823",
            "custom_key_vid_key__v": "MASTER__v:LICENSE:243244039892108289",
            "custom_key_source_type__v": "MASTER__v",
            "created_date__v": "2017-04-28T16:13:00.000-05:00",
            "custom_key_entity_type__v": "LICENSE",
            "custom_key_status__v": "A",
            "status_update_time__v": "2017-04-28T16:13:00.000-05:00"
        }]
    }],
    "addresses__v": [{
        "dpv_confirmed_indicator__v": "X",
        "nwk_primary_address__c": "Y",
        "postal_code_primary__v": "06810",
        "address_line_1__v": "24 Hospital Ave",
        "record_owner_type__v": "VOD",
        "premise__v": "24",
        "cbsa__v": "25420",
        "record_delta_id__v": "930253893783617535",
        "record_owner_name__v": "OpenData",
        "thoroughfare_trailing_type__v": "Ln",
        "locality__v": "Danbury",
        "delivery_address__v": "24 Hospital Ave",
        "country__v": "United States",
        "created_date__v": "2017-04-28T16:13:00.000-05:00",
        "premise_number__v": "24",
        "is_veeva_master__v": true,
        "thoroughfare__v": "Hospital Ave",
        "status_update_time__v": "2017-04-28T16:13:00.000-05:00",
        "address_type__v": "U",
        "delivery_address_1__v": "24 Hospital Ave",
        "sub_administrative_area__v": "Cumberland",
        "entity_type__v": "HCP",
        "address_verification_status__v": "V",
        "address_status__v": "A",
        "modified_date__v": "2017-04-28T16:13:07.000-05:00",
        "record_state__v": "VALID",
        "vid__v": "243244039892108291",
        "entity_vid__v": "242979976201110533",
        "postal_code__v": "06810-6099",
        "is_externally_mastered__v": false,
        "administrative_area__v": "Connecticut",
        "formatted_address__v": "24 Hospital Ave Connecticut 06810-6099 United States",
        "latitude__v": 40.261007,
        "address_ordinal__v": 1,
        "thoroughfare_name__v": "Hospital",
        "postal_code_secondary__v": "6099",
        "longitude__v": -76.963044,
        "custom_keys__v": [{
            "custom_key_entity_id__v": "243244039892108291",
            "custom_key_item_type__v": "ADDRESS",
            "custom_key_value__v": "243244039892108291",
            "modified_date__v": "2017-04-28T16:13:00.000-05:00",
            "vid__v": "930253893255495711",
            "custom_key_vid_key__v": "MASTER__v:ADDRESS:243244039892108291",
            "custom_key_source_type__v": "MASTER__v",
            "created_date__v": "2017-04-28T16:13:00.000-05:00",
            "custom_key_entity_type__v": "ADDRESS",
            "custom_key_status__v": "A",
            "status_update_time__v": "2017-04-28T16:13:00.000-05:00"
        }, {
            "custom_key_entity_id__v": "243244039892108291",
            "custom_key_item_type__v": "Address_vod__c",
            "custom_key_value__v": "a01V0000005MgywIAC",
            "modified_date__v": "2017-04-28T16:13:08.000-05:00",
            "vid__v": "930253893783519263",
            "custom_key_vid_key__v": "VCRM-00DV0000006tHq7MAE:Address_vod__c:a01V0000005MgywIAC",
            "custom_key_source_type__v": "VCRM-00DV0000006tHq7MAE",
            "created_date__v": "2017-04-28T16:13:08.000-05:00",
            "custom_key_entity_type__v": "ADDRESS",
            "custom_key_status__v": "A",
            "status_update_time__v": "2017-04-28T16:13:08.000-05:00"
        }]
    }],
    "parent_hcos__v": [],
    "research_set__c": [],
    "training_set__c": []
   }
 }

Custom key events

Web developers can associate custom keys to a record by calling a method during the select event from the widget. The select event is fired when users download a record.

Method Parameters Return
associateCustomKey (vid__v, custom_key_source_type, custom_key_item_type, custom_key_value, system_name)
  • vid__v (required) - the Network entity ID
  • custom_key_source_type (required) - The source type for the key; for example, VCRM.
  • custom_key_item_type (required)
  • - The item type for the new key; for example, HCO.
  • custom_key_value (required)
  • - The value for the new key.
  • system_name (optional)
  • - The name of the system for the new key.
The following events are fired to indicate whether the associate key call was successful or not:
  • veeva-network:<widgetName>:associate-custom-key-failed
  • veeva-network:<widgetName>:associate-custom-key-succeeded

This method creates a custom key on the record that is specified in the VID key. The custom key will be created as soon as the user downloads the record, firing the select method.

<veeva-network-search-widget
  id="searchWidget"
  widget-name="DefinedSearchWidget">
  </veeva-network-search-widget>

<script type="text/javascript">
  const widgetName = 'DefinedSearchWidget';

  // store a reference to the search widget element for event handling
  // and access to custom element's public API
  const searchWidgetEl = document.getElementById('searchWidget');

  // add the event listener for this widget's `select` event
  searchWidgetEl.addEventListener(`veeva-network:${widgetName}:select`, (ev) => {
    // get a reference to this `select`-ed record's `vid__v` value
    const vid__v = ev.detail.entity.entityId;

    // define alternative custom key format
    const custom_key_source_type = 'SOURCE_TYPE';
    const custom_key_item_type = 'ITEM_TYPE';
    const custom_key_value = 'VALUE';
    const system_name = 'SYSTEM_NAME';

    // use custom elements' `associateCustomKey` public API method
    searchWidgetEl.associateCustomKey(
      vid__v,
      custom_key_source_type,
      custom_key_item_type,
      custom_key_value,
      system_name
    );
  });

  // add the succeeded event listener for the emitted `associateCustomKey` request
  searchWidgetEl.addEventListener(`veeva-network:${widgetName}:associate-custom-key-succeeded`, (ev) => {
    // handle success case here
  });

  // add the failed event listener for the emitted `associateCustomKey` request
  searchWidgetEl.addEventListener(`veeva-network:${widgetName}:associate-custom-key-failed`, (ev) => {
    const errors = ev.detail.errors;
    // handle error case here
  });

Dialog mode

Web developers can configure the search widget so it opens in a popup window in their internal application instead of directly on the page.

Review the example code to see how this option can be configured.

<apex:page standardController="Account">

  <!-- Include the script to load widget -->
  <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
  <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-search-widget-loader.js"></script>

  <!-- These are the styles for the visualforce page. Put them in a custom-style to ensure no styles leak to the widget -->
  <custom-style>
    <style>
      .container {
        text-align: center;
      }
    </style>
  </custom-style>
  <!--
    Turn on popup mode:
      set show-in-dialog attribute to true or just add show-in-dialog attribute without setting a value.
      Note some environments (e.g. salesforce) require html attribute to have a value.
    Turn off popup mode:
      remove the show-in-dialog attribute.
  -->
  <veeva-network-search-widget
    id="searchWidget"
    widget-name="ServiceCloud"
    auth-domain="my.veevanetwork.com"
    widget-id="MTAwMDI70ztzZXJ2aWNLY2xvdWRhX19j"
    show-in-dialog="true">
  </veeva-network-search-widget>

  <!-- Add the search widget -->
  <div class="container">
    <button id="openWidget">open widget</button>
  </div>

  <script>
    const searchWidget = document.getElementById('searchWidget');
    const btn = document.getElementById('openWidget');
    btn.addEventListener('click', function () {
      searchWidget.start();
    });
  </script>

</apex:page>

Initialize the widget

You initialize the search widget using the start() method with the Network ID. When the widget is initialized, your business users can view the latest data from your Network instance from the internal application.

The start() method uses the syntax start(identifier:string), where identifier is the Network ID. It is used with any of the following properties:

  • hold-initialization - If True, start the initialization of the widget.
  • show-in-dialog - If True, open a dialog window for the search widget.

You close a widget in a dialog using the following:

  • close()

The close() method is used with the following property:

  • show-in-dialog - If True, close the dialog window for the search widget. The close event, veevanetwork:your_widget_name:close-widget, is read regardless of the value of show-in-dialog.

Each time the dialog is closed, the widget resets to the first page.

When these two blocks of code have been added to your internal website or application, your business users can access the search widget and search for the most up-to-date records in your Network instance and Veeva OpenData.

If users need to complete a transaction on a record, they can click the Select button beside the record to download it to their Network instance.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />

    <script type="text/javascript" src="https://sandboxwidgets.veevanetwork.com/veeva-network-manifest.js"></script>
    <script type="text/javascript" src="https://sandboxwidgets.veevanetwork.com/veeva-network-search-widget-loader.js"></script>

    <style>
      html, body {
        width: 100%;
        height: 100%;
      }
      #widgetContainer {
        width: 850px;
        height: 850px;
      }
      #widgetEl {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <button id="startButton" onclick="startWidget()">Start Widget</button>

    <div id="widgetContainer">
      <veeva-network-search-widget
        id="widgetEl"
        auth-domain="https://my.veevanetwork.com"
        widget-name="SampleWidget"
        widget-id="NTYyOzs7c2FtcGxld2lkZ2V0X19j"
        hold-initialization></veeva-network-search-widget>
    </div>

    <script type="text/javascript">
      var widgetEl = document.getElementById('widgetEl');

      function startWidget() {
        widgetEl.start();
      }

      widgetEl.addEventListener('veeva-network:SampleWidget:close-widget', function(ev) {
        console.log(ev.type, 'widget closed');
      });
    </script>
  </body>
</html>

Service Cloud™ - Case Study

This case study provides the example code used to create a download event that adds an account to Salesforce's Service Cloud application. There are many different ways that a download event can be created for a platform; this is just one example to display the capability of the search widget.

Scenario

The search widget has been embedded in Salesforce's Service Cloud application. A call center agent, Fernando Lopes, searches for an HCP named John Jones. The widget searches the records in Fernando's Network instance and Veeva OpenData and displays the results. He finds John Jones in the search results and reviews the record details to ensure that this is the correct HCP.

Fernando clicks Select Account to download the record to his Network instance and create a Service Cloud account for John Jones.

Solution

For this scenario, Joseph, a Service Cloud system integrator, must add custom HTML code to create the download event so a Service Cloud account can be created for the HCP record. Within the download event code, Joseph adds code to address the following requirements:

  • Salesforce remote object - The event must contain the Salesforce remote object so an account and its linked address can be created.
  • Field mappings - Map the Network fields in the search result's JSON response to the Service Cloud fields.
  • Address - Link the address child object to the account (so that the Addresses section displays).
  • Error message handling - Create error messages for the download event (failures and duplicates) so users have feedback if errors occurs.

On the Service Cloud page that contains the Network Search widget code, Joseph adds custom code to address all of the requirements.

The sections below detail each part of the code that is added. To review the entire HTML source (with comments) for the Service Cloud page that contains the search widget, see the "Search widget source code" section below.

Add the download event

Joseph adds the code to receive the download event within a <script> tag.

The event name is in the following format:

Event name

'veeva-network:<ServiceCloudA>:select'

where:

  • veeva-network is a predefined prefix. Do not change.
  • ServiceCloudA is the name of the widget that was defined in the Network search widget configuration for this example.
  • select is the predefined action name. Do not change.
const registerListeners = () => {

const searchWidget = document.getElementById('searchWidget');

// Listen download entity event on search widget
searchWidget.addEventListener(

// The event name format is: veeva-network:your-widget-name:select
// Replace <your-widget-name> with the widget name.
'veeva-network:ServiceCloudA:select',
// This is the event handler. 
(ev) => {
  // The record data is in the event detail object.
  // ev.detail.entity has the same format as the entity
  // object format in the Network API retrieve entity call
  const { entity } = ev.detail.entity;
  //This is the field mapping between Service Cloud and Network. 
  //The format is Service Cloud field name: Network field name.
  const account = {
    [FirstName: entity.first_name__v,
        LastName: entity.last_name__v,
        PersonEmail: entity.email_1__v,
        Specialty_1_vod__c: entity.specialty_1__v,
        NET_External_Id__c: entity.vid__v,
        Gender_vod__c: entity.gender__v]
  };
  // Declare a remote salesforce account object
  const salesforceAccount = new SObjectModel.Account();
  // Calling create method to create a new account in Salesforce
  // using the information we have from the field mapping above.
  salesforceAccount.create(account, (err, results, ev) => {
    // If results is not empty, the salesforce account was created.
    if (results && results.length > 0) {
      // Now start working on addresses. Here we use Promise to
      // defer the redirection to account page until we successfully
      // create all the addresses on salesforce
      new Promise((resolve, reject) => {
        // Get addresses from the downloaded record
        const addresses = entity.addresses__v;
        // If the record has addresses
        if (Array.isArray(addresses)) {
          // This is the count of calling create method 
          //on remote address object.
          // We will use it later to determine that we have finished 
          //iteration of the addresses and their creations.
          let count = 0;
          // Iterate addresses
          addresses.forEach((addr) => {
            //Address field mapping
            const address = {
              // This is where the address is linked to the account.
              // results[0] is the ID of the account
              // that we just created on salesforce
              Account_vod__c: results[0],
              Name: addr.address_line_1__v,
              Address_line_2_vod__c: addr.address_line_2__v,
              City_vod__c: addr.locality__v,
              State_vod__c: addr.administrative_area__v,
              Country_vod__c: addr.country__v
            };
            // Declare a remote salesforce address object
            const salesforceAddress = new SObjectModel.Address_vod__c();
            // Calling create method to create a new address on the
            // account we just created in salesforce with the information we
            // have from the address field mapping above.
            salesforceAddress.create(address, (addrErr) => {
              // Add one to the count once the creation call responds
              count += 1;
              // if there is an error, log it in the console
              if (addrErr) {
                console.error(addrErr.message + '\n', addrErr);
              }
              // if we have finished all the creations of the addresses
              // resolve the Promise to go to the next step
              if (count === addresses.length) {
                resolve();
              }
            });
          });
        }
      })
      // After addresses are created, go to account detail.
      .then(() => sforce.one.navigateToSObject(results[0]));
    //Error handling starts here.
    } else {
      // Error: failed to create salesforce account from downloaded record.
      // Log the error into console
      console.error(err.message + '\n', err);
      // Check if the error is because a duplicate value was found.
      const match = err.message.match(/^duplicate value found: .* with id: (.+)$/i);
      if (match) {
        // If it's duplicate error, redirect to the existing account.
        sforce.one.navigateToSObject(match[1]);
      }
    }
  });
}

);

};

if (document.attachEvent ? document.readyState === 'completed' : document.readyState !== 'loading') {
  registerListeners();
} else {
  document.addEventListener('DOMContentLoaded', registerListeners);

Include the Salesforce remote object

In addition to the download event, the HTML file must include the Salesforce remote object so a new account and its linked address can be created in the Service Cloud application. For more information about the Salesforce remote object, see the Salesforce documentation.

Joseph adds the remote objects, Account and Address_vod__c, to the HTML before the code where he receives the download event.

Add remote objects

<apex:remoteObjects>
  <apex:remoteObjectModel name="Account"></apex:remoteObjectModel>
  <apex:remoteObjectModel name="Address_vod__c"></apex:remoteObjectModel>
</apex:remoteObjects>

Define the field mapping

During the handling of the download event, the Network field names can be mapped to the Service Cloud field names so that the values in the account are accurate. The mapping format is: Service Cloud field name: Network field name.

Within the download event code, Joseph adds the following code to map the field names following the event detail object, ev.detail.entity.

Map field names

const account = {
    FirstName: entity.first_name__v,
    LastName: entity.last_name__v,
    PersonEmail: entity.email_1__v,
    Specialty_1_vod__c: entity.specialty_1__v,
    NET_External_Id__c: entity.vid__v,
    Gender_vod__c: entity.gender__v
        };

Link the address to the account

To ensure that the Service Cloud account has the address child object linked to it, Joseph adds code in the address iteration when handling the download event.

Link address object

const address = {            
    Account_vod__c: results[0],
    Name: addr.address_line_1__v,
    Address_line_2_vod__c: addr.address_line_2__v,
    City_vod__c: addr.locality__v,
    State_vod__c: addr.administrative_area__v,
    Country_vod__c: addr.country__v
        };

results[0] is the ID of the account that is being created in Service Cloud by the event code.

Error handling

Joseph adds error handling so business users have feedback if an issue occurs during the download event.

The else block handles the error when there is a failure trying to create a Salesforce account. The results variable provided in the callback of the create account API contains the information of the account created. If this variable is empty, then it means there is an error.

Secondly, Joseph identifies if it is a duplicate account error. If a business user downloads a record that has an existing account, the user will be automatically redirected to the existing account.

Error handling

} else} else {
     console.error(err.message + '\n', err);
    // Check if the error is because a duplicate value was found.
    const match = err.message.match(/^duplicate value found: .* with id:           (.+)$/i);
      if (match) {
        // If it's duplicate error, redirect to the existing account.
                sforce.one.navigateToSObject(match[1]);
              }
            }

With the new download event defined, whenever a business user clicks the Select button an account will be created in Service Cloud.

Search widget integration code

The entire code for the Service Cloud page that includes the search widget is defined below.

The code includes comments to help you navigate through each code instruction and a <custom-style> tag that contains the existing style for the Service Cloud page.

If you use the code from this section, ensure that you update any values to reflect your specific search widget.

<apex:page standardController="Account">

  <!-- Include the script to load widget -->
  <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
  <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-search-widget-loader.js"></script>

  <!--
  Include the Salesforce remote object so a new account and its linked address can be created.
  More information: https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_remote_objects_using.htm
  -->

  <apex:remoteObjects>
    <apex:remoteObjectModel name="Account"></apex:remoteObjectModel>
    <apex:remoteObjectModel name="Address_vod__c"></apex:remoteObjectModel>
  </apex:remoteObjects>

  <!-- These are the styles for the visualforce page. Put them in a custom-style to ensure no styles leak to the widget -->
  <custom-style>
    <style>
      html,
      html body.sfdcBody {
        height: 100%;
        padding: 0;
        margin: 0;
        box-sizing: border-box;
        border: 0;
      }

      html body.sfdcBody {
        background-image: url('https://na73.lightning.force.com/_slds/images/themes/oneSalesforce/banner-brand-default.png?cache=210.2'), linear-gradient(to top, rgba(32, 92, 159, 0), rgb(32, 92, 159));
        background-repeat: repeat;
        background-position: top left;
        padding: 0.75rem 0.75rem 0;
      }

      .container {
        width: 950px;
        height: 100%;
        min-height: 890px;
        box-sizing: border-box;
        border-top: 1px solid #333;
        border-top-left-radius: 0.25rem;
        border-top-right-radius: 0.25rem;
        margin: 0 auto;
      }

      .oRight .container {
        height: 690px;
        border: 1px solid #333;
        border-radius: 0.25rem;
      }
    </style>
  </custom-style>

  <!-- Add the search widget -->
  <div class="container">

    <veeva-network-search-widget
      id="searchWidget"
      widget-name="ServiceCloud"
      widget-id="MzA3Ozs7c2VydmljZWNsb3VkX19j"
      auth-domain="my.veevanetwork.com"
      enriched-results="true"
      enriched-results-language="en"></veeva-network-search-widget>
  </div>

  <!-- Add the event to download the record to Service Cloud and create a new account -->
  <script type="text/javascript">
    const registerListeners = () => {
      const searchWidget = document.getElementById('searchWidget');

      // listen download entity event on search widget
      searchWidget.addEventListener(
        // The event name format is: veeva-network:your-widget-name:select
        // replace <your-widget-name> with the widget name from step 2 above.
        'veeva-network:ServiceCloud:select',

        // This is the event handler.
        (ev) => {
          // The record data is in the event detail object.
          // ev.detail.entity has the same format as the entity
          // object format in the Veeva Network API retrieve entity call
          const { entity } = ev.detail.entity;
          console.log(ev.detail.entity);
          console.log(entity.vid__v);

          //This is the field mapping between Service Cloud and Network. The format is: Service Cloud field name: Network field name.
          const account = {
            FirstName: entity.first_name__v,
            LastName: entity.last_name__v,
            PersonEmail: entity.email_1__v,
            Specialty_1_vod__c: entity.specialty_1__v_value__u,
            NET_External_Id__c: entity.vid__v,
            Gender_vod__c: entity.gender__v_value__u,
          };

          // Declare a remote salesforce account object
          const salesforceAccount = new SObjectModel.Account();

          // Calling create method to actually create a new account in Salesforce
          // using the information we have from the field mapping above.
          salesforceAccount.create(account, (err, results, ev) => {
            // If results is not empty , it means we successfully created salesforce account
            console.log(results[0]);
            const custom_key_source_type = 'ServiceCloud';
            const custom_key_item_type = 'HCP';
            const system_name = 'ServiceCloud';

            // use custom elements' `associateCustomKey` public API method
            // NOTE: This is not limited to only being run from the `select`
            // event callback, and can be issues at any point in time.
            searchWidget.associateCustomKey(
              entity.vid__v,
              custom_key_source_type,
              custom_key_item_type,
              results[0],
              system_name
            );

            if (results && results.length > 0) {
              // Now start working on addresses. Here we use Promise to
              // defer the redirection to account page until we successfully
              // create all the addresses on salesforce
              new Promise((resolve, reject) => {
                // Get addresses from the downloaded record
                const addresses = entity.addresses__v;

                // If the record has addresses
                if (Array.isArray(addresses)) {
                  // This is the count of calling create method on remote address object.
                  // We will use it later to determine that we have finished iteration of
                  // the addresses and their creations.
                  let count = 0;

                  // Iterate addresses
                  addresses.forEach((addr) => {
                    //Address field mapping
                    const address = {
                      // This is where the address is linked to the account.
                      // results[0] is the id of the account we just created on salesforce
                      Account_vod__c: results[0],
                      Name: addr.address_line_1__v,
                      Address_line_2_vod__c: addr.address_line_2__v,
                      City_vod__c: addr.locality__v,
                      State_vod__c: addr.administrative_area__v_value__u,
                      Country_vod__c: addr.country__v,
                      Zip_vod__c: addr.postal_code__v,
                      External_ID_vod__c: addr.vid__v,
                    };

                    // Declare a remote salesforce address object
                    const salesforceAddress = new SObjectModel.Address_vod__c();
                    // Calling create method to create a new address on the
                    // account we just created in salesforce with the information we
                    // have from the address field mapping above.
                    salesforceAddress.create(address, (addrErr) => {
                      // Add one to the count once the creation call responds
                      count += 1;

                      // if there is an error, log it in the console
                      if (addrErr) {
                        console.error(addrErr.message + '\n', addrErr);
                      }

                      // if we have finished all the creations of the addresses
                      // resolve the Promise to go to the next step
                      if (count === addresses.length) {
                        resolve();
                      }
                    });
                  });
                }
              })
              // After addresses are created, go to account detail.
              .then(
                () => sforce.one.navigateToSObject(results[0])
              );

            } else {
              //Error handling starts here.

              // Error: failed to create salesforce account from the downloaded record.
              // log the error into console
              console.error(err.message + '\n', err);

              // Check if the error is because a duplicate value was found.
              const match = err.message.match(/^duplicate value found: .* with id: (.+)$/i);

              if (match) {
                // If it's duplicate error, redirect to the existing account.
                sforce.one.navigateToSObject(match[1]);
              }
            }
          });
        }
      );
    };

    if (
      document.attachEvent
        ? document.readyState === 'completed'
        : document.readyState !== 'loading'
    ) {
      registerListeners();
    } else {
      document.addEventListener('DOMContentLoaded', registerListeners);
    }
  </script>
</apex:page>

Network Profile widget

Network’s profile widget code provides all of the following development tasks that are required for integration:

  • User interface for viewing profiles.
  • Integration with the Network APIs for displaying Network profiles from your application.
  • Ongoing maintenance to support new features and data model changes.

The profile widget can also be configured to support edit mode to enable users to update records and submit data change requests.

How does it work?

The Network profile widget is supported for internal applications.

Administrators configure the widget settings in Network and then web developers add the generated code to their internal application. Then, from that application, users can open a profile and view Network's latest data for an HCO or HCP. Only profiles that users have access to through their assigned Network data visibility profile will display.

Applications must meet the following requirements:

  • The application is web-based.
  • Your Network instance uses single sign-on (SSO) authentication (every user must have a Network/SSO account).

Adding Network's profile widget in your internal application includes the following steps, depending on the configuration you choose:

  1. Configure the widget - Network administrators set up the widget in their Network instance. When the configuration is saved, code snippets are generated.
  2. Embed the widget - Web developers embed the generated code snippets into the portal.
  3. Initialize the widget - Web developers initialize the widget by opening records using Network entity IDs (VIDs) or custom keys.
  4. Define events to support edit mode – If administrators have configured the profile widget to allow users to edit records and submit change requests, web developers must add events to the profile widget to get the latest copy of the record (including new under review records) and to retrieve the task ID or the task details.

Embed the widget

Before you can embed a widget in a web application, a Network administrator must configure the widget in a Network instance and generate the code you need to embed.

Load the profile widget

The following code loads the profile widget, and must be included in your HTML page.

<script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-networkmanifest.js"></script>
<script type="text/javascript" src="widgets.veevanetwork.com/veevanetwork-profile-widget-loader.js"></script>

Ensure that the veeva-networkmanifest.js script is loaded first.

Enable the profile widget

The veeva-network-profile-widget element enables you to view the record profile. You include it within the <body> elements of your HTML page.

Use the following code to enable the widget:

<veeva-network-profile-widget
    widget-name="ProfileWidget"
    auth-domain="my.veevanetwork.com"
    widget-id="MTAwMDI7Oztwcm9maWxld2lkZ2V0X19j">
</veeva-network-profile-widget>

This code is generated when your Network administrator completes widget configuration in the Network instance.

Required Properties
widget-name
The name that the Network administrator defined during profile widget configuration in the Network instance.
auth-domain
The domain of your Network instance.
widget-id
A code generated by Network that applies to the profile widget configuration. If you make style changes to the widget in the profile widget configuration in the Network instance and click Generate Code again, the widget-id does not change.

Additional properties

Optional Properties
closeable
Boolean (default False). The ability to close the widget. Set this attribute to True to add a close (x) button to the top right corner of the widget header.

When the close button is clicked, the veeva-network:your_widget_name:close-widget event is invoked so you can add your own close event listener.
hold-initialization
Boolean (default False). Set this attribute to True to hold the initialization of the content of this widget. This is useful for preventing the authentication flow that displays a new window for single sign-on when your page is loading, to avoid disruption.

You can start the initialization of the content in the widget later by calling the start() function.
identifier
The Veeva Network ID (18-digit entity ID) or custom key.
show-in-dialog
Boolean (default False). Set this attribute to True to display the widget in a default dialog provided by Network. When True, both hold-initialization and closeable are also implicitly True.

When you call the start() function, the dialog with the widget will open. You can add a close() function to close the dialog.

Note: You must provide a button or link on the page for users to access the profile widget from a dialog.
wait-for-auto-approval

Boolean (default False). Set this attribute to True to wait for a DCR to be auto-approved before returning the record to end users.

When DCRs are set to be auto-approved, there can be a short delay processing the changes after they are submitted through the Profile or DCR widget. To help end users see the expected updates, this property will insert a short delay for the DCR to be auto-approved before returning the record.

When a DCR is auto-approved, the widget will return the latest record with the requested changes. When a DCR is not auto-approved, the widget will provide the latest record, but the requested changes may not yet be on the record.

demo-mode
Boolean (default False). Set this attribute to True to test the widget without single sign-on configured in sandbox instances only. Before using this attribute, you must request demo mode access through Veeva Support.
username
When demo-mode is True, this attribute specifies the user name required to access the widget.
password
When demo-mode is True, this attribute specifies the password required to access the widget.

Sample: Enable a widget with all properties

<veeva-network-profile-widget
    widget-name="ProfileWidget"
    auth-domain="my.veevanetwork.com"
    widget-id="MTAwMDI7Oztwcm9maWxld2lkZ2V0X19j"
    closeable="true"
    hold-initialization="true"
    identifier="Network:HCP:214066274852930560"
    show-in-dialog="true"
    wait-for-auto-approval="true"
    demo-mode="true"
    username="john.smith@my.veevanetwork.com"
    password="12345">
</veeva-network-profile-widget>

Initialize the widget

You initialize the profile widget using the start() method with the Network ID. When the widget is initialized, your business users can view the latest data from your Network instance from the internal application.

The start() method uses the syntax start(identifier:string), where identifier is the Network ID. It is used with any of the following properties:

  • hold-initialization - If True, start the initialization of the widget.
  • show-in-dialog - If True, open a dialog window for the profile widget.
  • identifier - The Network ID.

You close a widget in a dialog using the following:

  • close()

The close() method is used with the following property:

  • show-in-dialog - If True, close the dialog window for the profile widget.
  • The close event, veevanetwork:your_widget_name:close-widget, is read regardless of the value of show-in-dialog.

Implementation example

Web developers determine how the profile widget is accessed in their internal application; for example, the it could be linked to the HCP or HCO name in a portal. When the user clicks the name, the profile widget initializes and the record profile displays.

When a user performs the action to open the profile widget, it calls the start function:

profileWidget.start(document.getElementById('networkVid').value)

In this example, a web developer is configuring the profile widget to open when a user clicks the name of an HCP or HCO in their internal portal.

// For example, you have a list of entries (yourEntryList) with entity id in your UI.
    // You can add a click event listener to your list.
    yourEntryList.addEventListener('click', function (ev) {
        const target = ev.target;
        // then when the click event pops up to the list, you can check if
        // the event target is the trigger element of the profile widget,
        // for example, a name or an ID
        if (isProfileWidgetTrigger(target)) {
            // If it is the profile widget trigger element, then you can
            // trigger the profile widget to open a profile by calling the start
            // function on the profile widget element with the entity id
            // you get from your target element (here we assume you
            // already have the reference of the profile widget element).
            profileWidgetElement.start(getEntityId(target));
        }
    });

Define events to support edit mode

The profile widget is completely functional for business users to view records when the two provided code blocks generated by Network have been added to your internal application.

You must create events to support edit mode only if administrators have enabled the option in the profile widget configuration to allow users to submit data change requests. Check with your Network administrator to see if the profile widget supports data change requests.

Three events must be created to support data change requests:

  • dcr-presubmit provides the task data.
  • dcr-submitted provides a task ID.
  • select provides the latest copy of the record from the Network instance (including any new sub-objects that the user added in the change request).

Event requirements

Events require the following:

  • an event name
  • event entity data in JSON format

The event name uses the following format:

'veeva-network:ServiceCloudA:dcr-submitted'

where:

  • veeva-network is a predefined value. Do not change.
  • ServiceCloudA is the name of the widget that was defined in the Network profile widget configuration for this example. Replace this with the name of your profile widget.
  • dcr-submitted is the event name.

dcr-presubmit event

The dcr-presubmit event provides the task data for the changes that are submitted.

Sample event name

'veeva-network:ServiceCloudA:dcr-presubmit'

The event entity data for the dcr-presubmit event uses the following value:

Event entity data

(ev) => {
            const { eventdetails } = ev.detail;
}

where:

  • ev is the name of the event object. This value can be changed to your event object name.
  • ev.detail provides the JSON of the changes to the record.

Network provides a JSON response that includes all of the details for the change request, including the creator, requester notes, and all of the previous and new field values for the record and the associated child objects (addresses, licenses, parent HCO affiliations).

{
    "metadata": {
        "creator": "linda.wong@my.veevanetwork.com",
        "system": "ProfileWidget",
        "note": "John Smith is a new doctor in the Network Hospital",
        "source": "ProfileWidget-ServiceCloudProfile",
        "system": "ServiceCloud"
    },
    "entity_type": "HCP",
    "vid_key": "Network:HCP:214066274852930560",
    "entity": {
        "attribute 1": "Value 1",
        "attribute 2": "Value 2",
        " ... ": " ... ",
        "addresses__v": [
            {
                "vid_key": "Network:Address:214066274852930560",
                "attribute 1": "Value 1",
                "attribute 2": "Value 2",
                " ... ": " ... "
            }
        ],
        "licenses__v": [
            {
                "vid_key": "Network:License:340010313220883456",
                "address_vid_key": "Network:Address:26090478086232576",
                "attribute 1": "Value 1",
                "attribute 2": "Value 2",
                " ... ": " ... "
            }
        ],
        "parent_hcos__v": [
            {
                "vid_key": "Network:ParentHCO:340010217892742157",
                "parent_hco_vid_key": "Network:HCO:215249678634058752",
                "attribute 1": "Value 1",
                "attribute 2": "Value 2",
                " ... ": " ... "
            }
        ]
    }
}

dcr-submitted event

The dcr-submitted event provides the task ID for the changes that are submitted.

Sample event name

'veeva-network:ServiceCloudA:dcr-submitted'

The event entity data for the dcr-submitted event uses the following value:

Event entity data

(ev) => {
            const { eventdetails } = ev.detail;
}

where:

  • ev is the name of the event object. This value can be changed to your event object name.
  • ev.detail provides the JSON for the task ID.

Network provides a JSON response that includes the task ID for the change request.

Sample JSON response

{taskId: "934001685804876191"}

select event

The select event provides the latest copy of the record from the Network instance (including any new sub-objects that the user added in the change request).

Sample event name

'veeva-network:ServiceCloudA:select'

The event entity data for the select event uses the following value:

(ev) => {
            const { entity } = ev.detail.entity;
}

where:

  • ev is the name of the event object. This value can be changed to your event object name.
  • ev.detail.entity provides the JSON of the record that is the same object as the first object in the entities array returned from the Network Retrieve API. There is only one entity provided in the JSON.

Network provides a JSON response that includes all of the field values for the entity and the associated child objects (addresses, licenses, parent HCO affiliations).

{
    "entityId": "934001011900023167",
    "entityType": "HCP",
    "metaData": {},
    "entity": {
        "specialty_1__v": "Acute Care",
        "hcp_type__v": "P",
        "first_name__v": "David",
        "record_owner_type__v": "LOCAL",
        "grad_training__v": "N",
        "npi_num__v": "052005210521051",
        "record_delta_id__v": "934001072423731199",
        "last_name__v": "Smith",
        "record_owner_name__v": "Local",
        "fellow__v": "N",
        "kaiser__v": "N",
        "formatted_name__v": "David Smith",
        "primary_country__v": "US",
        "created_date__v": "2019-02-19T15:34:34.000Z",
        "pdrp_optout__v": "N",
        "is_veeva_master__v": false,
        "hcp_status__v": "A",
        "ama_do_not_contact__v": "N",
        "status_update_time__v": "2019-02-19T15:34:34.000Z",
        "modified_date__v": "2019-02-19T15:49:55.000Z",
        "verteo_id__c": "V-M9120V15BZ",
        "record_state__v": "UNDER_REVIEW",
        "global_target__c": 0,
        "record_version__v": 1,
        "vid__v": "934001011900023167",
        "is_externally_mastered__v": false,
        "specialty_1_rank__v": 1,
        "custom_keys__v": [{
                "custom_key_entity_id__v":
                "934001011900023167",
                "custom_key_item_type__v": "HCP",
                "custom_key_value__v": "934001011900023167",
                "modified_date__v": "2019-02-19T15:34:34.000Z",
                "vid__v": "934001011995181439",
                "custom_key_vid_key__v":
                "CUSTOMER__v/3/change_request:HCP:934001011900023167",
                "custom_key_source_type__v":
                "CUSTOMER__v/3/change_request",
                "created_date__v": "2019-02-19T15:34:34.000Z",
                "custom_key_entity_type__v": "HCP",
                "custom_key_status__v": "A",
                "status_update_time__v": "2019-02-19T15:34:34.000Z"
            }, {
                "custom_key_entity_id__v":
                "934001011900023167",
                "custom_key_item_type__v": "DCRID__v",
                "custom_key_value__v": "934001011901333919",
                "modified_date__v": "2019-02-19T15:34:34.000Z",
                "vid__v": "934001011995443583",
                "custom_key_vid_key__v":
                "WORKFLOW__v:DCRID__v:934001011901333919",
                "custom_key_source_type__v": "WORKFLOW__v",
                "created_date__v": "2019-02-19T15:34:34.000Z",
                "custom_key_entity_type__v": "HCP",
                "custom_key_status__v": "A",
                "status_update_time__v": "2019-02-19T15:34:34.000Z"
            }],
        "licenses__v": [],
        "addresses__v": [{
                "address_line_1__v": "30 York Street",
                "record_owner_type__v": "LOCAL",
                "record_delta_id__v": "934001072381526016",
                "record_owner_name__v": "Local",
                "locality__v": "New York",
                "country__v": "US",
                "created_date__v": "2019-02-19T15:49:55.000Z",
                "network_primary__c": "N",
                "is_veeva_master__v": false,
                "status_update_time__v": "2019-02-19T15:49:55.000Z",
                "address_type__v": "P",
                "entity_type__v": "HCP",
                "address_verification_status__v": "U",
                "address_status__v": "A",
                "modified_date__v": "2019-02-19T15:49:55.000Z",
                "record_state__v": "UNDER_REVIEW",
                "vid__v": "934001072372056448",
                "entity_vid__v": "934001011900023167",
                "postal_code__v": "1004",
                "administrative_area__v": "New York",
                "formatted_address__v": "30 York Street New York US-NY 1004",
                "address_ordinal__v": 2,
                "custom_keys__v": []
            }, {
                "address_line_1__v": "40 York Street",
                "record_owner_type__v": "LOCAL",
                "record_delta_id__v": "934001012009304064",
                "record_owner_name__v": "Local",
                "locality__v": "New York",
                "country__v": "US",
                "created_date__v": "2019-02-19T15:34:34.000Z",
                "network_primary__c": "Y",
                "is_veeva_master__v": false,
                "status_update_time__v": "2019-02-19T15:34:34.000Z",
                "address_type__v": "P",
                "entity_type__v": "HCP",
                "address_verification_status__v": "U",
                "address_status__v": "A",
                "modified_date__v": "2019-02-19T15:34:34.000Z",
                "record_state__v": "UNDER_REVIEW",
                "vid__v": "934001011986465152",
                "entity_vid__v": "934001011900023167",
                "postal_code__v": "10004",
                "administrative_area__v": "New York",
                "formatted_address__v": "40 York Street New York US-NY 10004",
                "address_ordinal__v": 1,
                "custom_keys__v": []
            }],
        "parent_hcos__v": [],
        "training_set__c": []
    }
} 

Network Affiliation widget

The affiliation widget enables business users to create influence maps for their health systems. Sales representatives, home office users, and key account managers can use the influence map to understand the main influencers at a hospital so they know who to target in a product's pre-commercial stage.

Users can add HCPs and HCOs to their influence map from Network and OpenData and define the relationship between the records. Custom-defined metrics can be tracked from the influence map; for example, Influence and Product Familiarity.

Records are restricted to a user's data visibility profile permissions.

How it works

Network's Affiliation widget is supported for the Network Portal and web-based internal applications; for example, intranets or platforms like Salesforce™.

Applications must meet the following requirements:

  • The application is web-based.
  • Your Network instance uses single sign-on (SSO) authentication (every user must have a Network/SSO account).

Using SSO ensures that business users with Network user profiles can access the widget directly from your internal application using their Network credentials.

Adding Network's affiliation widget in your internal application includes the following steps, depending on the configuration you choose:

  1. Configure the widget - Network administrators set up the widget in their Network instance. When the configuration is saved, code snippets are generated.
  2. Embed the widget - Web developers embed the generated code snippets into the portal.
  3. Initialize the widget - Web developers initialize the widget by opening records using Network entity IDs (VIDs) or custom keys.

Embed the widget

Before you can embed a widget in a web application, a Network administrator must configure the widget in a Network instance and generate the code you need to embed.

Load the affiliation widget

The following code loads the affiliation widget, and must be included in your HTML page.

<script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-networkmanifest.js"></script>
<script type="text/javascript" src="widgets.veevanetwork.com/veevanetwork-affiliation-widget-loader.js"></script>

Ensure that the veeva-network-manifest.js script is loaded first.

Enable the affiliation widget

The veeva-network-affiliation-widget element enables you to view the affiliations. You include it within the <body> elements of your HTML page.

<veeva-network-affiliation-widget
    widget-name="AffiliationWidget"
    auth-domain="my.veevanetwork.com"
    widget-id="MzA3Ozs7YWZmd2lkZ2V0X19j">
</veeva-network-affiliation-widget>

This code is generated when your Network administrator completes widget configuration in the Network instance.

Required Properties
widget-name
The name that the Network administrator defined during affiliation widget configuration in the Network instance.
auth-domain
The domain of your Network instance.
widget-id
A code generated by Network that applies to the affiliation widget configuration. If you make style changes to the widget in the affiliation widget configuration in the Network instance and click Generate Code again, the widget-id does not change.
Optional Properties
full-screen-mode
Boolean (default False). This attribute enables users to click a full-screen button to view the affiliation widget in full-screen mode. This property is only applicable to widgets embedded in Salesforce.
display-hco-metrics
Boolean (default False). Display metrics in the Affiliation widget. The metrics displayed are dependent on the metrics added by an Administrator in Network, during widget configuration.
identifier
The Veeva Network ID (18-digit entity ID) or custom key.
demo-mode
Boolean (default False). Set this attribute to True to test the widget without single sign-on configured in sandbox instances only. Before using this attribute, you must request demo mode access through Veeva Support.
username
When demo-mode is True, this attribute specifies the user name required to access the widget.
password
When demo-mode is True, this attribute specifies the password required to access the widget.

Sample: Enable a widget with all properties

<veeva-network-affiliation-widget
    widget-name="AffiliationWidget"
    auth-domain="my.veevanetwork.com"
    widget-id="MzA3Ozs7YWZmd2lkZ2V0X19j"
    full-screen-mode="true"
    display-hco-metrics="true"
    demo-mode="true"
    username="john.smith@my.veevanetwork.com"
    password="12345">
</veeva-network-affiliation-widget>

Initialize the widget

You initialize the affiliation widget using the start() method with the Network ID. When the widget is initialized, your business users can view the latest data from your Network instance from the internal application.

The start() method uses the syntax start(identifier:string), where identifier is the Network ID. It is used with any of the following properties:

  • hold-initialization - If True, start the initialization of the widget.
  • show-in-dialog - If True, open a dialog window for the widget.
  • identifier - The Network ID.

You close a widget in a dialog using the following:

  • close()

The close() method is used with the following property:

  • show-in-dialog - If True, close the dialog window for the widget.

The close event, veevanetwork:your_widget_name:close-widget, is read regardless of the value of show-in-dialog.

Embed the widget in Veeva CRM Online

The Affiliation widget can be used in your Veeva CRM Online application.

To embed the widget:

  1. Create a Visualforce page (see the Salesforce™ documentation. These Visualforce pages for Salesforce Lightning or Salesforce Classic can be used as a template.
  2. Update the widget snippet with your own generated code.

    <apex:page standardController="Account">
    
       <!-- Include the script to load widget -->
    
       <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
       <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-affiliation-widget-loader.js"></script>
    
    
      <!-- These are the styles for the visualforce page. Put them in a custom-style to ensure no styles leak to the widget -->
      <custom-style>
        <style>
           html,
          html body.sfdcBody {
            height: 100%;
            padding: 0;
            margin: 0;
            box-sizing: border-box;
            border: 0;
          }
    
          html body.sfdcBody {
            background-image:
              url('https://na73.lightning.force.com/_slds/images/themes/oneSalesforce/banner-brand-default.png?cache=210.2'),
              linear-gradient(to top, rgba(32, 92, 159, 0), rgb(32, 92, 159));
            background-repeat: repeat;
            background-position: top left;
            padding: 0.02rem 0.02rem 0;
          }
    
          .container {
            height: 100%;
            box-sizing: border-box;
            border-top: 1px solid #333;
            border-top-left-radius: 0.25rem;
            border-top-right-radius: 0.25rem;
            margin: 0 auto;
          }
    
          .oRight .container {
            border: 1px solid #333;
            border-radius: 0.25rem;
          }
    
        #widgetEl {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
          }
    
        </style>
      </custom-style>
    
      <!-- Add the affiliation widget -->
      <div class="container">
    
       <veeva-network-affiliation-widget
            id="widgetEl"
            auth-domain="verteo.veevanetwork.com"
            widget-name="AffiliationWidgetName"
            widget-id="NTE0Ozs7aGVhbHRoc3lzdGVtc19fYw=="></veeva-network-affiliation-widget>
        </div>
    </apex:page>

    <apex:page standardController="Account" sidebar="false">
       <!-- Include the script to load widget -->
       <script type="text/javascript" src="https://sandboxwidgets.veevanetwork.com/veeva-network-manifest.js"></script>
       <script type="text/javascript" src="https://sandboxwidgets.veevanetwork.com/veeva-network-affiliation-widget-loader.js"></script> 
    
       <!-- These are the styles for the visualforce page. Put them in a custom-style to ensure no styles leak to the widget -->
       <custom-style>  
          <style> 
             #widgetEl {
             width: 100%;
             height: 1000px;
             margin: 0;
             padding: 0;
             }
             .noSidebarCell {
             padding: 0px
             }
          </style>
       </custom-style>
       <div class="container">
          <veeva-network-affiliation-widget
             id="widgetEl"
             auth-domain="verteo.veevanetwork.com"
             widget-name="AffWidget"
             widget-id="MzA3Ozs7YWZmd2lkZ2V0X19j"
          full-screen-mode = "true">
          </veeva-network-affiliation-widget>
       </div>
    </apex:page>

  3. Create a Visualforce tab. Follow the steps in the Salesforce documentation.

Advanced widget techniques

The following sections outline techniques for developing widgets for unique situations.

Integrating widgets without single sign-on

You can test a widget without using single sign-on by configuring it to use demo mode. Demo mode is available for Sandbox instances only, and must be requested through Veeva Support.

After Veeva Support has enabled demo mode, you can use the following attributes for your widget:

  • demo-mode - Set this attribute to True to enable demo mode for the widget.
  • username - The user name required to access the widget.
  • password - The password required to access the widget.

The following example enables demo mode for a search widget:

<veeva-network-search-widget
    id="searchWidget"
    widget-name="ServiceCloud" 
    auth-domain="my.veevanetwork.com" 
    widget-id="MTAwMDI7OztzZXJ2aWNlY2xvdWRhX19j"
    demo-mode="true"
    username="john.smith@my.veevanetwork.com"
    password="12345">
</veeva-network-search-widget>

Embedding widgets dynamically

In order to embed multiple widgets on a page; for example, when splitting HCO and HCP searches, you will need to make the widgets dynamic. With dynamic widgets, all or part of the originating source widget configuration could change or configurable properties may need to be different based on decisions made in the web application.

The following sections describe each type of dynamic widget.

Partially dynamic widgets

In this example, the widget is initialized with the widget ID and name, while other attributes are set dynamically.

This method requires the hold-initialization flag to be True.

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
    <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-profile-widget-loader.js"></script>
  </head>
  <body>
    <div id="widgetContainer">
      <!-- for a partially dynamic solution, we can simply stub
      out the properties that we know will remain constant. Though,
      with this strategy we require the hold-initialization flag to
      be set to true -->

      <veeva-network-search-widget
        id="widgetEl"
        auth-domain="my.veevanetwork.com"
        show-in-dialog="true"
        hold-initialization="true"></veeva-network-search-widget>
    </div>

    <script type="text/javascript">
      (function() {
        // set references to pertinent DOM elements
        var widgetContainerEl = document.getElementById('widgetContainer');
        var widgetEl = document.getElementById('widgetEl');

        // set/calculate dynamically set properties
        var widgetName = 'XXX';
        var widgetId = 'XXX';

        // set properties dynamically
        widgetEl.setAttribute('widget-id', widgetId);
        widgetEl.setAttribute('widget-name', widgetName);
        // any other attributes you'd like

        // can also set up widget event bindings here
        widgetEl.addEventListener(`veeva-network:${widgetName}:select`, function(ev) {
          console.log(ev.detail);
        });

        // finally, we can issue a request to start the widget
        // NOTE: if this doesn't work for you, try bracing this in a `setTimeout` clause
        // to allow the widget element to fully be inserted into the DOM
        widgetEl.start();
        // setTimeout(function() { widgetEl.start(); }, 100);
      })();
    </script>
  </body>
</html>

Fully dynamic widgets

In this example, the all of the attributes for the widget are generated dynamically, from scratch.

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
    <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-profile-widget-loader.js"></script>
  </head>
  <body>
    <div id="widgetContainer"></div>

    <script type="text/javascript">
      (function() {
        // for this strategy, we will be creating the widget from scratch!
        var widgetContainerEl = document.getElementById('widgetContainer');

        var widgetName = 'XXX';
        var widgetId = 'XXX';

        // dynamically create widget element and properties
        var widgetEl = document.createElement('veeva-network-search-widget');
        widgetEl.setAttribute('widget-id', widgetId);
        widgetEl.setAttribute('widget-name', widgetName);
        widgetEl.setAttribute('auth-domain', 'my.veevanetwork.com');
        // any other attributes you'd like

        // can also set up widget event bindings here
        widgetEl.addEventListener(`veeva-network:${widgetName}:select`, function(ev) {
          console.log(ev.detail);
        });

        // finally, add the widget element to the DOM tree (need to make sure that
        // there is always at most one widget in the DOM otherwise. this is a known
        // limitation of the widget installation process)
        widgetContainerEl.innerHTML = '';
        widgetContainerEl.appendChild(widgetEl);

        // once the widget element enters the DOM, it will automatically be mounted
        // and open automatically. to change this behaviour, you can set the
        // `hold-initialization` flag to true. See pre-existing documentation on this property.
      })();
    </script>
  </body>
</html>

Embedding a widget in a frame

In rare circumstances, you may want to embed your widget in an iframe to access it properly.

This may be the case if, during widget loading, an exception indicates that resources are already in use or that elements have been duplicated on the page. This results in a blank space appearing where the widget should be.

Create the iframe document

The details of the widget are contained in a separate file; for example, widget_embed.html.

The embed file contains the usual definition for the widget, as follows:

widget_embed.html

<veeva-network-search-widget
    id="searchWidget"
    widget-name="Expense"
    auth-domain=“my.veevanetwork.com"
    widget-id="NTE0Ozs7dGVzdGluZ19fYw==">
</veeva-network-search-widget>

In addition to the widget definition, this file also contains the requisite handler and listener for events within the embedded widget.

In this example, the event data to be passed is defined in the eventData declaration.

<!-- Embedded widget event handling -->
<script type="text/javascript">
    // this method handles the generic behaviour of an emitted Network widget
    function handleWidgetEvent(ev) {
        var iframeURL = 'http://localhost:8000';

        // only pass this subset of the event details
        var eventData = {
            type: ev.type,
            detail: ev.detail
        };

        // the event data must be represented as a string
        eventData = JSON.stringify(eventData);

        // post the message to the parent that hosts this iframe
        window.parent.postMessage(eventData, iframeURL);
    }

    // add usual event listeners for the embedded widget
    window.addEventListener('veeva-network:Expense:select', handleWidgetEvent);
</script>

The event data, along with the URL of the embed, is passed to the parent document (in this case, the index.html file that embeds it), through the window.parent.postMessage method.

Embedding the widget file

The widget file you create is referenced from the application’s index.html file:

<!-- Sample iframe definition -->
<div id="iframeContainer">
   <iframe id="widgetiframe" src=“widget_embed.html"></iframe>
</div>

This file also contains the hooks required to receive event data, through the window.addEventListener call.

<!-- Embedded widget event handling -->
<script type="text/javascript">
    // listen to any events emitted by the iframe
    window.addEventListener('message', function(ev) {
        var iframeURL = 'http://localhost:8000';

        // disallow any messages incoming from another origin
        if (ev.origin !== iframeURL) return;

        // we need to parse the event data that was stringified
            in the iframe
        var eventData = JSON.parse(ev.data);

        // handle embedded widget events here
        switch (eventData.type) {
            case 'veeva-network:Expense:select':
            console.log('Received', eventData.type, 'with data:',           eventData.detail, 'outside iframe');
            logEventData(eventData);
            break;
            default:
            break;
        }
    });

    function logEventData(eventData) {
        var eventLogEl = document.getElementById('eventLog');
        eventLogEl.innerText = eventLogEl.innerText + 
            JSON.stringify(eventData) + '\n';
    }
</script>

The message attribute in the call is derived from the event data posted from the windowparent.postMessage call in the widget_embed.html file.

Embedding a widget in a frame using dialog mode

As with the examples in the previous section, you may want to embed your widget in an iframe to access it properly in the event of loading errors.

The following examples illustrate how to do this by embedding the widget as a dialog.

iframe.html:

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-manifest.js"></script>
    <script type="text/javascript" src="https://widgets.veevanetwork.com/veeva-network-profile-widget-loader.js"></script>

    <style>
      /* remove unwanted spaces when embedded within iframe */
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
      /* ensure that this element always has the maximum dimensions of the iframe */
      veeva-network-search-widget {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <!-- Sample embedded search widget -->
    <veeva-network-search-widget
      id="searchWidget"
      widget-name="Expense"
      auth-domain="verteo.veevanetwork.com"
      widget-id="NTE0Ozs7dGVzdGluZ19fYw=="
      hold-initialization
      show-in-dialog
      closable>
    </veeva-network-search-widget>

    <!-- Embedded wiget event handling -->
    <script type="text/javascript">
      // this method handles the generic behaviour of an emitted Network widget
      function handleWidgetEvent(ev) {
        var iframeURL = 'http://localhost:8000';

        // only pass this subset of the event details
        var eventData = {
          type: ev.type,
          detail: ev.detail
        };
        // the event data must be represented as a string
        eventData = JSON.stringify(eventData);

        // post the message to the parent that hosts this iframe
        window.parent.postMessage(eventData, iframeURL);
      }
      // add usual event listeners for the embedded widget
      window.addEventListener('veeva-network:Expense:select', handleWidgetEvent);
      window.addEventListener('veeva-network:Expense:close-widget', handleWidgetEvent);

      // listen to any events emitted by the host
      window.addEventListener('message', function(ev) {
        var hostURL = 'http://localhost:8000';

        // disallow any messages incoming from another origin
        if (ev.origin !== hostURL) return;

        // we can assume that the data always corresponds to a method of the widget API
        var searchWidgetEl = document.getElementById('searchWidget');

        // handle embedded widget events here
        switch (ev.data) {
          case 'start':
            searchWidgetEl.start();
            break;
          default:
            break;
        }
      });
    </script>
  </body>
</html>

index.html:

<!DOCTYPE html>
<html>
  <head>
    <link rel="import" href="../../polymer/lib/elements/dom-module.html">

    <!-- styles for demo pages, not necessary for configuration -->
    <link rel="stylesheet" type="text/css" href="../../demo.css" />

    <style>
    #iframeContainer {
      margin: 0 auto;
      width: 960px;

      /* we are setting the below to ensure that the iframe container doesn't take any space on the host page */
      overflow: hidden;
      height: 0px;
      box-sizing: border-box;
    }
    iframe {
      /* to achieve the desired effect of a dialog, we will position this in relation to the window */
      position: relative; /* this value will be toggled between `relative` and `fixed` when the dialog is opened */
      z-index: 1000;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;

      /* remove unwanted border on iframe when embedding widget */
      border: none;
    }
    </style>
  </head>
  <body>
    <a href="/">Back to Home</a>

    <h1>IFrame Installation with Dialog</h1>

    <p>
        Embedded widget instance inside a dialog.
    </p>

    <button id="startWidgetBtn" onclick="startWidget()">Start Widget</button>

    <div class="banner">iframe start</div>

    <!-- Sample iframe definition -->
    <div id="iframeContainer">
      <iframe id="widgetIFrame" src="iframe.html"></iframe>
    </div>

    <div class="banner">iframe end</div>

    <h2>Event Log</h2>
    <textarea id="eventLog" disabled></textarea>

    <!-- Embedded widget event handling -->
    <script type="text/javascript">
      function logEventData(eventData) {
        var eventLogEl = document.getElementById('eventLog');
        eventLogEl.innerText = eventLogEl.innerText + JSON.stringify(eventData) + '\n';
      }

      function startWidget() {
        var iframeEl = document.getElementById('widgetIFrame');
        var hostURL = 'http://localhost:8000';
        iframeEl.contentWindow.postMessage('start', hostURL);

        // set the positioning to fixed to achieve the correct modal look
        iframeEl.style.position = 'fixed';
      }

      // listen to any events emitted by the iframe
      window.addEventListener('message', function(ev) {
        var iframeURL = 'http://localhost:8000';

        // disallow any messages incoming from another origin
        if (ev.origin !== iframeURL) return;

        // we need to parse the event data that was stringified in the iframe
        var eventData = JSON.parse(ev.data);

        // handle embedded widget events here
        switch (eventData.type) {
          case 'veeva-network:Expense:select':
            console.log('Received', eventData.type, 'with data:', eventData.detail, 'outside iframe');
            logEventData(eventData);
            break;
          case 'veeva-network:Expense:close-widget':
            // reset the position of the iframe so that we can interact with the reset of the page again
            var iframeEl = document.getElementById('widgetIFrame');
            iframeEl.style.position = 'relative';
            break;
          default:
            break;
        }
      });
    </script>
  </body>
</html>
Feedback

If the following title isn't the topic you want to provide feedback for, update the topic name in the Topic field.

Provide your feedack for the topic.