better software through user-centered design

There are several approaches to organizing code in any application.  With the Model View Controller (MVC) pattern, much of the code is organized for you by convention alone, specially if you are using ASP.Net MVC.

In an effort to follow the conventions and patters set forth by MVC, I started thinking about how to pull the JavaScript out of my views, that is, I want my JS code organized in a pattern satisfactory with the file structure of MVC.  One thing I love about MVC, is that when I look at a controller, I know almost exactly where to look to find the view, and vice versa.  The Solution Explorer screenshot shows my take on how to organize JS code in such a matter.

JS Namespace and Class Setup

The App.js initializes the namespacing and classing that will be used to organize the client-side, and more importantly prevents that pesky global namespace from becoming cluttered.  It sounds pretty hard core talking about namespacing and classing on the client-side right?  It is.  It is highly technical and complex to….ok well maybe it is just a series of objects, that looks and feels like C#’s namespacing/classing at runtime. Note that this is accessed using the dot notation (MYAPPLICATION.App.Ajax.PostForm();).  In this case I split the “App” code away from the “View” code.

 I chose to do this simply because that is how the C# project namespacing was setup in the project structure.

The Ajax, Form, and Utilities classes are just infrastructure code that I use to post forms, create pdf’s, override default behavior using prototype, setup default jQuery UI functionality, etc.  Read more about submitting MVC forms using jQuery if you dare.

MYAPPLICATION.Views{} is simply an object that allows me to organize my JS code similar to how the views are generated.  There are two advantages I have found that instantly save me time; 1) I can look at a view in the VS Solution Explorer and know exactly where to find the associated JS, and 2) I can look at a url route and know exactly where the JS for that page lives.   Another big benefit is it helps keep the JS out of the view.  This leaves only 1 line of code in the view itself, which is required to instantiate the JS for said view:

<script>
    MYAPPLICATION.Views.History.Index.Init({ orderDetailGridUrl: '@Url.Action("OrderDetailGrid", "History", null)' });
</script>

Note that if you use Firebug (which you should be), it makes debugging a bunch easier as well because you can select the appropriate script file (which Firebug organizes by Controller thanks to our file structure) versus scrolling through the markup on the script tab and trying to find the right line, YUCK.

 Then all the History code lives in one file, and is loaded after the MYAPPLICATION.App{} code so it can utilize all utilities and ajax goodness already written.

MYAPPLICATION.Views.History = {
    Index: {
        Init: function (options) {
            // History index page code goes here...

        }, ///end Index.Init()
    }
};

So far there have been no drawbacks to this approach because you can still use other JS patterns that work well with this setup.  I have used the JS module pattern in other applications and instantiated them the same way as shown here.

Advertisements

Quote for Today

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”
-Martin Fowler et al, Refactoring: Improving the Design of Existing Code (p15), 1999

I just downloaded a new version of Windows Server 2008 R2 64-bit and to my dismay, Dreamspark shells the image out as a .img file instead of the typical and beloved .iso. I looked online to find out what the heck to do with the .img file and found a bunch of sites explaining how to run a command line program (VBoxManage) to convert the .img into a .vdi.  The conversion went well but the .vdi did not work when I tried to mount and start the VM to boot to it.

To my delight, I then tried renaming the .img to have an extension of .iso and SNAP!  It works.  Mounted up the .iso in typical fashion and Server 2008 is now running on my MacBook Pro.  I understand Ubuntu is handing out .img files instead of .iso as well.  Must be some better compression value or the like (the Server 2008 R2 Standard/Enterprise/Web/Datacenter all in one was only 3GB).

Great Quote

The right place to put any given unit of code is where it provides the most value and reliability with the least cost and load on the system.
-David Wendelken

Saw this in a forum post, and thought it was a great quote.

MVC3’s model binding support is fantastic! The software our team develops is search/results based so staying on a single page to display a grid is important. There are many jQuery based element serializers, but I wrote my own because I wanted something extremely basic and simple and hey, why not since it is so easy! Before we look at the serializer, let’s look at how we use jQuery to submit our form so the Post does not render a new page.

jQuery Form Post (link to code example)

Now that we bound our function to the form submit event, we can serialize our form. The serializer function is very basic but works great. It uses the jquery data() to include stored data to bind to the model. This can be really handy for non-form elements and custom client-side functionality. Basically any information you want to head back to the server, just add like so:

$("#myForm").data("ToPost", { SelectedGridIds: [1, 14, 922] });

The serializer function will automatically add the “ToPost” data objects to the JSON object to be returned to the server.

jQuery Form Serializer (link to code example)

Now my Post function has stopped default event propagation, and serialized the data I need, now we just need to post it. You can use your favorite ajax post method, but I prefer to use the wrapper function I created. It uses jQuery do make the actual post (which I also prefer).

Implementation:

First, we need a quick little jQuery plugin to manage all our “Form” operations:
//Creates a jQuery accessor for NRPC.Form object.
//Example: $('form').Form('Post', {options});
// Decorate each class with the jQuery self-executing function to prevent IE from blowing up in debug mode.
(function ($) { 

$.fn.Form = function (method) {
if (MyNamespace.Form[method]) {
return MyNamespace.Form[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return MyNamespace.Form.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on MyNamespace.Form');
}
};

})(jQuery);

Sweet, now when the page loads, all we have to do is register the form post with $(“form”).Form(‘Post’);.

Here is a real-world implementation:

$("form#Search").Form('Post', {
targetId: 'divGrid',
onAjaxSuccess: function (resultGridHtml) {
var gridResult = $(resultGridHtml)[0];
$("#divGrid").html(gridResult);
}
});

This is a simple wrapper for keeping consistency in ajax calls. This happens to use jQuery ajax but the key to the wrapper is that the internal functionality can be re-written or a new library utilized across the board. I have always liked this idea but now cherish it due to recent dealings with ExtJS and their complete lack of backwards compatibility on every release.

MyNamespace.Ajax = {
//Setup ajax default properties
Defaults: {
type: ‘POST’,
url: ”,
contentType: ‘application/json; charset=utf-8’,
dataType: ‘json’,
success: function (result) { },
error: function () { }
},

// Global Post for any Ajax calls.
// This is meant to be a wrapper for the jQuery $.ajax method and should not be circumvented without great reason.
Post: function (options) {
options = $.extend(this.Defaults, options);
//Serialize the json object if it has not already been done.
if (options.data && typeof (options.data) == “object”) {
options.data = JSON.stringify(options.data);
}

$.ajax({
type: options.type,
url: options.url,
contentType: options.contentType,
dataType: options.dataType,
data: options.data,
success: function (result) {
if (options.success) {
options.success.apply(this, Array.prototype.slice.call(arguments));
}
},
complete: function (request, status) {
if (options.complete) {
options.complete.apply(this, Array.prototype.slice.call(arguments));
}
},
error: function (request, typeText) {
MyNamespace.Utilities.LogError(request.responseText);
if (options.error) {
options.error.apply(this, Array.prototype.slice.call(arguments));
}
}
});
}
};

This is a wrapper for submitting forms. I use this with ASP.Net MVC and specifically with MVC3 because the serialized data will automatically bind to the model.
Post Dependencies: JSON2.js by Douglas Crockford, Serialize Function by me, Ajax Wrapper by me

/// Posts existing form via ajax. Automagically binds your form elements to the view model.
Post: function (options) {

var defaults = {

onSubmit: null, /// function to run before default validation occurs (used for custom validation)
onAjaxSuccess: null, /// function to run when ajax returns successful
onAjaxComplete: null, /// function to run on ajax completion
onAjaxError: null, /// function to run when ajax returns error
targetId: '' /// id of the element to dump the results in

}, json = {}, formObject = $(this);

this.submit(function (event) {

try {
/// Prevent form from submitting
event.preventDefault();

/// Use the OnSubmit even to run any custom validations you may have.
if (options.onSubmit) {
var onSubmitResult = options.onSubmit.apply(this, Array.prototype.slice.call(arguments));
if (onSubmitResult !== undefined && onSubmitResult === false) {
return onSubmitResult;
}
}

/// Check if form is valid and stop submit if it is not.
if (!$(this).valid()) { return false; }

if (options.targetId && !$("#" + options.targetId).is(":empty")) {
$("#" + options.targetId).empty();
}

/// Serialize form elements and associated data options
json = formObject.Form('Serialize', options);

MyNamespace.Ajax.Post({
url: formObject.attr('action'),
type: formObject.attr('method'),
dataType: 'html',
data: JSON.stringify(json),
success: function (result) {
try {
if (options.onAjaxSuccess) {
options.onAjaxSuccess.apply(this, Array.prototype.slice.call(arguments));
}
} catch (error) {
MyNamespace.Utilities.LogError(error);
}
},
complete: function (XMLHttpRequest, textStatus) {
try {
if (options.onAjaxComplete) {
options.onAjaxComplete.apply(this, Array.prototype.slice.call(arguments));
}
} catch (error) {
MyNamespace.Utilities.LogError(error);
}
},
error: function(error){
MyNamespace.Utilities.LogError(error);
try {
if (options.onAjaxError) {
options.onAjaxError.apply(this, Array.prototype.slice.call(arguments));
}
} catch(error){
MyNamespace.Utilities.LogError(error);
}
}
});

} catch (error) {
MyNamespace.Utilities.LogError(error);
}

});
return this;

} //end Post

%d bloggers like this: