Friday, February 24, 2012

Using knockout.mapping Plugin with Asp .Net MVC – Part 1a

In part 1 of this series I explored knockoutjs and completed a simple display view in the UI.

A few people have spoken to me about the implementation and commentated on what they perceived as a leakage into the Separation of Concerns regarding how bindings are managed via the data-bind attributes. While the comments certainly led to some interesting debates, I thought how can I remove some of those Concerns away from the UI.

Some research and I came across Neil Kerkin's blog post Exploring TodoMVC and knockout.js with unobtrusive bindings where he discusses an approach using knockout.js supports custom binding providers that allow us to refactor the code to achieve a bit more separation.

I won't go too much into what I have done because Neil explains it a lot better than I could. Therefore I'll just post the newer code base that will be used for part 2.

The javascript which looked like the following:

Code Snippet

(function (getworkouts, $, undefined) {    //create view model and initialise with default data.
    getworkouts.viewModel = function() {
        this.workouts = ko.mapping.fromJS(
                    [],
                    {
                        key: function (workout) { return ko.utils.unwrapObservable(workout.Id); }
                    });
                };    //use ajax call to populate the view model from the Action and also bootstraps knockout
    getworkouts.getDataFromSource = function () {
        $.ajax({
            type: 'POST',
            url: '/Home/GetWorkouts',
            dataType: 'json',
            success: function (data) {
                var model = new getworkouts.viewModel();
                model.workouts = ko.mapping.fromJS(data);
                ko.applyBindings(model);
            }
        });
    };
} (window.getworkouts = window.getworkouts || {}, jQuery));$(document).ready(function () {
    //bootstrap call
    getworkouts.getDataFromSource();
});


becomes

Code Snippet



/* File Created: February 5, 2;01;2 ;*/

(function (getworkouts, $, undefined) {

    window.setUpBindings.SetCustomBindings();

    //create view model and initialise with default data.

    getworkouts.viewModel = function () {

        this.workouts = ko.mapping.fromJS(

                    [],

                    {

                        key: function (workout) { return ko.utils.unwrapObservable(workout.Id); }

                    });

    };

    //use ajax call to populate the view model from the Action and also bootstraps knockout

    getworkouts.getDataFromSource = function () {

        $.ajax({

            type: 'POST',

            url: '/Home/GetWorkouts',

            dataType: 'json',

            success: function (data) {

                var model = new getworkouts.viewModel();

                model.workouts = ko.mapping.fromJS(data);

                getworkouts.bindings = {

                    workouts: { foreach: model.workouts },

                    workoutsDateTime: function () { return { text: this.DateTime }; },

                    workoutsWorkoutName: function () { return { text: this.WorkoutName }; }

                };

                //set ko's current bindingProvider equal to our new binding provider

                ko.bindingProvider.instance = new ko.customBindingProvider(getworkouts.bindings);

                ko.applyBindings(model);

            }

        });

    };

} (window.getworkouts = window.getworkouts || {}, jQuery));

$(document).ready(function () {

    //bootstrap

    getworkouts.getDataFromSource();

});

    



With an additional javascript snippet of

Code Snippet



/* File Created: February 16, 2012 */

(function (setupBindings, $, undefined) {

    setupBindings.SetCustomBindings = function() {

        ko.customBindingProvider = function(bindingObject) {

            this.bindingObject = bindingObject;

            //determine if an element has any bindings

            this.nodeHasBindings = function(node) {

                return node.getAttribute ? node.getAttribute("data-class") : false;

            };

            //return the bindings given a node and the bindingContext

            this.getBindings = function(node, bindingContext) {

                var result = { };

                var classes = node.getAttribute("data-class");

                if (classes) {

                    classes = classes.split(' ');

                    //evaluate each class, build a single object to return

                    for (var i = 0, j = classes.length; i < j; i++) {

                        var bindingAccessor = this.bindingObject[classes[i]];

                        if (bindingAccessor) {

                            var binding = typeof bindingAccessor == "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor;

                            ko.utils.extend(result, binding);

                        }

                    }

                }

                return result;

            };

        };

    };

} (window.setUpBindings = window.setUpBindings || {}, jQuery));



The view then becomes

Code Snippet



<tableclass="gridtable">

    <thead>

        <tr>

            <th>

                Date Of Workout

            </th>

            <th>

                Name Of Workout

            </th>

        </tr>

    </thead>

    <tbody data-class="workouts">

        <tr>

            <td>

                    <span data-class="workoutsDateTime"></span>

            </td>

            <td>

                    <span data-class="workoutsWorkoutName"></span>

            </td>

        </tr>

    </tbody>

</table>



Once again the code is available at Github.

I've rushed over the implementation mainly because I don't want to any credit away from Neil Kerkin's solution to the Separation of Concerns problem.

See you at part 2.

No comments:

Post a Comment