I was implementing a new plugin for a client the other day and naturally I went through the same process I go through for every time I do this:
- Check Adobe docs
- Get frustrated
- Google “adobe analytics doplugins”
- Read 3–6 blog posts
- Stress eat
- Do something I know is imperfect and hope for the best
- Test and see it works well enough
- Try to explain to the client why it took so long
This is obviously not ideal.
What I’m really looking for is a deconstruction of doPlugins
that picks up where Jan Exner left off: if you’re not familiar with doPlugins
, read this first, then come back to me.
Ok, now that we know how doPlugins
works, what is the actual best way to implement this handy but confusing feature?
Just use the Common Analytics Plugins extension!
Okay okay, I know, but that doesn’t really solve the whole problem: if you want a variable to be set whenever a beacon is fired, you have to have some JavaScript code in doPlugins
.
When you install the Common Analytics Plugins extension, you set up a rule that’s triggered on Library Loaded
to install the plugins you want to use. This keeps the core Analytics extension relatively small and performant, and it allows you to easily upgrade outdated plugin code (no more using version 1 of getPreviousValue
when the latest is version 3). However, you still have to call getPreviousValue()
somewhere, and if you want it for every beacon, the best place to call it is in doPlugins
.
Assigning the doPlugins function to the tracker object
Let’s talk style for a minute. There are many ways to declare the doPlugins
function. As of the time this post was published, Adobe docs recommend the following:
s.usePlugins = true;
s.doPlugins = function(){
/* code goes here */
}
As may already know, when you assign a function as a property of an object, it has a special name: a method. So now we can call it the s.doPlugins method. (technically function is still correct; a method is always a function, but a function is not always a method)
Some other ways I’ve seen:
Passing s
as an argument to the function
// You don't need to pass any arguments to the function!
s.doPlugins = function(s){
/* code goes here */
}
I’m not sure why folks do this but it’s not necessary to include an argument here. Even if you haven’t made the tracker object globally accessible, the extension still makes the tracker object available (via a shared module) when doPlugins
runs (that’s how you’re able to execute s.doPlugins()
after all!). Any code you’ve written outside the function block is available inside the function as well, because it’s part of the closure. Passing s
as an argument doesn’t really do anything except make me sad.
Separate named function statement, then assigning s.doPlugins to that function
// Not wrong but a little more verbose
function s_doPlugins(){
/* doPlugins code here */
}
s.doPlugins = s_doPlugins;
Why have a function called s_doPlugins
that gets assigned to s.doPlugins
? That seems confusing. This seems like a stylistic choice, but to me it’s much cleaner to go with Adobe’s recommended approach.
Using an IIFE
Another (weird) thing I’ve seen—wrapping doPlugins
in an IIFE (immediately-invoked function expression, pronounced “iffy”):
// Just ... no
s.doPlugins = (function(s){
/* plugin code */
})(s);
There are a few reasons why you’d want to use an IIFE in JavaScript:
- You want to execute a function as soon as it’s declared
- You want to add encapsulation to some code so other parts of your code don’t collide with it
- You want to leverage
return
as a means to stop execution - You want to show off for your friends
Whatever your intentions, there’s no reason to wrap doPlugins
in an IIFE:
doPlugins
runs when the AppMeasurement library tells it to, so you don’t need extra instructions for it to execute right away—it’s only ever going to execute when AppMeasurement tells it to, IIFE or not. In fact, using an IIFE could cause things to break.- You don’t need to further encapsulate your
doPlugins
code. It’s already being executed as a shared module in Launch, so the scope is restricted to that context. - For the most part, Launch (and the JS runtime in the browser) takes care of when
doPlugins
is done executing.
If you feel you have a compelling reason to use an IIFE here, for the love of Pete, please document it—don’t leave it up to the next engineer to figure out. (Good documentation is just polite.)
So where to put actual plugin code?
Now that we’ve determined the best way to declare doPlugins
, you’ve got two choices for including actual plugin code that you’ll use in your implementation:
- Use the Common Analytics Plugins extension
- Copy and paste from Adobe docs page into the Custom Tracking Code section of the Analytics extension configuration
Unless you have a really, really good reason, go with the first choice.
If you do you have a really, really good reason (e.g. “I inherited this implementation which was built before the Common Analytics Plugins extension was made available”, or “I want to manage the versions of each plugin myself because I don’t have enough to do already and I’m insane”), then you have to include it in the custom tracking code.
If you must do this, have some respect for whoever comes after you, and include this at the bottom of the tracking code, underneath the declaration for doPlugins
. Why? It makes the code cleaner. The JavaScript language has a nice feature called hoisting which is at first confusing and perhaps counterintuitive, but once you’ve written a huge swath of spaghetti code and come back to it months or years later trying to remember what the hell it does so you can explain it to someone else, you start to understand how using it to make your code more readable is super helpful.
Take these two blocks of code:
function myFunction(){
/* function code */
}
function anotherFunction(){
/* function code */
}
myFunction();
var a = anotherFunction();
myFunction();
var a = anotherFunction();
function myFunction(){
/* function code */
}
function anotherFunction(){
/* function code */
}
These two blocks are functionally identical, but which do you think is easier to read? In the first one, we have to sift through all of our function code before we can get to the meat of whatever we’re trying to do. In the second, we see immediately what our code is doing, and we’ve abstracted some of the messy parts down below where it’s accessible but not in the way.
Small tweaks
When you go to install a plugin manually (i.e. by adding it to the custom tracking code section of the Analytics extension), Adobe docs give you a snippet of code that looks something like this:
/******************************************* BEGIN CODE TO DEPLOY *******************************************/
/* Adobe Consulting Plugin: apl (appendToList) v4.0 */
function apl(lv,va,d1,d2,cc){var b=lv,d=va,e=d1,c=d2,g=cc;if("-v"===b)return{plugin:"apl",version:"4.0"};var h=function(){if("undefined"!==typeof window.s_c_il)for(var k=0,b;k<window.s_c_il.length;k++)if(b=window.s_c_il[k],b._c&&"s_c"===b._c)return b}();"undefined"!==typeof h&&(h.contextData.apl="4.0");window.inList=window.inList||function(b,d,c,e){if("string"!==typeof d)return!1;if("string"===typeof b)b=b.split(c||",");else if("object"!==typeof b)return!1;c=0;for(a=b.length;c<a;c++)if(1==e&&d===b[c]||d.toLowerCase()===b[c].toLowerCase())return!0;return!1};if(!b||"string"===typeof b){if("string"!==typeof d||""===d)return b;e=e||",";c=c||e;1==c&&(c=e,g||(g=1));2==c&&1!=g&&(c=e);d=d.split(",");h=d.length;for(var f=0;f<h;f++)window.inList(b,d[f],e,g)||(b=b?b+c+d[f]:d[f])}return b};
/******************************************** END CODE TO DEPLOY ********************************************/
Still with me? Great. Don’t worry about trying to understand how it works unless you’ve got an extra hour or two to kill — just know that when apl()
is executed, it appends a value to an existing list of values stored in a string.
I know to paste this into the Analytics extension custom tracker code section … but how do I do it in such a way that it looks and works well?
Here’s what my custom tracker code might look like:
// 1. some perfunctory lines
s.usePlugins = true; // usePlugins is set to `false` by default. you have to set this to `true` or doPlugins() won't do anything
s.doPlugins = function(){ // this is Adobe's recommended way to declare a `doPlugins` function
// 2. use apl to include event1 for each pageview beacon
s.events = s.apl(s.events, 'event1');
}
// 3. plugin code goes down here
// NOTE: If you're using the Common Analytics Plugins extension, you'd load the plugin on Library Loaded
// and not include the plugin code here
// ⬇ copied directly from Adobe docs on 8 October 2021 ⬇
/******************************************* BEGIN CODE TO DEPLOY *******************************************/
/* Adobe Consulting Plugin: apl (appendToList) v4.0 */
function apl(lv,va,d1,d2,cc){var b=lv,d=va,e=d1,c=d2,g=cc;if("-v"===b)return{plugin:"apl",version:"4.0"};var h=function(){if("undefined"!==typeof window.s_c_il)for(var k=0,b;k<window.s_c_il.length;k++)if(b=window.s_c_il[k],b._c&&"s_c"===b._c)return b}();"undefined"!==typeof h&&(h.contextData.apl="4.0");window.inList=window.inList||function(b,d,c,e){if("string"!==typeof d)return!1;if("string"===typeof b)b=b.split(c||",");else if("object"!==typeof b)return!1;c=0;for(a=b.length;c<a;c++)if(1==e&&d===b[c]||d.toLowerCase()===b[c].toLowerCase())return!0;return!1};if(!b||"string"===typeof b){if("string"!==typeof d||""===d)return b;e=e||",";c=c||e;1==c&&(c=e,g||(g=1));2==c&&1!=g&&(c=e);d=d.split(",");h=d.length;for(var f=0;f<h;f++)window.inList(b,d[f],e,g)||(b=b?b+c+d[f]:d[f])}return b};
/******************************************** END CODE TO DEPLOY ********************************************/
We’ve set s.usePlugins
to true, we’re using Adobe’s recommended approach to declaring doPlugins
, and we’re hoisting the plugin code. So why doesn’t this work?!
When you try to run s.apl()
in the above code, it will throw a ReferenceError
because apl
is not a property of s
. Of course it’s not: sure, apl
is part of the closure, so you could use apl()
by itself (i.e. not as a method of s
) in your doPlugins
code—but what if you need to use apl
somewhere other than in the extension config?
doPlugins
is a method of the tracker object. That means wherever s
is available, doPlugins
is available as well. Not so for our good ol’ apl
plugin though: this is only part of the same scope as the custom tracking code. So how do we make it available when we want to (ahem, need to) use apl
in, say, the custom code of a Set Variables action? We have to tweak the plugin code a little to make it a method of the tracker object as well, just like s.doPlugins
.
/* default */
function apl(/*...*/)
/* change to */
s.apl = function(/*...*/)
Rather than just declaring the function in the scope of the extension config code, we’re declaring it on the tracker object itself. Now we can use it in any custom code block! It’s also a little more cohesive — I know apl
is related to my Adobe Analytics implementation because it’s part of the tracker object.
Wrapping up
We’ve covered a lot here, and hopefully each of these things is a little more clear now:
- How to (and not to) declare
doPlugins
- The best way to include plugin code
- How to make plugins work outside of the Analytics extension config’s custom tracker code
I hope to write a lot more on this topic — I want to share as much knowledge with you, dear reader, so you don’t have to piece it all together yourself. Thanks for reading!
Questions? Feedback? Find me on twitter: @tyssejc