Search code examples
c#javascriptasp.net-mvcdecoupling

Seeking recommendations on how to decouple JS code from views


JavaScript plays an increasingly important role in most web solutions, but I find that it is much harder to decouple the JS code from the view specifics than it is for the server-side code.

What techniques are people using to decouple the JS code, in order to reduce the maintenance burden and to make it as resilient to minor view changes as possible?

To provide a concrete example, I have a view that looks sort of like the following:

<div id="signin" class="auth">
    <h2>Sign in</h2>
    <div id="resultbox" class="box" style="display: none;"></div>

    <div class="form">
        <p>Required fields are marked <span class="yellow">*</span></p>
        <form action="@Url.Action( MVC.Account.Authenticate() )" method="post" id="authform">
            <label for="Identification">Alias or email <span class="yellow">*</span></label></p>
            @Html.TextBoxFor( m => m.Identification, new { size="22", tabindex="1" } )

            <label for="Password">Password <span class="yellow">*</span></label></p>
            @Html.PasswordFor( m => m.Password, new { size="22", tabindex="2" } )

            <a href="#" class="but_styled" id="btn_signin" tabindex="3">Sign in</a>
        </form>
    </div>
</div>

The JS part is split into two files - the first is my "business logic" class and the second is used mostly for wiring:

(function (pp, $, undefined) {
    pp.account = {};

    function hideResultBox() {
        var box = $('#resultbox');
        box.hide();
        box.removeClass('info_box').removeClass('error_box');
    }
    function showResultBox(result) {
        var box = $('#resultbox');
        if (result.Success === true) {
            $('#resultbox_content').html(result.Message);
            box.addClass('info_box').show();
        } else {
            $('#resultbox_content').html(result.Message);
            box.addClass('error_box').show();
            var messages = '';
            for (var error in result.Errors) {
                messages += '<li>' + result.Errors[error] + '</li>';
            }
            if (messages !== '')
                $('#resultbox_content').append('<br /><ul>' + messages + '</ul>');
        }
    }

    pp.account.authenticate = function (data) {
        hideResultBox();
        $.post('/account/authenticate', data, function (result) {
            showResultBox(result);
            if (result.Success === true) {
                setTimeout(function () { window.location = result.Url; }, 1000);
            }
        });
    };
})(window.pressplay = window.pressplay || {}, jQuery);

And the wiring part:

$(document).ready(function () {
    $('#signin a').live('click', function () {
        var form = $(this).closest('form').serialize();
        pressplay.account.authenticate(form);
        return false;
    });
});

The problem with the code above is how closely it is tied to how the view looks (element ids that must be present, structure, etc.), but I have no good ideas on how it could be improved.

It just seems to me that, if I continue down this path, the JS files end up being a mess of properly encapsulated logic combined with all sorts of view-specific stuff.

Is this the best I can hope to do or are there any techniques I can apply to avoid some of this mess?

My own ideas on how to improve this revolve around building some sort of server-side generated "element selectors" JS class, such that I could write the JS without using quite as many string references to classes and element ids. But I am not sure how one would go about generating that or whether it'll just be worse to maintain in the end.


Solution

  • Some thoughts:

    • Don't base functionality on element "id" values, except for view features that really do involve elements that are "naturally" unique. Use classes instead whenever possible.
    • Use "data-" attributes to communicate feature configuration information from HTML to JavaScript. (But don't put code in your "data-" attributes; in my opinion that's a really terrible idea. Sorry about that, fans of Knockout. To each his own.)
    • Use an event pub/sub system to communicate between feature "bundles" to keep interdependencies under control.
    • Use <body> classes to characterize different sorts of pages in order to increase the efficiency of your "wiring". That way, feature code can tell very quickly whether it needs to be even considered for a given page.

    edit — to clarify the last one: let's say you've got some features that have to do with <form> pages, like date pickers, or auto-complete inputs, or other things like that. Sometimes there are features that only make sense for certain kinds of forms. In such cases, using a body class can make it simple to figure out whether a feature should bother to look around the DOM for elements to affect:

    if ($('body').is('.password-form')) { 
      // perform specific initializations
    }
    

    The web application I'm currently involved with isn't too big, but it's not trivial either. I find that I can keep just one big collection of JavaScript for the entire site and include it identically on every page, and I don't have any performance concerns (currently) (well IE7 is slow, but this technique is not the cause of that).