Easy template injection in JavaScript for userscript authors, plugin devs, and other people who want to fuck with Web page content

The Predator Alert Tool for Twitter is coming along nicely, but it’s frustratingly slow going. It’s extra frustrating for me because ever since telling corporate America and its project managers to go kill themselves, I’ve grown accustomed to an utterly absurd speed of project development. I know I’ve only been writing and rewriting code for just under two weeks (I think—I honestly don’t even know or care what day it is), but still.

I think it also feels extra slow is because I’m learning a lot of new stuff along the way. That in and of itself is great, but I’m notoriously impatient. Which I totally consider a virtue because fuck waiting. Still, all this relearning and slow going has given me the opportunity to refine a few techniques I used in previous Predator Alert Tool scripts.

Here’s one technique I think is especially nifty: template injection. That’s a fancy way to describe changing the source code of a Web page on-the-fly. Basically, any time you need to change something on a Web page, or any time something on the Web page changes that you need to intercept (for which I use Mutation Observers). As you can imagine, this is central to the way Predator Alert Tool userscripts work, as well as most browser plugins and the like.

The idea behind template injection is to define a set of replacement strings that you can use in a “template” (itself just a string of HTML). Then, insert the template at a specific point in the DOM. The core of my simple template injection system is a function called (wait for it) “injectTemplate” and it looks like this:

PATTwitter.UI.injectTemplate = function (name, params, ref_el, injectMethod) {
    var injectMethod = injectMethod || 'appendTo';
    var t = PATTwitter.UI.Templates[name];
    var tags = PATTwitter.UI.Templates.Tags;
    for (x in tags) {
        t = t.replace(new RegExp(x, 'g'), tags[x](params));
    }
    return jQuery(t)[injectMethod](ref_el);
};

You can specify in injectMethod in the fourth parameter to injectTemplate, which is expected to be a jQuery DOM manipulation function. It defaults to .appendTo() but you could also pass insertAfter, for instance, to invoke .insertAfter() on the reference element (ref_el).

The magic happens in the for loop with the call to t.replace(new RegExp(x, 'g'), tags[x](params));. The variable t is a template that I’ve previously defined, like this:

PATTwitter.UI = {};              // FlightJS components and such.
PATTwitter.UI.Templates = {};    // Stores template HTML for UI components.
PATTwitter.UI.Templates.dashboard_list_stat = '\
<li id="pat-twitter-pat-lists" class="DashboardProfileCard-stat Gric-cell u-size1of3">\
    <a class="DashboardProfileCard-statLink u-textCenter u-textUserColor u-linkClean u-block" title="%PAT_TWITTER_ITEM_COUNT% PAT Lists" href="https://twitter.com/%PAT_TWITTER_SCREEN_NAME%/lists#pat-twitter-twitter-blocklist">\
        <span class="DashboardProfileCard-statLabel u-block">PAT Lists</span>\
        <span class="DashboardProfileCard-statValue u-block">%PAT_TWITTER_ITEM_COUNT%</span>\
    </a>\
</li>';

Notice that since the template is itself a JavaScript string, every line ends with a backslash (\). In the template, there are placeholders in the form of strings like %PAT_TWITTER_USER_SCREEN_NAME%. Each of these is the key to a property stored in an object so that the template tags reference functions that all return strings, like so:

PATTwitter.UI.Templates.Tags = { // Helpers for quickly injecting HTML.
    '%PAT_TWITTER_PREFIX%'          : function () { return 'pat-twitter-'; },
    '%PAT_TWITTER_ITEM_ID%'         : function (item_info) { return item_info.id; },
    '%PAT_TWITTER_ITEM_NAME%'       : function (item_info) { return item_info.name; },
    '%PAT_TWITTER_ITEM_NAME_DASHED%': function (item_info) {
        if (item_info.name) {
            return item_info.name.replace(/ /g, '-');
        }
    },
    '%PAT_TWITTER_ITEM_COUNT%': function (item_info) { return item_info.count; },
    '%PAT_TWITTER_USER_SCREEN_NAME%'     : PATTwitter.getMyTwitterScreenName,
};

So when we invoke injectTemplate(), we’re simply iterating over the template string with the for...in loop, and finding-and-replacing the tags with the result of the function to which the tag points,the call to tags[x](params).  For the sake of completeness, here’s the PATTwitter.getMyTwitterScreenName function:

PATTwitter.getMyTwitterScreenName = function () {
    return jQuery('.js-mini-current-user').data('screen-name');
};

Again, the point is that it returns a string.

Since injectTemplate() returns a jQuery collection, it can be used like this:

var lists = PATTwitter.getMyLists(); // returns an Array
var injectedElement = PATTwitter.UI.injectTemplate('dashboard_list_stat', {
    {'count': lists.length }
}, jQuery('.DashboardProfileCard-statList'))

Now injectedElement is a jQuery object holding attached DOM nodes. If you’re using something like Flight JS as Twitter does, you can create a simple component and attach your component to the injected code like so:


var myComponent = flight.component(function () {
    this.onClick = function (e) {
        alert('I was clicked!');
    };

    this.after('initialize', function () {
        this.on('click', this.onClick);
    });
});
myComponent.attachTo(injectedElement);

And there you have it. The result of all this, in the current Predator Alert Tool for Twitter prototype, is that when you load your Twitter homepage, an additional “Dashboard statistic” is displayed underneath your follower count and so on. It looks like this:

The Predator Alert Tool for Twitter modifies various parts of the Twitter Web interface using a simple template injection technique. Here you can see an additional row added to user's profile Dashboard statistics displaying the number of Predator Alert Tool Lists they are subscribed to.
The Predator Alert Tool for Twitter modifies various parts of the Twitter Web interface using a simple template injection technique. Here you can see an additional row added to user’s profile Dashboard statistics displaying the number of Predator Alert Tool Lists they are subscribed to.

As Dr. Seuss might say, inject it in a loop. Inject it for a hoot. Inject it with a toot.

Um. Yeah. Okay, I’m done being nerdy now.