Working with FXM, I faced a very interesting sporadic issue: every 10-15 times some of the components were not loading on a page. A response object was identical all the time, with no errors in the console, no changes in a site code since the last deployments. I think you get the situation.

However, this issue was able to cause a lot of trouble for a client as FXM was used on many legacy sites. So I continued digging…

Research

As usual, I’ve tried to get into the details of an actions workflow and find potential weak points:

  • A page from an external site loads
  • An FXM beacon script, referenced in a head tag of the page, gets parsed by a browser and loads a SCBeacon object, mentioned at the end of this page.
  • As a part of a constructor of the SCBeacon object, an AJAX request takes place and loads HTML snippets from Sitecore.
  • Once the AJAX request is succeeded, a visitSuccess function executes.
  • During the execution of it looks for HTML elements using jQuery like selectors and replace (or put something before/after) them with an HTML, got from Sitecore.

A response object is always the same I’ve started looking for reasons why elements might not be generated in DOM.

To do that I’ve replaced a beaconApi.js file, that you could find in /Sitecore/shell/client/Services/assets/libs, with an un-minified version and put a lot of breakpoints.

What was discovered

During an investigation, I’ve found out that some selectors were not able to return an element object.

1
var elements = Sizzle(match.Selector, document);

In addition to that, an element was not able to be detected by a document.getElementById(), which is a clear sing DOM being not loaded by the moment of execution of this function.

Resolution

We could not move the FXM script to the end of the body, as inline and analytics won’t be available (SCBeacon would be undefined till the end of a page processing). Also, we could not delay the beacon initialization, as it might increase component load time and affect UX.

So the solution is to embed waiting for DOM to be loaded after you got the response from the AJAX request to Sitecore.

var processResponse = function(resp){
  ...
  events.dispatch('ready');
  status = 'ready';
}

var visitSuccess = function(resp) {
  var readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete"
      || document.readyState === "interactive" ) {
      clearInterval(readyStateCheckInterval);
      processResponse(resp);
    }
  }, 10);
}

internalService.trackPageVisit().then(visitSuccess).fail(dispatchError);

What you need to notice is that you could not use DOM loaded event, it might be fired earlier, as well as wait for only a complete event, as during an **interactive **state DOM is ready to be used.

Sitecore ticket: 465566

Additional thoughts

From my perspective, injecting HTML snippets and tracking analytics events are separate functions, which ideally should be handled by separate scripts:

  • bundle/analytics - loaded in an HTML head and available for inline scripting.
  • bundle/FXM - an injector script, loaded at the end of DOM, before custom libraries, so it could work with anything already rendered and processed by a browser on a page.

As usual: Share if you like the post. Follow me on Twitter @true_shoorik