Single-Page Application Implementation (Client Side)

Single-page applications (SPA) are becoming increasingly popular in web development. Popular SPA frameworks include React, Vue, AngularJS, Ember, and Backbone. If your website uses one of these frameworks, read this article for the recommended implementation method.

How it works

With this solution, the implementation for SPAs is identical to non-SPAs. There's no need to implement an API call for SPA page load or mark which campaigns are relevant for these calls. Moreover, the solution is seamless to the framework.

The heavy lifting is done by Dynamic Yield:

  • Loading of page elements is monitored, even when the page itself does not fully reload. This is done using a Web API called MutationObserver, which provides hooks into DOM mutations and enables us to track when a DOM node is inserted, destroyed or modified. With that, Dynamic Yield applies (or reapplies) changes at the right moment. When the page deactivates, Dynamic Yield no longer attempts to change the elements on that page.
  • The Dynamic Yield script has a state machine, and all of the campaigns are stateful smart objects. This way, the script responds to changes in your applications.

Known limitations

  • A page refresh is required to assign users to CRM-based audiences.
  • Setting the page context is mandatory.

🚧

If auto-detect rules (context rules) are enabled for a specific page type, the page context implemented on this page type serves as a fallback if all rules don't apply.

New-page detection

The SPA solution uses multiple methods to detect changes to the page:

  • URL changes are detected using the History API and Hashchange events.
    URL change detection ignores changes to URL query string parameters.
  • Changes in the Title tag in the page Head, or edits to Document.Title.
  • Context changes are detected by edits to the DY.recommendationContext object.

Campaign rendering

Dynamic content and recommendation campaigns are rendered directly into the DOM.

Sections that don't inherit site CSS

❗️

We recommend enabling Inherit site CSS and removing any redundant CSS from your Dynamic Yield campaigns. To do so, contact your Customer Success or Technical Account Manager.

If your section doesn't inherit site CSS, dynamic content and recommendation campaigns are rendered directly into the shadow DOM. As a result, these campaigns don't interfere with the DOM change detection of the front-end framework.

To enable seamless access to the campaigns' HTML elements, the campaign JavaScript code has multiple querying functions overridden, first querying the campaigns' shadow DOMs. If no elements are found, it queries the DOM as a fallback.

These functions are:

  • document.getElementById
  • document.querySelector
  • document.querySelectorAll
  • document.getElementsByName
  • document.getElementsByClassName
  • document.getElementsByTagName

Troubleshooting shadow DOM usage

What is #shadow-root, and why is it visible in all of my Dynamic Yield campaigns running on the page?

shadow-root (or shadow DOM) is how Dynamic Yield injects content into SPA-enabled sites, and should be visible within any campaigns running on the page that are set to replace/add before/add after. This is considered normal behavior for sites with SPA enabled.

Why does the CSS in my campaign not affect the elements on my web page?

Relevant to sites without the INHERIT_SITE_CSS feature flag enabled.

This behavior is also due to the shadow DOM. Elements (and style tags) within the shadow DOM are in a separate DOM instance from the rest of the web page.

If you're trying to manipulate objects running inside the shadow DOM from outside, or vice versa, use JavaScript instead of CSS.

Targeting the DOM from the shadow DOM (same syntax as no shadow DOM):

document.querySelector('#someElement').style.color = 'red';

Targeting the shadow DOM from the DOM:

document.querySelector('div[data-dy-embedded-object]').shadowRoot.querySelector('#someElement').style.color = 'red'

Targeting the shadow DOM from a separate shadow DOM:

// same as "Targeting Shadow DOM from DOM", but may want to add a waitForElement for timing purposes

DYO.waitForElement('#shadow-root-container div[data-dy-embedded-object]', function() {

   document.querySelector('#shadow-root-container div[data-dy-embedded-object]').shadowRoot.querySelector('#someElement').style.color = 'purple'

 })

📌

jQuery can be used to update the styling or toggle classes of elements running within the shadow DOM, using these same methods but with $() syntax instead of a querySelector.