jones.busy

technical musings of a caffeine converter

NAVIGATION - SEARCH

Separating responsibility client-side in MVC with RequireJs and RMP

I my previous post, I talked about splitting my Knockout view model from the rest of my JavaScript by using employing the  Revealing Module Pattern to my code. I mentioned at the end of the post that I could take it one step further and split the code into separate script files then use something like RequireJs to organise the dependencies. It turned out to be a bit more fiddly than I expected so here’s my experience in the hope that it may point other newbies in this area in the right direction (or indeed, prompt any JS veterans to point out my mistakes)

Firstly, I want to tackle the important issue of “why do it if it’s complicated?”. Granted, what I had before worked but, as with most of my development, my code is an evolutionary product and whilst I always aim for it to be complete, I always work under the expectation that either I am going to have to come back here to make further improvements or, more importantly, someone else might. With that in mind, whilst there may seem to be an unjustified overhead initially in spending time introducing another library and re-organising existing, working code, I do believe that it’s one worth paying. Secondly, it’s mostly only complicated because of the learning curve and that’s really a one-off that can benefit in the long term elsewhere.

I had already tidied up my ‘viewmodel’ script by separating out the various areas of concern into three different modules: viewmodel, view and what I referred as rendering which was responsible for manipulating the view based on user interactions and start up defaults. These modules were contained in a separate file leaving only the following script inside the actual view:

   1: @section Scripts



   2: {



   3:     <script src="~/scripts/app/page.details.js"></script>



   4:     <script type="text/javascript">



   5:         (function () {



   6:             var viewModel = details.initialiseViewModel(ko.mapping.fromJS(@Html.Raw(Json.Encode(Model))));



   7:             detailsView.initialiseView(viewModel);



   8:         })();



   9:     </script>



  10: }

The next step was about separating out the modules into individual script files and managing the dependencies between them. Before I did this, I revisited the responsibility question of each module and decided that I wanted to make some changes.

I wanted the viewmodel module solely responsible for view data, computed data and commands, but clean of actual view components (ids, classes etc.) etc. – those should be handled in the view module.

The rendering module was bugging me as it shared some of the view module’s responsibility. What I actually was missing was a module that acts as a sort of controller so I decide to clean up the rendering module and allow the view module to handle click events and manipulation of view model data and introduce a dataService module, solely responsible for conversing with a remote service. This did not need to be defined per page though so I created this under the RMP pattern within my scripts/apps folder as I wanted it initialised on startup.

What I ended up with is shown below – basically MVVM without the Model as we already have a domain model on the back end which is then converted into DTOs for the front end so I did not need another model for this. A Model can also act as the DAL but I don’t like this coupled approach and all of my DA goes through multiple layers leaving my web app completely clean of the DA technology.

image

The view model module declares the properties and functions I want my view to be able to bind to. I see the functions acting like the Command pattern does in WPF and Silverlight so where I need to talk to the data service, I use the following knockout notation to go through the view model:

   1: data-bind="click: deleteCompanyCommand"

Where I am only manipulating the view, I am using plain old event handling in my view module to control this:

   1: $("#createCompanyBtn").on("click", initialiseCreateCompanyDialog);

Both the View and ViewModel modules are hooked up via the Require library with the following notation. Note I opted to make the View dependent on the ViewModel module:

ViewModel:

   1: define(function () {



   2:



   3:     var initialiseViewModel = function (data) {



   4:     ...



   5:     return {



   6:         initialiseViewModel: initialiseViewModel



   7:     };



   8: });

View:

   1: define(['pageScripts/viewModel'], function (viewModel) {



   2:     var viewModel,



   3:     ...



   4:     initialiseView = function (data) {



   5:         viewModel = viewModel.initialiseViewModel(data);



   6:         viewSubscriptions();



   7:         wireEvents();



   8:     }



   9:



  10:     return {



  11:         initialiseView: initialiseView



  12:     };



  13: });

Then, inside the html, I have the following script:

   1: @section Scripts



   2: {



   3:     <script type="text/javascript">



   4:



   5:         require.config({



   6:             baseUrl: "/scripts",



   7:             paths: {



   8:                 pageScripts: "views/index"



   9:             }



  10:         });



  11:



  12:         require(['pageScripts/view'], function(view) {



  13:             view.initialiseView(ko.mapping.fromJS(@Html.Raw(Json.Encode(Model))));



  14:         });



  15:     </script>



  16: }

The config section sets up my base url and then provides me with an alias to easily refer to my scripts for that particular page – remember, i don’t want these scripts being loaded elsewhere, only for this page. The require loads in the dependency for my View which, in turn, has already declared its dependency on the view model.

I did find that trying to use explicit or relative paths inside the require seemed to result in undefined dependencies further down the chain but I would recommend setting up those paths for cleanliness in any case.

I also have the option of using the Require Minimiser to negate the impact of needing to load multiple resources as this will combine and minify all my script into one resource for download. Very nice indeed :)

I’ve found this whole app an interesting challenge in determining which web technologies, in particular, 3rd party javascript/ css packages work well together. I certainly found the using Twitter Bootstrap, which its attribute binding approach fits in nicely with Knockouts MVVM approach and both have enabled me to take a clean, separated line of attack when managing my javascript. Coming from a C# and Xaml background, that’s a really pleasant and familiar feel to how I like to code.