Hey, I'm writing a book too :) Click the link below to preorder and save ;)
»» Get the Only Reference You'll "ever" Need on JavaScript Engineering Interviews, Now! ««
July 28th in Widget by .

JavaScript Widget Development Best Practices (Part 5: Getting Your Hands Dirty)

getting our hands dirty

getting our hands dirty

I’m giving a talk on JavaScript Widget development best practices tomorrow at jstanbul 2012.

At the conference, I will have a 30-minute timeframe to express as much as I can with respect to external JavaScript widget development best practices.

Since 30 minutes is not enough for this, my aim is to give a quickly overview, and send the audience to this blog for further investigation.

This means that this blog article (that I’m currently writing) must be as complete and as coherent as possible, before my talk tomorrow.

So I’m going to cram as much things into here as possible. I will intentionally leave certain parts incomplete, and I will definitely leave (some obvious, some not so obvious) security loopholes. I will cover them in the following articles of the series.

The Result

At the end of this tutorial we will be creating something similar to this:

widget login user interface

widget login user interface

widget login form

widget login form

widget (logged in)

widget (logged in)

widget console

widget console

Note that the login form above is dynamically injected from the widget provider’s domain (http://api.widget.www). And the widget lives in a totally different publisher domain (http://publisher.www).

That’s one of the many things that makes external JavaScript widget development complicated.

For a broad overview of other challenges involved in External JavaScript Widget Development, you can view my #jstanbul 2012 slides on the topic

So let’s start and get our hands dirty; shall we?

Last Part in Review

Here’s a recap of what we’ve done so far: In the former article, we have seen different ways to send initialization parameters to our widget, and we have also listed several techniques on how to establish cross-domain communication between the publisher’s site and the widget API server. We choosed JSONP as a cross-browser proxy, while mentioning the possible drawbacks of our choice.

Let’s Render a User Interface

When it comes to external JavaScript widgets, there are various ways to render. The most common two are:

  • Rendering the widget’s UI by appending HTML before the widget’s script tag.
  • And rendering the widget’s UI by explicitly specifying a target location (i.e. a DOM element with a specific attribute) on the publisher’s website.

The former technique involves traversing the publisher’s DOM, locating the instance or instances of widget bootloader scripts, and injecting HTML before each script.

While in the latter, we explicity specify placeholder HTML tags as an anchor positions to render the widget. We’ll see an example of this technique down below.

We’ll continue our tutorial with the second implementation, since…

  • It gives more flexibility;
  • It is less prone to errors;
  • It does not create race conditions (when we have more than one scripts on the page);
  • Requires less lines of code;
  • And is a better demonstration of separation of concerns.

Create an Anchor on the Publisher’s Website

Let’s start by adding a div with a special data-wd-anchor attribute.

Our bootloader script will detect this attribute and render our widget’s user interface inside that div.

-//publisher.www/index.jade
    ...
    body(lang='en')
        section#Main
            h2 Welcome to Publisher Website

            p(style='float:right;width:300px') Lorem ipsum dolor sit amet.

            p(style='float:left;width:300px') Vestibulum dui erat.

            div(data-wd-anchor, style='float:left;width:300px;margin-top:20px;margin-left:10px;')

            p(style='float:right;width:300px') Mauris ac dolor ante.

And here is the piece of code on the widget API script that does the rendering:

// api.widget.www/api.v.0.1.js

    /*
     * Renders the widget
     */
    function render(state) {
        var div = getWidgetAnchor();

        if (!div) {
            return;
        }

        o2.Dom.loadCss(
            o2.String.concat(kApiRoot, kCssPath),
            function() {
                renderWidget(div, state.data);

                processPostRenderActions();
            }
        );
    }

We will come to o2.Dom.loadCss later.
For now lets focus our attention to getWidgetAnchor method:

    /*
     * Find a place to append the widget UI.
     */
    function getWidgetAnchor() {
        var div = null;

        // divs is a "live" node list
        var divs = document.getElementsByTagName(kDiv);
        var len  = divs.length;
        var i    = 0;

        for (i = 0; i < len; i++) {
            div = divs[i];

            if (div.hasAttribute(kWidgetAnchor)) {
                return div;
            }
        }

        return null;
    }

The method simply finds the first div element with the data-wd-anchor attribute and returns it.

Styling the Widget

To give the desired look and feel and functionality to our widget, we have to add some CSS styling to our widget.

There are several ways of doing this:

  • We can use inline styles (as in [div style="background:#aaccff;border:1px solid">...[/div]);
  • We can load our CSS file as a dependency at the initialization phase of the bootloader;
  • We can lazy load the CSS just before rendering the widget;
  • We can embed the CSS declerations directly into JavaScript and append them to a style node that we dynamically create.

Each of these techniques have their pros and cons.
In this tutorial we will continue with the “lazy loading” approach.

Adding More Modules to the Dependency Loader

But first we’ll need to add a couple of more JavaScript files to our dependency loader:

    /*
     * Load necessary o2.js components in noConflict mode.
     */
    function loadDependencies(callback) {
        log('o->loadDependencies(');
        log(callback);
        log(')');

        setReadyState(kLoadingDependencies);

        loadScripts(kO2Root, [
            'o2.meta.js',
            'o2.core.js',
            'o2.string.core.js',
            'o2.jsonp.core.js',
            'o2.dom.constants.js',
            'o2.dom.core.js',
            'o2.dom.load.js',
            'o2.event.constants.js',
            'o2.event.core.js',
            'o2.validation.core.js',
            'o2.method.core.js',
            'o2.collection.core.js'
        ], callback);
    }

Note that we are adding more and more files to the initializer array. Since every item in this array corresponds to one more HTTP request, in a production application we will have to merge, minify and gzip those scripts into a single file to improve speed and performance (we’ll come to that in followup articles).

Asynchronously Load Widget CSS

Let’s see our render code once more:

    /*
     * Renders the widget
     */
    function render(state) {
        ...

        o2.Dom.loadCss(
            o2.String.concat(kApiRoot, kCssPath),
            function() {
                renderWidget(div, state.data);

                processPostRenderActions();
            }
        );
    }

The above code appends http://api.widget.www/css/v.0.1/widget.css file to the head of the document, and then executes the callback closure.

Just for a sec, assume that the CSS that we load contains the following directive

body {background: red !important;}

After loading the CSS, we’ll see that all of a sudden the publisher’s page background will become red.
Remember? We don’t own publisher’s DOM, so we have to constraint our styling to our widget only.

Styling a widget requires a lot of testing with a combination of different demo publisher pages and user agents; since unless you’re injecting your widget with an iframe (and there are good reasons to do so; one of which is preserving the look and feel), the publisher’s styles can always override yours.

Moreover you have to decide the following yourself?

  • Shall the widget look exactly the same in every single one of the publishers?
  • Or shall the widget inherit some of the styling of the publisher, to give a more streamlined look and feel?
  • Shall we, as the widget authors, provide a way to easily modify the styling of our widget depending on their taste?
  • And if so, to what extent?

There is no silver bullet to correctly answer these questions, and you have to judge for yourself regarding how rigid or how flexible your approach may be.

Techniques to Ensure Style Consistency

To avoid our styles conflict with the publisher’s we will need to prefix them with our namespace, for example:

.wd-box p {
    margin : 5px !important;
    padding: 0 !important;
}

.wd-box.wd-login {
    width : 332px;
}

.wd-boxBody {
    background   : #fefefe;
    border-top   : 1px solid #dde0e8;
    border-bottom: 1px solid #dde0e8;
    border-left  : 0;
    border-right : 0;
    padding      : 10px 20px;
}

Since .box is a really generic class name, it’s possible that our publisher may have a .box class defined already, and it may interfere with ours. So instead of using class names like .box and .login, we prefix them with our widget namespace wd- as in .wd-box, and .wd-login.

As a rule of thumb, prefix anything that’s publicly reachable and modifiable (like css classes, global variables, DOM attributes…)

However due to the rules of CSS specificity, simply prefixing our classes may not provide enough protection: Our rules may be overridden by a more specific ruleset of the publisher’s.

If that’s the case we can over-specify our selectors like:

body#wd-mastercontainer #wd-wrap .wd-box.wd-login {
    width : 332px;
    margin: 10px;
}
[/sourcecodde]

Using two id selectors makes our selector <strong>highly unlikely</strong> to be unintentionally overridden by the publisher's styles. And if the publisher "intentionally" overrides the styles, she should know what she's doing anyway.

Another thing we can do is to <strong>abuse !important</strong> declarations:

[sourcecode language="css"]
.wd-boxBody {
    background   : #fefefe !important;
    border-top   : 1px solid #dde0e8 !important;
    border-bottom: 1px solid #dde0e8 !important;
    border-left  : 0 !important;
    border-right : 0 !important;
    padding      : 10px 20px !important;
}

Keep in mind that overusing !important declarations also takes away the ability give the publisher freedom to override our styles, if they want: !important declerations will always take precedence. Use them sparingly.

Render the Initial UI

After loading the CSS, we render the widget UI inside the callback. That’s as easy as assigning an innerHTML.

    /*
     * Does the actual rendering.
     */
    function renderWidget(container, html) {
        if (!container) {
            return;
        }

        container.innerHTML = html;
    }

Post-Render Processing

And after rendering the widget UI, we do further processing (in processPostRenderActions):

    /*
     * Things done after the initial view is rendered.
     */
    function processPostRenderActions() {
        delegateEvents();

        processQueue();

        window[kWidgetQueueAlias] = queue;

        setReadyState(kComplete);

        fireAsyncInit();
    }

Here’s what the above code does:

  • We bind event handlers;
  • Then we process the job queue that the publisher has provided us;
  • Then we overload the queue implementation with an Array-like object;
  • Then we set ready state to complete to indicate that our initialization has been completed;
  • Then we call _wdAsyncInit to tell the publisher that we are done with the widget initialization.

Let’s see all those codes one by one:

Event Delegation

    /*
     * Use event delegation to bind widget events.
     */
    function delegateEvents() {
        log('o->delegateEvents()');

        o2.Event.addEventListener(document, kClick, document_click);
    }

We simply add a global event listener to document’s click event.

Process Job Queue

The job queue is simply an array (window._wdq) of directives that are delated to be executed until the widget is ready. Since the directives are executed async, it does not affect speed of the widget’s initialization. It also does not degrade the publisher site’s performance.

Here’s how we process the job queue:

    /*
     * Processes the job queue item by item.
     */
    var processQueue = function() {
        log('o->processQueue()');

        setReadyState(kBeginProcessQueue);

        var q = null;

        if (window[kWidgetQueueAlias] &&
                    o2.Validation.isArray(window[kWidgetQueueAlias])) {
            q = window[kWidgetQueueAlias];

            while (q.length) {
                execute(q.pop());
            }
        }

        processQueue = noop;
    };

Where the execute function is as follows:

    /*
     * Executes the job queue asyncronously.
     */
    function execute(item) {
        log('o->execute()');

        var action = item[kAction];

        switch (action) {
            case kEcho:
                log('ECHO: ');
                log(item[kPayload]);

                break;
            default:
                log('ERROR: no mapping for action "' + action + '"');
        }
    }

The method simply executes API methods depending on the action parameter.

So the following code in the publisher’s page:

window._wdq.push({action : 'echo', payload : {'hello' : 'world'}});

Will print

ECHO:
{"hello" : "world"};

on the console.

Overload Queue Implementation

And after that we overload the queue implementation:

window[kWidgetQueueAlias] = queue;

Where the queue implementation is:

    /*
     * An overridden version of the async job queue.
     */
    var queue = {
        items : [],

        push : function(item) {
            log('o->queue.push()');

            execute(item);
        }
    };

We just overload the push method so that the publisher can call window._wdq.push even after we initialize our widget.

Call Async Init Function

Facebook uses exactly the same approach, when initializing FB JS SDK.

At the end of the flow we simply call window._wdAsyncInit to give publisher the freedom execute their post-initialization logic, if necessary.

Click Event Delegation

Now let’s observe the global document click event handler:

    /*
     * Global event handler on document's click event.
     */
    function document_click(evt) {
        log('document_click()');

        var target = o2.Event.getTarget(evt);

        var id = target.id;

        if (!id) {
            return;
        }

        // Just for demonstration.
        var params = {};
        params[kUsername] = 'dummy';
        params[kPassword] = 'dummy';

        if (id.indexOf(kLoginButtonId) > -1) {
            o2.Jsonp.get(
                o2.String.concat(kApiRoot, kLoginPath),
                params,
                processUserLogin
            );
        }
    }

To understand what this function does let’s flashback to initialize method:

    /*
     * Initialize after loading prerequisites.
     */
    function initialize() {
        log('o->initialize()');

        setReadyState(kLoadedDependencies);

        if (!window.o2) {return;}

        window.o2.noConflict(kO2Alias);

        o2 = window[kO2Alias];

        var config = getConfiguration();

        config[kGuid] = o2.String.generateGuid();

        loadInitialState(config, processPostInitialization);
    }

We send a guid parameter (via o2.String.generateGuid()) to the server. This way, the server’s the rendered HTML will
have this guid prefix appended to ids of its elements.

Here’s a sample view for our widget’s rendered HTML:

<form class="wd-box wd-login" id="wd_h5b9s072n8l">
    <fieldset class="wd-boxBody">
        <label for="wd_usernameh5b9s072n8l">Username</label>
        <input type="text" required="required" placeholder="type in your &quot;Cool Publisher&quot; 
        username." tabindex="1" id="wd_usernameh5b9s072n8l">
        <label for="wd_passwordh5b9s072n8l">
        <a class="wd-rLink" tabindex="5" href="/">Forgot password?</a><span>Password</span></label>
        <input type="password" required="required" tabindex="2" id="wd_passwordh5b9s072n8l">
    </fieldset>
    <footer>
        <label for="wd_chk_rememberh5b9s072n8l">
            <input type="checkbox" tabindex="3" id="wd_chk_rememberh5b9s072n8l">
            <span>Keep me logged in</span>
        </label>
        <input type="button" class="wd-btnLogin" tabindex="4" 
            value="Login" id="wd_btnLoginh5b9s072n8l">
    </footer>
</form>

So on the global document_click event handler, if the user clicks on the login button, we detect it an do a JSONP API call:

...
        if (id.indexOf(kLoginButtonId) > -1) {
            o2.Jsonp.get(
                o2.String.concat(kApiRoot, kLoginPath),
                params,
                processUserLogin
            );
        }
...

When the call succeeds, processUserLogin is executed, which simply refreshes the widget view:

    /*
     * User login JSONP callback.
     */
    function processUserLogin(response) {
        var div = getWidgetAnchor();
        div.innerHTML = response.data;
    }

Session Persistence

And here’s what happens on the server when the user sends a login request:

// api.widget.wwwroot/index.js
...
        /**
         * Authenticates user.
         */
        app.get(v_0_1(route).LOGIN, function(req, res) {
            var username = req.param(parameter.USERNAME);
            var password = req.param(parameter.PASSWORD);
            var callback = req.param(parameter.CALLBACK);

            if (!username) {
                res.send(statusCode.NO_DATA);
            }

            if (!password) {
                res.send(statusCode.NO_DATA);
            }

            if (!callback) {
                res.send(statusCode.NO_DATA);
            }

            var session = req.session;

            session[parameter.USERNAME] = username;

            var params = {};

            sendJsonp(req, res, params, callback);
        });

Where sendJsonp is:

    /*
     *
     */
    function send(res, callback, result) {
        res.send(
            callback + '(' + JSON.stringify(result) + ');'
        );
    }

    /*
     *
     */
    function sendJsonp(req, res, params, callback) {
        var result = {
            data :
                isUserLoggedIn(req) ?
                createDashboard(params) :
                createLoginForm(params)
        };

        send(res, callback, result);
    }

And isUserLoggedIn is:

    /*
     *
     */
    function isUserLoggedIn(req) {
        return !!req.session[parameter.USERNAME];
    }

Without any verification, we add user’s username to the session, and send a dashboard html to the publisher to refresh its widget’s view.

Note that this is just for demonstration purposes. Normally we should have validated username/password of the user against our database and send an error in the JSONP response if they don’t match. We’ll implement this in followup articles.

Since we store the username in the session; when we refresh publisher’s page our widget will immediately render the dashboard instead of the login form. We will not see the login form until the user’s session expires.

Third Party Issues

There’s one caveat though. To persist session, we are making use of third party cookies.

In some of the browsers (like Safari) third party cookies are disabled by default. And in all of the browsers users can explicitly disable third party cookies. We cannot persist user date in the Session object, if third party cookies are disabled.

There are ways to overcome these, which we will explore in followup articles too. For instance Facebook uses a dedicated login window in a popup in conjunction with an approach similar to the klein bottle method, to be able to read and write third party cookies.

Size Does Matter

If you have followed all the tutorials this far, you migh have seen that the size of our bootloader is growing as we add more and more feature.

Currently this size is manageable, however after a certain limit, you may want to submodules for each logical group, and either lazy-load them or load it while loading the initial dependencies.

I’m trying to bring this tutorial series to a somewhat complete state before my seminar in jstanbul 2012, so I won’t have time for this refactoring right now.

However we’ll be doing this refactoring, along with certain security enhancements, deplyoyment scripts, compression and minification in the upcoming tutorials.

Next Up?

In the upcoming tutorials we will cover the following topics, and more:

  • Refactoring our API to further split into logically related modules;
  • Minification and deployment;
  • Authorization and authentication;
  • Persisting and retrieving data using CORS;
  • Strengthening the security of our application, preventing against CSRF attacks.

Read the Source Luke

You can get the source code accompanying this tutorial at this github history snapshot.

Conclusion

If this tutorial looks like a few articles loosely coupled together out of hassle, it probably is :)

In the followup articles we will elaborate on many of the issues we’ve just touched the surface of here.

Also for the unlikely case that you are in Istanbul (since 90% of my blog readers are in the Bay Area), see you in jstanbul 2012!

And, as always, feel free to send your comments and suggestions.

It's me: Volkan! Jack of all Trades, Samurai of JavaScript ;) Since 2003, I’ve been doing front-end development on client-heavy AJAX web applications. Currently, I'm a JavaScript Hacker at "SocialWire". Before that I was a VP of Technology at "GROU.PS", a well-known do-it-yourself social networking platform that allows people to come together and form interactive communities around a shared interest or affiliation. Before that, I was a JavaScript Engineer at "LiveGO", a globally known social mash-up. Before that, I was the CTO of a business network ("cember.net") which was acquired by Xing AG for around 4.2M Euros. See my linkedin profile to find more about me; I also share worth-following bits an pieces on twitter.

VIsit Volkan Özçelik's website

Leave a Reply





o2.js _
Fork Ribbon