SPAs, DTM, and clearVars

For some reason, I see a lot of SPAs right now, some of them actually embedded into otherwise harmless and perfectly likable web sites. I guess they’re not going to go away anytime soon. This article is part of my “don’t fight it” exercise.

It is a follow-up to the article on tracking single-page applications. I want to add two important things:

  1. a brief discussion on EBRs and hashes, and
  2. a workaround for the fact that the s object is only instantiated once and we need to be careful with s.t() calls and what data they send.

EBRs & Hashes

Back in the days, SPAs had this ugly habit of breaking the browser navigation (back button would exit the SPA rather than going back “one page”), until someone decided that the hash part of the URL could be used to simulate pages.

So these days, most SPAs do change the URL when they render a new “page”.

In an SPA that displays books, I could move from the overview (on http://my.book.site/books.html#/overview) to one of the books (e.g. http://my.book.site/books.html#/john+m+harrison/light), or maybe to a list of some books (on http://my.book.site/books.html#/authors/h).

This is great news for your friendly marketer, and for you, too!

What it means is that we no longer rely on you — the developer — to call DCRs at every turn. Instead, we can use EBRs along with the “pushState or hashchange” event type in DTM.

[Screenshot]
DTM event types – hashchange
In the Analytics part of that rule, we select “s.t(); – increments a pageview”, and we specifiy a page name.

[Screenshot]
EBR – s.t and pageName
The “Page Name” Data Element can potentially be really easy, and just read window.location.hash, but that would be cheating, wouldn’t it?!

That EBR completely covers normal tracking, which is why I usually call it “Quasi-normal Page Load”.

Everybody happy. Except…

The clearVars Conundrum

When we are dealing with an SPA, conceptually, there will be one single Page Load Rule, which runs when the SPA is loaded. That happens when someone lands on it for the first time, or when they reload. The normal flow within the SPA does not reload any pages, though.

As a result, we get a tracking object (“s object”) that never dies. Or rather, unlike the usual “page scope”, where a loading page creates the s object, and the next page creates a new one, in an SPA, the lifetime of the s object is unnaturally long. It is immortal, so to speak.

Now that is an issue.

Why?

A long time back, I used to write on this blog about “variables”, and I always put that in quotes, because you really shouldn’t see something like s.eVar47 as a variable if you’re a developer. I used to call it a container.

But then I spent a day with my kids in an amusement park last month, and now I have a better analogy:

We went on a roller coaster. There was a (nicely short) queue, which at the end split up into individual queues with little barriers. When a roller coaster train arrived, the barriers would let two people pass and sit on the train, then close again. The train would then head off.

The s object is like those individual queues at the end with the little barriers. You put people (values) into each little queue (eVarXYZ, propXY, …) as needed, then the train arrives, takes them and leaves (s.t() call).

The big difference is that s.t() copies the people, so they are still at the head of the queue once the train has left.

Uh-oh.

That means that the next train will grab the same people, plus those that have arrived at formerly empty little queues in the meantime.

Think about custom tracking on some visitor action. You likely want to track some event and maybe an eVar, but I’m pretty sure you’re not interested in all the other stuff that currently sits in the s object.

And that’s ok, that’s why we have s.linkTrackVars and s.linkTrackEvents. Works as designed, I’d say.

But for an s.t() call, which sends the lot, we usually except the s object to be shiny and new, which in an SPA, it isn’t. Nobody originally thought we would reuse the object, but that’s what SPAs do.

The answer is a little-known utility method, that is part of the AppMeasurement core code: s.clearVars().

The s.clearVars() method deletes the values for most of the “variables”, or it takes all those people standing by the mini-gates and tells them to leave. Nicely.

We can use it with an SPA like so:

  1. visitor moves to “new page”
  2. call s.clearVars()
  3. track the “new page”

Or like so, pre-emptively, so to speak:

  • track “new page”
  • call s.clearVars()

Whichever way, only by calling s.clearVars() can we be sure our tracking doesn’t have “old values”.

Order

The first time I implemented this, I had to do it the first way, i.e. call s.clearVars() before tracking, which raised an interesting question: how can I make sure those two things happen in that exact order?

The answer to that involves some Javascript and a DCR, of course.

In my SPA, I had an EBR that listened to “pushstate or hashchange”, then tracked a bunch of “variables” and some events.

When I realised that all “variables” and events from the previous “page” were also on the tracking call, I did the following:

  1. created a new DCR
  2. moved all tracking from the “Analytics” section of the EBR over to the DCR (“moved” as in: created the same things in the DCR, then deleted them in the EBR)
  3. created a 3rd-party Javascript script in the EBR
  4. in that script, called s.clearVars(), then the DCR via _satellite.track('')

As a result, the EBR (which is still being called) doesn’t actually track anything. But it cleans all the “variables”, and then calls a DCR which does. A bit like a manager who takes a request, then passes it on to a worker.

Easy peasy, isn’t it?

5 thoughts on “SPAs, DTM, and clearVars

  1. Yes, this is a huge problem for lots of folks- thanks for the insight! I tend to run s.clearVars in the Custom Code Condition block in the EBR, which is guaranteed to run before any of the rule logic- something like “s.clearVars(); return true” as a condition. The only potential problem is that that won’t work if your s object isn’t globally scoped.

    Like

    1. I came across yet another way today:
      Make EBRs for triggers, which then call DCRs. One of those DCRs does nothing but call s.clearVars().

      I guess that helps separate logic and implementation detail.

      Like

  2. Hi, This is indeed a headache and i would classified as a flaw of DTM. I Don’t know why they don’t allow an option to tell the rule to base itself to what it will see in the template. I mean you are using a TMS so why is this missing or making users life more difficult?

    Anyway, on my side i like using the EBR for the hash change pages and either use a condition (which is not possible in DCR) OR i wrote a function (included on my global page load rule) which i call in each EBR or DCR custom js (i.e. in the analytics editor) and tell it to clear all vars that is NOT in that specific rule.
    I have also noticed that any custom js you include in any type of rules do not have access to the s object unless you use the JS editor in the analytics section of the rule.

    Let me know if you want me to share the function.

    Like

  3. Pingback: 2016 for Developers | Web Analytics for Developers

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s