Single-page applications (SPA) are becoming more and more popular in web development. Popular SPA frameworks include React, Vue, AngularJS, Ember, and Backbone. If your website is using one of these frameworks, read this article for the recommended implementation method.
Guidelines
With this solution, the implementation for single-page applications is identical to non-single-page applications. There is 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.
How it works
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.
- Our elements are added through the Shadow DOM, in a way that doesn’t interfere with your application lifecycle.
Diagram of the current guidelines
Known limitations
- The campaign experience CSS is not inherited from the site CSS, and therefore must be added explicitly in the variation code.
- A page refresh is required in order to assign users to CRM-based audiences.
- Setting the page context is mandatory.
Note: If auto-detect rules are enabled for a certain page type, there is no need to implement the page context on this page type as it won't be taken into account.
Page-change 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. - Detection of the DY.recommendationContext object, which Dynamic Yield treats as a new page.
Note that no SPA event is sent unless the change in page URL also triggers a change in the page context.
Campaign rendering
Dynamic content and recommendation campaigns are rendered into the Shadow DOM instead of the DOM directly. As a result, these campaigns do not interfere with the DOM change detection of the front-end framework.
To enable seamless access to HTML elements of these campaigns, the JavaScript code of the campaigns has multiple querying functions overridden, first querying the Shadow DOMs of the campaigns. 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 and FAQs
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 should be considered normal behavior for sites with SPA enabled.
Disabling the Serve on every SPA Event has no effect on the shadow-root itself. Shadow roots are implemented from your site when the SPA_FLOW feature flag is enabled.
Why is the CSS in my campaign not affecting the elements on my web page?
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 are trying to manipulate objects running inside the Shadow DOM from outside, or vice versa, use JavaScript instead of CSS.
Targeting DOM from Shadow DOM (same syntax as no shadow DOM):
document.querySelector('#someElement').style.color = 'red';
Targeting Shadow DOM from DOM:
document.querySelector('div[data-dy-embedded-object]').shadowRoot.querySelector('#someElement').style.color = 'red'
Targeting Shadow DOM from 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'
})
Note: jQuery can be used to update the styling, or toggle classes of elements running within the shadow DOM, using the same methods above but using $() syntax instead of a querySelector
Why are my external libraries not reflected in the campaign?
Currently, our external script feature adds external libraries to the header tag, and so, the external code is not part of the shadow DOM and therefore does not affect the internal campaign code.
You can either paste the code internally or programmatically import the code using the library URL.
How can I verify that an SPA event was fired?
If you suspect your SPA events are not firing, or want to confirm they are working as expected, open your DevTools (or Web Developer Tool of choice), go to the Network tab, and filtering by "json?".
The network request is sent to the endpoint st.dynamicyield.com/spa/json.