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! ««
March 13th in Code Organization Guidelines by .

How to Refactor a Mess into an Organized Web Application (Part 3: the Client Side)

"Client Side" is Your Gateway

"Client Side" is Your Gateway

In part 2 of the series, we organized the server-side of our vcard demo application. Today, we will be dealing with the client side:

  • We will be progressively enhancing our initial application to get vcard data without refreshing the page;
  • We will be strictly diving our application into four tiers;
  • And we will be defining the relationship and message flow between those tiers.

Bottom Line Up Front

All the modifications and refactorings mentioned in this article revolve around this diagram:

Messaging Sequence Between the 4 Tiers of Separation

Messaging Sequence Between the 4 Tiers of Separation

The moral of the story is:

  • All callbacks go to the delegation tier.
  • Delegation tier cannot call the communication tier or the presentation tier directly, it should call the behavior tier first.
  • Presentation tier is the “sink“, it cannot call anyone else.
  • The only exception is the persistence tier: everyone can read from it, but only the behavior tier can write to it.

Initial JavaScript

Let’s start by adding some JavaScript to our app:

<?php
    namespace o2js\vcardapp\presentation\script;

    //examples/vcardapp/include/presentation/script/index.php
?>
<?php /* ROOT NAMESPACE */ ?>
<script type="text/javascript">var VCardApp = {};</script>
<script type="text/javascript" charset="UTF-8" src="js/index.js"></script>

In the above code, we defined a global VCardApp namespace and added an index.js file to the bottom of the page.

The only two global references we will be using in this application will be o2 (to bind cross-browser event-handling and AJAX functionality) and VCardApp. We will be enclosing all of our application logic inside the VCardApp namespace.

If I could offer you only one advice “do not pollute the darn global namespace!” would be it.

Keeping the global namespace clean, will not only give you a sane codebase, but it will also ensure the sanity of your mind in future code changes, bugfixes, and feature implementations.

The content of the index.js is simple:

(function(document){
    'use strict';

    /*
     * Common Constants
     */
    var kNone  = 'none';
    var kBlock = 'block';

    /*
     * Common Elements
     */
    var kActivatorDiv     = 'VCardActivator';
    var kContentDiv       = 'VCardContent';
    var kVCardButton      = 'vcard-volkan';
    var kVCardCloseButton = 'vcard-volkan-close';

    /*
     * Common HTML
     */
    var vCardHtml = [ ... some HTML ... ].join('');

    function showVCard() {
        document.getElementById(kContentDiv).innerHTML = vCardHtml;
        document.getElementById(kContentDiv).style.display = kBlock;
    }

    function closeVCard() {
        document.getElementById(kActivatorDiv).style.display = kBlock;
        document.getElementById(kContentDiv).style.display = kNone;
    }

    document.getElementById(kVCardButton).onclick = function() {
        showVCard();

        // assigning click event after the button is rendered.
        // we'll be implementing an even delegation pattern below soon,
        // which will solve this problem.
        document.getElementById(kVCardCloseButton).onclick = function() {
            closeVCard();
            return false;
        };

        return false;
    };
}(this.document));

Instead of using a static hard-coded HTML (i.e. the vCardHtml variable), it’s more logical to call an API endpoint to get the vcard information.

But first, we’ll need to lay some AJAX groundwork:

Add AJAX Support

When we look at o2.js API documentation for Ajax functionality, we’ll see that the o2.Ajax class is defined in ajax.core module:

static class o2.Ajax
A static class for making AJAX GET and POST requests.
Defined in ajax.core

Each o2.js module has o2.{modulename}.js file naming, by convention.

Keeping this in mind let’s add the ajax module to include/presentation/script/index.php and see what happens:

...
<script type="text/javascript">var VCardApp = {};</script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.ajax.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="js/index.js"></script>

After adding that and refreshing the page we’ll see the following error in Firebug:

framework is undefined
[Break On This Error]
var _         = framework.protecteds;

That’s because we’ve not included o2 and o2.protecteds namespaces, which are defined in o2.meta.js and o2.js respectively.

Let’s also add these modules to the mix:

<script type="text/javascript">var VCardApp = {};</script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.meta.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.ajax.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="js/index.js"></script>

Adding these, and refreshing the page afterwards, we’ll see yet another error in Firebug console:

uncaught exception: framework.protecteds: Class "StringHelper" does not currently exist.
> var generateGuid = require(kStringHelper, 'generateGuid');

Using the same reasoning and searching o2.js API documentation, we’ll see that class StringHelper is defined in stringhelper.core module which is located in o2.stringhelper.core.js. So let’s add it as well:

<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.meta.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.stringhelper.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.ajax.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="js/index.js"></script>

We’ll see yet another error:

uncaught exception: framework.protecteds: Class "EventHandler" does not currently exist.
> var listen = require('EventHandler', 'addEventListener');

After adding event-related o2.js modules (o2.eventhandler.constants.js and o2.eventhandler.core.js) all errors would be gone and we will be ready to use AJAX functionality in our app:

<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.meta.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.core.js"></script>

<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.stringhelper.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.eventhandler.constants.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.eventhandler.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.ajax.core.js"></script>

One of the good parts of o2.js is its high modularity: If the only thing we need is AJAX functionality, instead of adding the entire framework, we can add only related modules and keep our code base as tiny as possible.

Though, admittedly, figuring out what to include this way takes some some effort. In order to make the process seamless, I plan to create a dependency resolver for o2.js, so that the minimum amount of code required can be automatically generated and downloaded with the click of a button.

Move Rendering Logic to Presentation Tier

When we look at our initial index.js, we see that everything is lumped together. That’s okay for a simplistic application like this one.

But as the application grows, logically categorizing functionality to different layers makes the application

  • easier to debug,
  • easier to maintain,
  • and less prone to errors.

Taking this into account, let us create a RenderController class in the Presentation Tier:

// js/controller/presentation/render/rendercontroller.js

(function(app, document) {
    'use strict';

    var me = app.RenderController = {};

    var kActivatorDiv     = 'VCardActivator';
    var kContentDiv       = 'VCardContent';

    /*
     * Common constants.
     */
    var kBlock = 'block';
    var kNone  = 'none';

    /*
     * Common HTML
     */
    var vCardHtml = [ ... some HTML ... ].join('');

    me.showVCard = function() {
        document.getElementById(kContentDiv).innerHTML = vCardHtml;
        document.getElementById(kContentDiv).style.display = kBlock;
        document.getElementById(kActivatorDiv).style.display = kNone;
    };

    me.closeVCard = function() {
        document.getElementById(kActivatorDiv).style.display = kBlock;
        document.getElementById(kContentDiv).style.display = kNone;
    };
}(this.VCardApp, this.document));

And the new index.js would become:

(function(app, document){
    'use strict';

    /*
     * Common elements.
     */
    var kVCardButton      = 'vcard-volkan';
    var kVCardCloseButton = 'vcard-volkan-close';

    var showVCard  = app.RenderController.showVCard;
    var closeVCard = app.RenderController.closeVCard;

    document.getElementById(kVCardButton).onclick = function() {
        showVCard();

        document.getElementById(kVCardCloseButton).onclick = function() {
            closeVCard();
            return false;
        };

        return false;
    };
}(this.VCardApp, this.document));

We’ll also have to add a reference to rendercontroller.js to footer scripts:

<?php
    namespace o2js\vcardapp\presentation\script;

    // includes/presentation/script/index.js
?>
<?php /* ROOT NAMESPACE */ ?>
<script type="text/javascript">var VCardApp = {};</script>

... o2.js library code ...

<script type="text/javascript" charset="UTF-8" src="js/presentation/controller/render/rendercontroller.js"></script>

<script type="text/javascript" charset="UTF-8" src="js/index.js"></script>

You can view what we did so far at this github snapshot.

Now let us modify the RenderController.showVCard function so that it dynamically displays a vcard info instead of printing a static constant:

(function(o2, app, document) {
    'use strict';

    var me = app.RenderController = {};

    var kActivatorDiv = 'VCardActivator';
    var kContentDiv   = 'VCardContent';

    /*
     * Common constants.
     */
    var kBlock = 'block';
    var kNone  = 'none';

    /*
     * Aliases
     */
    var $    = o2.$;
    var hide = o2.DomHelper.hide;
    var show = o2.DomHelper.show;

    me.showVCard = function(html) {
        var contentDiv = $(kContentDiv);
        contentDiv.innerHTML = html;

        show(contentDiv);
        hide(kActivatorDiv);
    };

    me.closeVCard = function() {
        hide(kContentDiv);
        show(kActivatorDiv);
    };
}(this.o2, this.VCardApp, this.document));

The “html” parameter of showVCard function will be passed to it after calling an API endpoint.

Server-Side API Endpoint

So let’s create an API endpoint on the server side. It would be easy, because in the former tutorial we defined a VCardManager class that retrieves the vcard info of users. The only thing that remains is simply call the getVCardHtml method of it.

<?php
    namespace o2js\vcardapp\rpc\service;

    // service/vcard.php

    include("../include/config/constants.php");
    include("../include/business/manager/vcard.php");

    use o2js\vcardapp\config\constants\ServiceKey;
    use o2js\vcardapp\business\manager\VCardManager;

    echo VCardManager::getVCardHtml($_GET[ServiceKey::USER_NAME]);
?>

Calling http://localhost/o2.js/examples/vcardapp/service/vcard.php?u=volkan will give an output as follows:

VCard API Output

VCard API Output

And the HTML of it would be:

<h1>Volkan Özçelik</h1>
<dl>
    <dt>Web</dt>
    <dd><a href="http://o2js.com">o2js.com</a></dd>
    <dt>geekli.st</dt>
    <dd><a href="http://geekli.st/volkan">@volkan</a></dd>
    <dt>github</dt>
    <dd><a href="https://github.com/v0lkan">@v0lkan</a></dd>
    <dt>LinkedIn</dt>
    <dd><a href="http://linkedin.com/in/volkanozcelik">volkanozcelik</a></dd>
    <dt>Email</dt>
    <dd><a href="mailto:volkan@o2js.com">volkan@o2js.com</a></dd>
    <dt>twitter</dt>
    <dd><a href="http://twitter.com/linkibol">@linkibol</a></dd>
</dl>
<p class="clear"><a href="../close" id="vcard-volkan-close"
    class="close" title="close" ><span>back to home</span></a></p>

PageController

Now let’s create a PageController JavaScript class. This class will be an intermediate business object between our Delegation and Presentation tiers.

Tip: Since the Delegation Tier cannot directly call Presentation Tier methods
(it’s a “design by contract), it should chain its responsibility over an intermediate Behavior Tier business object.

(function(app, document) {
    'use strict';

    // js/behavior/controller/page/pagecontroller.js

    /**
     *
     */
    var me = app.PageController = {};

    /*
     * Aliases
     */
    var show  = app.RenderController.showVCard;
    var close = app.RenderController.closeVCard;

    /**
     *
     */
    me.showVCard = function() {
        show();
    };

    /**
     *
     */
    me.closeVCard = function() {
        close();
    };
}(this.VCardApp, this.document));

EventController

Event-handlers and callbacks belong to the Delegation Tier. So let’s move our event-handling logic to the Delegation Tier, by creating an EventController class there.

(function(app, document) {
    'use strict';

    // js/delegation/controller/event/eventcontroller.js

    /**
     *
     */
    var me = app.EventController = {};

    /*
     * Common Elements
     */
    var kVCardButton      = 'vcard-volkan';
    var kVCardCloseButton = 'vcard-volkan-close';

    /*
     * Behavior Tier Stub
     */
    var page = app.PageController;

    /**
     *
     */
    me.bindEventHandlers = function() {
        document.getElementById(kVCardButton).onclick = function() {
            page.showVCard();

            document.getElementById(kVCardCloseButton).onclick = function() {
                page.closeVCard();

                return false;
            };

            return false;
        };
    };
}(this.VCardApp, this.document));

Then the new index.js would be:

(function(app) {
    'use strict';

    var controller = app.EventController;

    controller.bindEventHandlers();
}(this.VCardApp));

Now our index.js file is nothing but a simple bootstrapper.
Since it’s just a bootstrapper, it’ll be logical to move it to the bootstrap folder.

Tip: Did you notice that we did not use a DOM content ready event handler. That’s because we included our scripts at the bottom of the page. DOM is already ready, when our scripts launch — a nice side-effect ;) .

After all these changes the order of our footer scripts will be as follows:

<script type="text/javascript">var VCardApp = {};</script>

<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.meta.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.core.js"></script>

<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.stringhelper.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.eventhandler.constants.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.eventhandler.core.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.ajax.core.js"></script>

<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.stringhelper.transform.js"></script>
<script type="text/javascript" charset="UTF-8" src="../../o2.js/o2.domhelper.style.js"></script>

<script type="text/javascript" charset="UTF-8" src="js/presentation/controller/render/rendercontroller.js"></script>

<script type="text/javascript" charset="UTF-8" src="js/behavior/controller/page/pagecontroller.js"></script>

<script type="text/javascript" charset="UTF-8" src="js/delegation/controller/event/eventcontroller.js"></script>

<script type="text/javascript" charset="UTF-8" src="js/bootstrap/init.js"></script>

Event Delegation to the Rescue

Things seem to be more “organized“. The only thing peculiar thing is that we bind a new event handler to the “close” button every time we click the “show vcard” button:

        document.getElementById(kVCardButton).onclick = function() {
...
            //
            // The event below is bound after every single click action on 
            // document.getElementById(kVCardButton)
            //
            document.getElementById(kVCardCloseButton).onclick = function() {
            };

...
        };

We can sort this out by using event delegation.

Let’s modify our code so that it delegates click events by looking at the event target:

(function(app, document) {
    'use strict';

    /**
     *
     */
    var me = app.EventController = {};

    /*
     * Common Elements
     */
    var kVCardButton      = 'vcard-volkan';
    var kVCardCloseButton = 'vcard-volkan-close';

    /*
     * Behavior Tier Stub
     */
    var page = app.PageController;

    /**
     *
     */
    me.bindEventHandlers = function() {

        document.onclick = function(evt) {
            var src = window.event ? window.event.srcElement : evt.target;

            switch (src.id) {
                case kVCardButton :
                    page.showVCard();

                    break;
                case kVCardCloseButton :
                    page.closeVCard();

                    break;
                default :

                    break;
            }

            return false;
        };
    };
}(this.VCardApp, this.document));

You can see where we currently are at this github snapshot.

Cross-Browser Event Handling

Now let’s change our event registration mechanism a bit:

(function(app, o2) {
    'use strict';

    // js/delegation/controller/event/eventcontroller.js

    /**
     *
     */
    var me = app.EventController = {};

    /*
     * Common Elements
     */
    var kVCardButton      = 'vcard-volkan';
    var kVCardCloseButton = 'vcard-volkan-close';

    /*
     * Commin Events
     */

    var kClick = 'click';

    /*
     * Behavior Tier Stub
     */
    var page = app.PageController;

    /*
     * Aliases
     */
    var listen         = o2.EventHandler.addEventListener;
    var getTarget      = o2.EventHandler.getTarget;
    var preventDefault = o2.EventHandler.preventDefault;

    /**
     *
     */
    me.bindEventHandlers = function() {

        listen(document, kClick, function(evt) {
            var src = getTarget(evt);

            if (!src) {
                return;
            }

            switch (src.id) {
                case kVCardButton:
                    preventDefault(evt);
                    page.showVCard();

                    break;
                case kVCardCloseButton:
                    preventDefault(evt);
                    page.closeVCard();

                    break;
                default:

                    break;
            }
        });
    };
}(this.VCardAPp, this.o2));

Use of Event Constants

You may have noticed that instead of using string literals for registering the click event like
listen(document, ‘click‘, function(evt) {“, we preferred to use a constant kClick variable.

Event constants provide an easy way to refer to specific event types. Using a constant instead of the String value helps us identify typographical errors more quickly. If we misspell a constant name in our code, the JavaScript parser will catch the mistake.

On the other hand, if we misspell the event string like “listen(document, ‘click1‘, function(evt) {“, the event handler will be registered for a type of event that will never be dispatched. And we’ll spend hours of your development time, trying to debug what went wrong.

In addition to that, assigning commonly-used strings to constants will help in minification and obfuscation of our JavaScript code.

Now let’s move our constants to proper configuration objects:

...
    'use strict';

    /**
     *
     */
    var me = app.EventController = {};

    /*
     * Common Elements
     */
    var cce = app.config.constants.element;
    var kVCardButton      = cce.BTN_VCARD;
    var kVCardCloseButton = cce.BTN_VCARD_CLOSE;
...

where js/config/constants/element.js would be:

(function(app) {
    'use strict';

    /*
     *
     */
    app.config = app.config || {};

    /*
     *
     */
    app.config.constants = {
        element : {
            BTN_VCARD       : 'vcard-volkan',
            BTN_VCARD_CLOSE : 'vcard-volkan-close'
        }
    };
}(this.VCardApp))

Event Callback Factory

Let’s modify the EventCallback class further, by adding a factory method:

(function(app, o2) {
    'use strict';

    // examples/vcardapp/js/delegation/callback/event/eventcallback.js

    /**
     *
     */
    var me = app.EventCallback = {};

    /*
     * Common Elements
     */
    var cce = app.config.constants.element;
    var kVCardButton      = cce.BTN_VCARD;
    var kVCardCloseButton = cce.BTN_VCARD_CLOSE;

    /*
     * Behavior Tier Stub
     */
    var page = app.PageController;

    /*
     * Aliases
     */
    var eh             = o2.EventHandler;
    var getTarget      = eh.getTarget;
    var preventDefault = eh.preventDefault;

    /**
     *
     */
    var CallbackFactory = {

        /**
         *
         */
        create : function(src) {
            switch (src.id) {
                case kVCardButton :
                    return page.showVCard;

                case kVCardCloseButton :
                    return page.closeVCard;

                default :
                    return null;
            }

            return null;
        }
    };

    /**
     *
     */
    me.document_click = function(evt) {
        var src = getTarget(evt);

        if (!src) {
            return;
        }

        var delegate = CallbackFactory.create(src);

        var shouldPreventDefault = !!delegate;

        if (shouldPreventDefault) {
            preventDefault(evt);
        }

        if (!delegate) {
            return;
        }

        var source = src;
        var eventArgs = {};

        delegate(source, eventArgs);
    };
}(this.VCardApp, this.o2));

As our application grows, this callback factory will grow as well, so it’s better to move it to its own file:

(function(app, o2) {
    'use strict';

    // examples/vcardapp/js/delegation/callback/event/eventcallback.js

    /**
     *
     */
    var me = app.EventCallback = {};

    /*
     * Aliases
     */
    var eh             = o2.EventHandler;
    var getTarget      = eh.getTarget;
    var preventDefault = eh.preventDefault;

    /*
     * Factory
     */
    var factory = app.EventCallbackFactory;

    /**
     *
     */
    me.document_click = function(evt) {
        var src = getTarget(evt);

        if (!src) {
            return;
        }

        var mixed = factory.create(src);
        var delegate = mixed.callback;
        var args = mixed.args;

        var shouldPreventDefault = !!delegate;

        if (shouldPreventDefault) {
            preventDefault(evt);
        }

        if (!delegate) {
            return;
        }

        delegate.apply(src, [src, args]);
    };
}(this.VCardApp, this.o2));

Where EventCallbackFactory is defined as:

(function(app) {
    'use strict';

    // examples/vcardapp/js/delegation/factory/callback/event/eventcallbackfactory.js

    /**
     *
     */
    var me = app.EventCallbackFactory = {};

    /*
     * Common Elements
     */
    var cce               = app.config.constants.element;
    var kVCardButton      = cce.BTN_VCARD;
    var kVCardCloseButton = cce.BTN_VCARD_CLOSE;

    /*
     * Page Callback
     */
    var pc                       = app.PageCallback;
    var callbacks                = {};
    callbacks[kVCardButton]      = {callback : pc.showVCard,  args: []};
    callbacks[kVCardCloseButton] = {callback : pc.closeVCard, args: []};

    var defaultCallback = {callback : null, args : null};

    /**
     *
     */
    me.create = function(src) {
        log('app.EventCallbackFactory.create');

        if (!src) {
            return defaultCallback;
        }

        return callbacks[src.id] || defaultCallback;
    };
}(this.VCardApp));

PageCallback

And the PageCallback class is as follows:

(function(app) {
    'use strict';

    /**
     *
     */
    var me = app.PageCallback = {};

    /*
     * Stubs to the Behavior Tier
     */
    var pc         = app.PageController;
    var showVCard  = pc.showVCard;
    var closeVCard = pc.closeVCard;

    /**
     *
     */
    me.showVCard = function() {
        showVCard();
    };

    /**
     *
     */
    me.closeVCard = function() {
        closeVCard();
    };
}(this.VCardApp));

What we’ve covered up till now is can be found at this github snapshot.

Dive Into the Communication Tier

AjaxController

Now let’s bind AJAX events, so that we can dynamically retrieve the VCard content:

(function(app) {
    'use strict';

    // examples/vcardapp/js/communication/controller/ajax/ajaxcontroller.js

    /**
     *
     */
    var me = app.AjaxController = {};

    /*
     * Config
     */

    var ac   = app.config;
    var acc  = ac.constants;

    var kUsername    = ac.serviceKey.USERNAME;

    var kCurrentUser = acc.user.USERNAME;

    /*
     * Proxy
     */
    var proxy = app.AjaxProxy;

    me.showVCard = function() {
        var params = {};

        params[kUsername] = kCurrentUser;

        proxy.sendShowVCardRequrest(params);
    };
}(this.VCardApp));

AjaxProxy

Where AjaxProxy is something like:

(function(app, o2) {
    'use strict';

    // examples/vcardapp/js/communication/proxy/ajax/ajaxproxy.js

    /**
     *
     */
    var me = app.AjaxProxy = {};

    /*
     * Aliases
     */
    var merge = o2.CollectionHelper.merge;
    var get   = o2.Ajax.get;

    /*
     * Callbacks
     */
    var cb                              = app.AjaxCallback;
    var handleShowVCardRequestComplete  = cb.handleShowVCardRequestComplete;
    var handleShowVCardRequestError     = cb.handleShowVCardRequestError;
    var handleShowVCardRequestException = cb.handleShowVCardRequestException;

    /*
     * Config
     */

    var ac  = app.config;
    var acc = ac.constants;

    /*
     * Api
     */
    var kApiUrl = acc.api.URL;

    /*
     * Parameters
     */
    var getVCardParams = ac.serviceParameter.getVCard;

    /**
     *
     */
    me.sendShowVCardRequrest = function(parameters) {
        var params = {};

        merge(params, getVCardParams);
        merge(params, parameters);

        get(kApiUrl, params, {
            oncomplete  : handleShowVCardRequestComplete,
            onerror     : handleShowVCardRequestError,
            onexception : handleShowVCardRequestException
        });
    };
}(this.VCardApp, this.o2));

AjaxCallback

And the AJAX callbacks are as follows:

(function(app) {
    'use strict';

    // examples/vcardapp/js/delegation/callback/ajax/ajaxcallback.js

    var me = app.AjaxCallback = {};

    /*
     * Stubs to the Behavior Tier
     */

    function renderVCardUi(result) {
        app.PageController.renderVCardUi(result);
    }

    /*
     * Logger
     */
    var log = app.Logger.log;

    /*
     *
     */
    var kEmpty        = '';
    var kErrorOccured = app.config.constants.template.TEMPORARY_ERROR;

    /**
     *
     */
    me.handleShowVCardRequestComplete = function(result) {
        log('app.AjaxCallback.handleShowVCardRequestComplete');

        if (!result) {
            return;
        }

        renderVCardUi(result);
    };

    /**
     *
     */
    me.handleShowVCardRequestError = function(status, statusText) {
        log(['app.AjaxCallback.handleShowVCardRequestError status: "',
            status ,'" statusText: "', statusText ,'".'
        ].join(kEmpty));

        renderVCardUi(kErrorOccured);
    };

    /**
     *
     */
    me.handleShowVCardRequestException = function(ex){
        log(['app.Ajaxcallback.handleShowVCardException message: "',
            ex.message ,'" ex: "', ex.toString() ,'".'
        ].join(kEmpty));

        renderVCardUi(kErrorOccured);
    };
}(this.VCardApp));

Logger

Let’s also defined a simple Logger class to log things when an error or an exception occurs:

(function(app) {
    'use strict';

    // examples/vcardapp/js/log/logger.js

    var me = app.Logger = app.Logger || {};

    var isProduction = app.config.constants.IS_PRODUCTION;

    if (!isProduction) {

        /**
         *
         */
        me.log = function(stuff) {
            if (window.console) {
                window.console.log(stuff);
            }
        }
    } else {
        me.log = function() {
        };
    }
}(this.VCardApp));

Final Touches

And let’s add some concluding retouches to the PageController:

(function(app) {
    'use strict';

    /**
     *
     */
    var me = app.PageController = {};

    /*
     * Loading...
     */
    var kProgress = app.config.constants.template.LOADING;

    /*
     * Stubs for the Presentation Tier
     */
    var close    = app.RenderController.closeVCard;
    var showCard = app.RenderController.showVCard;

    /*
     * Stubs for the Communication Tier
     */
    var show = app.AjaxController.showVCard;

    /*
     * Logger
     */
    var log = app.Logger.log;

    /*
     * Stubs for the Delegation Tier
     */
    var bindEventHandlers = function() {
        app.EventController.bindEventHandlers();
    };

    /**
     *
     */
    me.renderVCardUi = function(html) {
        log('app.PageController.renderVCardUi');

        showCard(html);
    };

    /*
     *
     */
    var renderVCardUi = me.renderVCardUi;

    /*
     *
     */
    function showLoading() {
        renderVCardUi(kProgress);
    }

    /**
     *
     */
    me.showVCard = function() {
        log('app.PageController.showVCard');

        showLoading();
        show();
    };

    /**
     *
     */
    me.closeVCard = function() {
        log('app.PageController.closeVCard');

        close();
    };

    /**
     *
     */
    me.init = function() {
        log('app.PageController.init');

        bindEventHandlers();
    }
}(this.VCardApp));

the End Result

Here’s how our app eventually looks like:

VCard App Preview

VCard App Preview

Message Sequence Between Tiers

After adding Logger.log statements to the public methods of every single object, we can easily observe the message flow between tiers.

Let’s have a deeper look at the cross-module messaging of our app:

Initial Bootstrap

Here’s the boostrapping event sequence:

# Bootstrap:
app.PageController.init                   <Behavior>
    app.EventController.bindEventHandlers <Delegation>
  • The PageController (in the Behavior Tier) calls bindEventHandlers method of the EventController (in the Delegation Tier).

Clicking “Show VCard” Button

Here’s what happens when we click the “show vcard” button:

# On "Show VCard" button click:
    # Find a delegate to execute:
    app.EventCallback.document_click     <Delegation>
        app.EventCallbackFactory.create  <Delegation>

    # Execute the delegate:
    app.PageCallback.showVCard       <Delegation>
        app.PageController.showVCard <Behavior>

            # Show loading:
            app.PageController.renderVCardUi    <Behavior>
                app.RenderController.showVCard  <Presentation>

            # Send AJAX request:
            app.AjaxController.showVCard            <Communication>
                app.AjaxProxy.sendShowVCardRequrest <Communication>

            # On AJAX response:
            app.AjaxCallback.handleShowVCardRequestComplete <Delegation>
                app.PageController.renderVCardUi            <Behavior>
                    app.RenderController.showVCard          <Presentation>
  • A registered callback in the Delegation Tier triggers showVCard action of the PageController (in the Behavior Tier);
  • Then PageController first calls the Presentation Tier to show a loading animation;
  • and then calls the AjaxController in the Communication Tier to send an AJAX request,
  • AjaxController delegates this action to AjaxProxy,
  • After a successful return from the AjaxProxy, AjaxCallback.handleShowVCardRequestComplete method on the Delegation Tier is triggered;
  • Then the Delegation Tier calls PageController in the Behavior Tier,
  • And PageController calls the RenderController in the Presentation Tier to render the final UI.

Clicking “Close” Button

The sequence of events after we click on the close VCard button, is also similar:

# On "Close VCard" button click:
    # Find a delegate to execute:
    app.EventCallback.document_click     <Delegation>
        app.EventCallbackFactory.create  <Delegation>

    # Execute the delegate:
        app.PageCallback.closeVCard             <Delegation>
            app.Pagecontroller.closeVCard       <Behavior>
                app.RenderController.closeVCard <Presentation>

A Picture is Worth a Thousand Words

We can summarize the above sequences with the message flow diagram below:

Messaging Sequence Between the 4 Tiers of Separation

Messaging Sequence Between the 4 Tiers of Separation

That’s All Folks

And that completes this part of the series. You can see what we’ve done so far at this github history snapshot.

Next Up?

In the next article of the series…

  • We’ll ehnance /vcardapp/people/volkan page with JavaScript;
  • We’ll be adding HTML5 local history support;
  • We’ll be adding offline-browsing support;
  • We’ll implement a pub-sub mechanism between tiers to decrease cross-module dependency;
  • We’ll minify CSS and JavaScript assets into one.

To sum up, we will polish our app by progressively enhancing it, and making it able to use modern HTML5 capabilities whenever the user agent supports them.

Until then, feel free to share your thoughts and suggestions in the comments section below :) .

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