Improving the Embedded Widget User Experience with Custom Events

This page provides guidance for embedding Opower widgets for multi-premise, multi-operating company (multi-OpCo), and single-page application (SPA) scenarios. It is intended for implementers, web developers, and utility administrators who integrate the widgets and need to manage customer context, authentication, and navigation events.

Embedded Opower widgets emit events at key moments, including when a user selects a widget link, when authorization is needed, or when the widget is ready to start. You can configure event listeners in your portal code to handle these events, improving the embedded experience in the following situations:

  • Single-page applications that must avoid full-page reloads
  • Pages with multiple widgets that link together across tabs or accordions
  • Sessions where customers switch between multiple premises or OpCos
  • When you need to optimize performance by loading widgets only when necessary

Use this guidance to integrate customer account data, set up performance features, and maintain a consistent user experience across embedded widgets.

Handling Widget Navigation and Preventing Page Refreshes

Applies to: Single-page applications and multi-widget pages. Not required for implementations that allow full-page navigation for all widget links.

To keep the user on the same page and avoid full-page reloads, intercept and manage widget navigation events. This enables in-page routing and helps maintain context across widgets.

Note:

Each opower:link event includes a specific experienceName value. The experienceName value is included in the opower:link event payload. You can use it to route the customer to the appropriate page or correct location within a page. The experienceName values configured for your embedded widgets will be provided to you by your Contact Your Delivery Team.

Handling Widget-to-Widget Navigation

When a user selects a link inside an embedded widget, the widget triggers an opower:link event. By default, this event causes the browser to navigate to the target location, resulting in a full-page reload. If the destination content already exists on the current page (for example, in another tab or accordion), you can prevent unnecessary full-page reloads by intercepting opower:link, calling event.preventDefault(), and routing within the existing page (for example, switching tabs or opening an accordion).

Example JavaScript for Routing within a Page:

window.addEventListener('opower:link', function(event) { 
 if (event.detail.experienceName === 'energyUseDetails') { 
   event.preventDefault(); 
   $.hide(event.target); 
   $.show('#energyUseDetails'); 
  } 
});

Note:

Add animation or smooth scrolling logic when redirecting to minimize user confusion. This best practice helps maintain the user's orientation during navigation.

Example Event Payload

When a user selects a link in a widget, the opower:link event is triggered. The event includes details about the intended navigation action, including the widget that initiated the link.

{
widgetName: 'widget-data-browser',
experienceName: 'energyUseDetails',
url: {
    protocol: 'https:',
    slashes: true,
    auth: null,
    host: 'util.opower.com',
    port: null,
    hostname: 'util.opower.com',
    hash: null,
    search: '?fixtures=true',
    pathname: '/ei/x/energy-use-details',
    path: '/ei/x/energy-use-details?fixtures=true',
    href: 'https://util.opower.com/ei/x/energy-use-details?fixtures=true',},
    target: '_self'
}

Setting or Switching Customer Context

Setting Customer Context at Startup

Applies to: Multi-premise, single-page applications, and multi-OpCo scenarios. May also apply to OpenID Connect and other authentication models that provide account context data.

To ensure embedded widgets load the correct customer context—such as for customers with multiple premises or customers switching accounts across multiple operating companies (OpCos)—listen for and handle the opower:start event. When opower:start fires, the event payload includes an Opower JavaScript API object (opowerApi) for managing customer context. Your portal must:

  1. Capture the API object for future use (for example, account switching without page reload).
  2. Call opowerApi.setEntityIds(...) to set the initial customer context.
  3. Call opowerApi.start() to load widget content.

Note:

The identifiers you pass to opowerApi.setEntityIds(...) must exist in the identity data returned by your authentication integration (for example, an OpenID Connect UserInfo response). See the Oracle Utilities Opower SSO Configuration Guide for guidance, which describes creating a UserInfo endpoint.
Providing a Single Customer or Account ID (Multi‑Premise)

Applies to: Multi-premise and single-page application scenarios (recommended). Not required for single-account customers unless your portal still needs to set context explicitly.

You can provide the customer ID to further identify the customer interacting with an embedded widget. You can accomplish this by listening for and handling the opower:start event. Providing the customer ID ensures that customers with multiple premises are given the correct experience.

The setEntityIds method accepts an array of exactly one value:

window.addEventListener('opower:start', function(event) { 
    window.opowerApi = event.detail;
    window.opowerApi.setEntityIds(['ABC-123']);
    window.opowerApi.start(); 
});
Providing a Customer or Account ID with a Utility Code (Multi‑OpCo)

Applies to: Multi-OpCo and single-page application scenarios (recommended). Not required for single-operating company portals.

For multi-OpCo parent portals, you must set context using an object that includes both the account identifier and the utility or OpCo utility code. A utility code is set up as part of the program launch process. Contact Your Delivery Team if you are unsure of your code. The setEntityIds method still accepts an array of exactly one value, but the value is an object.

{ accountId: 'ABC-123', clientCode: 'UTIL' } 

window.addEventListener('opower:start', function (event) { 
  window.opowerApi = event.detail; 
  window.opowerApi.setEntityIds([{ 
    accountId: 'ABC-123', 
    clientCode: 'UTIL' 
  }]); 
  window.opowerApi.start(); 
}); 
Handling Asynchronous Startup for Customer Context Lookup

Applies to: Single-page applications, multi-premise scenarios, and multi-OpCo portals. Not required for implementations that can set context synchronously.

If additional logic is required to determine the customer context, use an asynchronous handler and call the event.preventDefault() function to prevent synchronous execution.

window.addEventListener('opower:start', function(event) { 
  window.opowerApi = event.detail;
  event.preventDefault(); // Prevent synchronous start of widget.
  fetchOpowerConfiguration(function(error, configuration){ // fetchOpowerConfiguration is an example utility defined function
      if (error) {
      //  Handle your own error. Widget content has not loaded.
      return;
      } 
      window.opowerApi.setEntityIds(configuration.entityIds);
      window.opowerApi.start();
      }
    }); 
});
Specifying Script Loading Order

Applies to: Single-page applications, multi-widget pages, multi-premise scenarios, multi-OpCo portals, and OpenID Connect integrations. In practice, this applies to any implementation that uses the opower:start handler.

Define the opower:start event handler in the correct order relative to the core.js script tag, which is the main script for our core library.

It is recommended that you place the core.js script tag in the <head> and the opower:start event handler in the <body>. The core.js must contain the defer attribute to ensure that core widget library is loaded and executed in the proper order. See the HTM5 specification for more information about the defer attribute.

Alternatively, place the event handler and the core.js script tag in the <head>. However, in that case the event handler must be defined before the core.js script tag.

Switching Accounts after Page Load without Refresh

Applies to: Single-page applications and multi-premise scenarios. Not required for static sites that reload the page on account switch.

In multi-premise and multi-OpCo scenarios, customers may switch accounts during the same session. In single-page applications, account switching should happen without a page reload. After your portal completes its own account-switching logic (session updates, permission validation, navigation updates), update Opower widgets by calling window.opowerApi.setEntityIds(...).

function switchToAccount(newCustomerId) { 
    // Execute utility-specific account switching logic first 
    // (for example, update session, validate permissions, etc.).
    if (window.opowerApi) { 
        window.opowerApi.setEntityIds(entityIDsArray); 
        // Note: opower:start will not fire again during this session. 
    } 
} 

Implementation Note: The setEntityIds method accepts an array of exactly one value:

Single-OpCo: ['ABC-123']

Switching Accounts for Multi-OpCo Portals

Applies to: Multi-OpCo portals and single-page applications (recommended). Not required for single-OpCo portals.

For multi-OpCo portals, the best method for switching accounts depends on the utility website implementation. Utilities may use the Billing Account Selector widget provided that your widgets are embedded in a single page and the area would not be affected by the site’s billing account selector. The Billing Account Selector enables reliable account switching, supports browser redirects, and provides tight integration with other Opower widgets (including automatic customer data re-fetching upon account selection).

Utilities can also use their custom account selector and call window.opowerApi.setEntityIds() for multi-OpCo switching, similar to the Providing a Single Customer or Account ID (Multi‑Premise). However, a page refresh may be required depending on the implementation.

Implementation Note: The setEntityIds method accepts an array of exactly one value:

Multi-OpCo: [{ accountId: 'ABC-123', clientCode: 'UTIL' }]

Supporting Cross-OpCo Authorization with OIDC UserInfo (Example Structure)

Applies to: OpenID Connect integrations and multi-OpCo portals. Not required for non-OpenID Connect authentication models or single-OpCo portals.

For utilities operating multiple subsidiaries or OpCos under a single parent site, the OpenID Connect UserInfo response should be structured to return all accounts that the customer is authorized to access, each tagged with its associated operating company.

{ 
   "sub": "456aae91-90bf-4ef4-9e1b-cc46e38a265a", 
   "user_accounts": [ 
      { "id": "965203814700-0031002578", "opco": "UTIL" }, 
      { "id": "203184955371-4067812994", "opco": "epeo" }, 
      { "id": "710298357401-9840361257", "opco": "tanc" } 
   ] 
}
  • The sub field represents the unique identifier for the user.
  • The user_accounts array lists all accounts available to the user, with each entry including an account id and an opco code for the operating company.

Providing Access Tokens for OpenID Connect

Responding to Authorization Challenges

Applies to: OpenID Connect integrations. Not required for pre-authenticated experiences (do not include this listener).

When authenticating using OpenID Connect, provide an access token by listening for and handling the opower:unauthorized event. Oracle uses the access token to issue a GET request to the UserInfo endpoint and retrieve the user's account details.

Embedded widgets require access tokens when they are initially loaded on the page, as well as when the widget requires the access token again but the token has expired.

// Synchronous handler example
window.addEventListener('opower:unauthorized', function(event) {
  var authorize = event.detail.authorize;
  var authorization = {
   accessToken: 'ABC123XYZ' // The access token string as issued by the authorization server
  }
  authorize(null, authorization); // Do not call if user is logged out
}

For asynchronous handlers:

// Asynchronous handler example
window.addEventListener('opower:unauthorized', function(event) {
  var authorize = event.detail.authorize;
  event.preventDefault(); // to instruct Opower authorization logic to wait for async callback
  fetchOpowerAuthorization(function(error, authorization) { // fetchOpowerAuthorization is an example utility defined function
    authorize(error, authorization);
  }
}

Specifying Script Loading Order (OpenID Connect)

Applies to: OpenID Connect integrations. Not required for pre-authenticated experiences (where the listener is excluded).

Place the opower:start in the correct order relative to the core.js script tag, which is the main script for our core library. For more information, see Specifying Script Loading Order above.

Optimizing Widget Load Performance

Delaying Widget Initialization

Applies to: Multi-widget pages and single-page applications. Not required for single-widget pages where immediate widget load is acceptable.

By default, embedded widget content begins downloading and executing immediately. If widgets are not the primary focus of the page, you can improve perceived page load performance by delaying widget initialization until the customer opens the tab or accordion containing the widget.

function addInitializeTabListener(tabId, tabContentId, html) {
  var tab = document.getElementById(tabId);
  var tabContent = document.getElementById(tabContentId);
  tab.addEventListener('click',
    function() {
      tabContent.innerHTML = html
    },
    { once: true }
  )
}

addInitializeTabListener(
  'widget-data-browser-tab', 
  'widget-data-browser-content', 
  '<h3>Data Browser</h3><opower-widget-data-browser></opower-widget-data-browser>'
);

<div id="widget-data-browser-tab">
  <div id="widget-data-browser-content">
    <!-- On click of the tab, the Data Browser widget is inserted here -->
  </div>
</div>

To extend this pattern to a second widget (for example, Neighbor Comparison):

addInitializeTabListener(
  'widget-neighbor-comparison-tab',
  'widget-neighbor-comparison-content', 
  '<opower-widget-neighbor-comparison></opower-widget-neighbor-comparison>'
);