Has anyone used on-page s.contextData[] variables successfully with Dynamic Tag Manager?

StackOverflow https://stackoverflow.com/questions/22373076

  •  14-06-2023
  •  | 
  •  

Question

We are migrating a site from Adobe Tag Manager 2 -> Dynamic Tag Manager.

This site has various s.contextData[] variables set within the page, which we are trying to re-utilise for our DTM implementation, however it seems that with DTM the s object is not created until well after the page code has loaded, resulting in an "s is undefined" error.

(In contrast, with TM2, the s object existed well before the bottom part of the HTML file was loaded, so creating s properties caused no problems.)

Has anyone made use of in-page s.contextData with DTM?

Was it helpful?

Solution

To start, I'd like to apologize in advance for the tl;dr. However, I feel all the explanation is necessary so that you can (more) fully understand the issues, since it's not really a simple issue to overcome.

Okay so there's several things working against you, and there's several solutions to it, depending on what you are ultimately looking to achieve.

Issue #1: namespace

The most immediate issue is that DTM doesn't output the s object in the global namespace; it's buried with the _satellite object somewhere.

Now, there are (unofficial) ways to make it put the s object in the global namespace, depending on how you are including the Adobe Analytics core code. For example, in the Adobe Analytics tool config section, in the "general" expandable, if you have it set to "managed" and have it pasted within the code block you open with "open editor", you can instantiate the s object yourself, same as what it looked like in the traditional s_code.js file:

var s = s_gi('rsid here');

This will write the s object to the global namespace. However, there are a side effect with this - it override's DTM's built-in prod/staging rsids that you specify in the "account numbers" section at the top of the config. IMO this is no big loss, as that setting is pretty damn useless to begin with.

For starters, it doesn't account for if your implementation uses Dynamic Account List (DAL). If you were already using the AppMeasurement library instead of legacy code, DAL was removed anyways. But most people are still using some version of the legacy s_code.js (version <= H26.2) and a fair chunk of people make use of DAL.

DTM simply doesn't offer any kind "DAL" mechanism, beyond what gets output when you use the prod vs. stage script include (or override with debugging cookies to point to staging). The good news is that if you are using DAL, you can put it in your general config with your core code and it will work. But again, only with legacy code base. DTM pushes you to use the AppMeasurement code-base even for migration and AppMeasurement doesn't support DAL either.

But also, those "dynamic" account numbers only activate when you physically output the staging vs. production script include (or override prod with dtm debug cookies). In practice, nobody wants to have to physically output 2 separate code tags depending on environment, because this doesn't really work out so well if you're using a versioning system like git or svn. I don't know what DTM people were thinking when they did this but.. whatever. I only include the production script include and then pop the debug cookies based on environment (which is what they should have provided within their interface to begin with.. but again, whatever). Anyways...

Issue #2: script execution order

The Second (and more important/relevant to your issue), even if you make the s object global, there are code execution order vs. timing issues. Even if you set it in the config to "Load Adobe Analytics code at [Page Top]", it's still going to be an issue. The main DTM script include in the header will execute before your on-page s.contextData is executed, however, DTM doesn't output the Adobe Analytics code as code straight in that include. Instead, it outputs it as its own script include. You can see this by going to the developer console in your browser (or firebug if on firefox, or a general packet sniffer like charles proxy). You will see the satelliteLib-[id string] script include and then a separate request for s-code-contents-[id string].

In my attempts to dissect their obfuscated code, near as I can tell, s-code-contents-[id string] is supposed to be output with a document.write, which theoretically means that it should be output before you set s.contextData later on in the page. Also, unless I'm reading it wrong, this chart seems to confirm that this is how it should work.

However, this is not what I see happening in tests I have done. When I output console.log('calling from [location]') in various places, I see that the on-page code is consistently being output before the s object is instantiated.

Honestly, I'm feeling like there's a bug, or at least a misleading of definitions about order execution, but I hesitate to point my finger. But here is what I see happening (consistently) and it doesn't seem to line up with the chart. These are console.log() calls I placed in various places, along with // notes about where the are:

page load - top - sequential html             // this is a page load rule set for top, console log is set with sequential html call
page load - top - sequential js               // this is a page load rule set for top, console log is set with sequential js call. 
hello from on-page                            // this is an on-page js code block between top and bottom satellite code
readyState: loading                           // this is in same code block as above, to show current document.readyState
calling config general                        // this is inside the adobe analytics main config "managed" code block, and the config is set to execute the adobe tag on "page top"
readyState: interactive                       // this is also inside the "managed" code block
page load - bottom analytics                  // this is page rule set for bottom, console log is within adobe analytics tag custom code section in the rule
page load - top - adobe analytics custom code // this is a separate page load rule set to execute on top. console log is also in adobe analytics custom code.

So, I don't know whether or not this is a bug in DTM's code or if I'm misunderstanding definitions for things or what.. but this seems inconsistent to what is shown in the chart in several places (compare to the "DTM Library Loaded" text on the right of the chart link above), and IMO it's inconsistent with my expectations of what "Page Top" should mean. Anyways..

What to do to actually fix your issue

Okay, now that you (hopefully) understand the 2 main issues (namespace and order of execution), you should (hopefully) be able to go from there. There really isn't an officially documented way to deal with this, per se, though the documentation does somewhat imply things here and there. So what the "best" way to is to fix this kinda depends on how the rest of your stuff is setup.

But here is one example of how I have dealt with this. Within the Adobe Analytics config, I have s_doPlugins defined. This can go into the managed code block or the customize code page code block, but you have to instantiate the s object yourself if you put it in the managed (see Issue #1 way at the top). Now, you may be asking yourself why I would define s_doPlugins when I could just make a DTM rule that replicates it. The short answer is that DTM cannot accurately nor feasibly replicate s_doPlugins, and that's sort of a big deal, especially to people who are migrating and already have implementations that rely on that functionality. In addition to that, there is a bug with DTM wizard overwriting and/or not properly setting Adobe Analytics variables within custom code section of rules, but that's a whole other topic of conversation(this has been fixed). So long story short, I define s_doPlugins within the config to get around DTM's limitations/bugs.

So, with s_doPlugins, I have the following code:

s.usePlugins=true
function s_doPlugins(s) {
  if (typeof window['dtm_s']=='object') {
    for (var v in window['dtm_s']) {
      if (window['dtm_s'].hasOwnProperty(v)) {
        s[v] = window['dtm_s'][v];
      }
    }
  }
}
s.doPlugins = s_doPlugins;

Then, for my on-page custom code, I change it to this (example):

<script type="text/javascript">
var dtm_s = {
  'pageName' : 'some page',
  'prop1' : 'some value',
  'contextData' : {
    'foo' : 'bar',
    'another' : 'context var'
  }
};
</script>

So what all this does is..

1) now I have a place to put on-page code like the traditional omniture code. If you want to make it even easier to migrate, don't put s in the global namespace in DTM (define s_doPlugins within the custom section instead). Then somewhere before your on-page code, make a dummy s and s.contextData object:

var s = {'contextData':{}};

And update the s_doPlugin loop to look for window['s'] instead of 'dtm_s' and then you can leave your on-page custom code as-is (minus the s_code.js script include and s.t trigger, of course). I usually do it with dtm_s namespace though because frankly, s namespace is too short and several times i have run into namespace conflict issues because of it, so IMO it's worth the effort to change it.

2) since s_doPlugins is now defined, this will get executed on every s.t and s.tl call, same as it traditionally does. What the code itself does is look for the dtm_s payload and pop Adobe Analyics vars. The nice thing about this is that s_doPlugins gets called after DTM's logic that pops/overwrites stuff from it's "form wizard", so this gets around those "wizard" bugs (a diff topic of conversation).

Note: One thing that can be improved upon this (and I do indeed do it this way, but I'm keeping it simple for example sake) is to utilize this principle to populate DTM Data Elements instead, and then route it through to the Adobe Analytics code. That way the data is exposed to DTM to be used for other tags and rules as well, not just Adobe Analytics.

OTHER TIPS

As a workaround, have you tried putting the codes you need globally in the box for [Javascript / Third Party Tags] as Javascript and have the checkbox [Execute Globally] checked. Also, you might want to load the codes at the Top of the Page. Both my codes in the [Adobe Analytics] and [Javascript / Third Party Tags] boxes are able to thereafter access the 'global' codes. It works for me!

I know this is a bit of an old question, but I ran across this issue when trying to determine how to fire some custom s.tl() calls from Non-Sequential JavaScript in a page load rule. This is for a new implementation that utilizes AppMeasurement and is completely Marketing Cloud managed code.

This isn't a fully tested solution, but seems to be working at present for our use.

  1. Create a new Data Element. I named it dtm_s_object. I also set a default to our development report suite, just to be safe.
  2. In the element, I put the following code:

    var adobe_obj = _satellite.tools["INSERT YOUR ADOBE TOOL ID"];
    var adobe_rsid = adobe_obj.settings.account;
    return s_gi(adobe_rsid);
    
  3. Then, in your rule, you can set a local variable to use within the code as follows:

    var s = _satellite.getVar('dtm_s_object');
    s.tl(true, 'o', 'foo');
    

To get your Adobe Analytics Tool ID, you'll just need to type: _satellite.tools in the console and hit enter. Then you can browse through to determine which is Adobe Analytics.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top