Archive

Posts Tagged ‘Knockout.js’

Knockout and Custom Binding Providers

September 30, 2012 Leave a comment

Since I have such a strong background with XAML technologies, Knockout.js has been a natural fit for my development style. I really like the ability to program in a MVVM approach. Recently, I watched Ryan Neimeyer’s session he gave at the That Conference and I was really impressed with being able to create custom binding providers for Knockout.

When developing applications in XAML, I have always loved using Caliburn.Micro as I found Rob Eisenberg’s implementation of convention over configuration to be a great time saver and value add when trying to build applications quickly. Well, with that said, I wanted to create a simple version that would facilitate using conventions but also allow you to provide your own data-bind statements when you wanted to as well.

Before we jump over to my sample on jsFiddle, I wanted to walk you through the simple rules that I have put in place. First of all, I wanted my code to be a minimalistic as possible.

I tried using the name attribute of an element but it turns out that not all DOM children expose the name attribute. With that, I decided to go with use the data-name attribute that is supported with HTML5.

The following link is a sample test page on jsFiddle that demonstrates my solution.

I will walk you through the code required to build my custom binding provider for Knockout.js.

//knockout-conventionBindingProvider v0.0.11 | (c) 2012 Matt Duffield | http://www.opensource.org/licenses/mit-license
!(function (factory) {
    //AMD
    if (typeof define === "function" && define.amd) {
        define(["knockout", "exports"], factory);
        //normal script tag
    } else {
        factory(ko);
    }
}(function(ko, exports, undefined) {
    //a bindingProvider that uses something different than data-bind attributes
    //  options - is an object that can include "attribute" and "valueUpdate" options
    //    "attribute"       - this is the name of the attribute we are trying to check for binding
    //    "valueUpdate"     - this is the binding event that is fired, e.g. change, keyup, keypress, afterkeydown
    //    "anchorTypes"     - represents binding to an A tag
    //    "buttonTypes"     - represents binding to button, submit tags
    //    "checkedTypes"    - represents binding to checkbox and radio tags
    //    "imageTypes"      - represents binding to an IMG tag
    //    "textTypes"       - represents binding to EM, H1, H2, H3, H4, H5, H6, SPAN, STRONG tags
    //    "valueTypes"      - represents binding to password, text, textarea tags
    var conventionBindingProvider = function (options) {
        this.existingProvider = new ko.bindingProvider();

        options = options || {};

        // attribute used for our binding convention.  Default:  "data-name"
        this.attribute = options.attribute || "data-name";

        // defines which browser event KO will use to detect changes. Default: 'change'
        this.valueUpdate = options.valueUpdate || 'change';

        this.anchorTypes = options.buttonTypes || ['A'];
        this.buttonTypes = options.buttonTypes || ['button', 'submit'];
        this.checkedTypes = options.checkedTypes || ['checkbox', 'radio'];
        this.imageTypes = options.imageTypes || ['IMG'];
        this.textTypes = options.textTypes || ['EM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN', 'STRONG'];
        this.valueTypes = options.valueTypes || ['password', 'text', 'textarea'];

        //determine if an element has any bindings
        this.nodeHasBindings = function (node) {
            if (node.getAttribute && node.getAttribute("data-bind")) {
                return this.existingProvider.nodeHasBindings(node);
            }
            return node.getAttribute ? node.getAttribute(this.attribute) || node.getAttribute("data-focus") : false;
        };

        //return the bindings given a node and the bindingContext
        this.getBindings = function getBindings(node, bindingContext) {
            var result = {};

            // If a binding is already setup, use it.
            if (node.getAttribute && node.getAttribute("data-bind")) {
                result = this.existingProvider.getBindings(node, bindingContext);
            } else {
                var item = node.getAttribute(this.attribute);
                if (item) {
                    //var desc = node.id + " (" + node.nodeName + " - " + node.type + ")";
                    var bindingAccessor = this.mapBindings(node, item, bindingContext);
                    if (bindingAccessor) {
                        var binding = typeof bindingAccessor == "function" ?
                        bindingAccessor.call(bindingContext.$data) : bindingAccessor;
                        ko.utils.extend(result, binding);
                    }
                }
            }

            // Apply focus() on the node containing the data-focus attribute.
            item = node.getAttribute("data-focus");
            if (item != null) {
                node.focus();
            }

            return result;
        };

        this.mapBindings = function (node, bindingName, bindingContext) {
            var obj = {};
            //valueUpdate: 'change'
            obj["valueUpdate"] = this.valueUpdate;

            if (this.isBinding(node.nodeName, this.anchorTypes)) {
                return function () {
                    //click: run
                    obj["click"] = bindingContext.$data[bindingName];
                    return obj;
                };
            }
            else if (this.isBinding(node.type, this.buttonTypes)) {
                return function () {
                    var bindingCanName = "can" + bindingName;
                    //click: run
                    //enable: canrun
                    obj["click"] = bindingContext.$data[bindingName];
                    if (bindingContext.$data[bindingCanName] != null) {
                        obj["enable"] = bindingContext.$data[bindingCanName];
                    }
                    return obj;
                };
            }
            else if (this.isBinding(node.type, this.checkedTypes)) {
                return function () {
                    //checked: isActive
                    obj["checked"] = bindingContext.$data[bindingName];
                    return obj;
                };
            }
            else if (this.isBinding(node.type, this.valueTypes)) {
                return function () {
                    //value: firstName
                    obj["value"] = bindingContext.$data[bindingName];
                    return obj;
                };
            }
            else if (this.isBinding(node.nodeName, this.textTypes)) {
                return function () {
                    //text: title
                    obj["text"] = bindingContext.$data[bindingName];
                    return obj;
                };
            }
            else if (this.isBinding(node.nodeName, this.imageTypes)) {
                return function () {
                    //attr: { src: imagePath };
                    obj["attr"] = {};
                    obj["attr"]["src"] = bindingContext.$data[bindingName];
                    return obj;
                };
            }

            return null;
        };

        this.isBinding = function (value, types) {
            if ($.inArray(value, types) > -1) {
                return true;
            }
            else {
                return false;
            }
        };

    };

    if (!exports) {
        ko.conventionBindingProvider = conventionBindingProvider;
    }

    return conventionBindingProvider;
}));

The first thing you might notice is that I am using Asynchronous Module Definition (AMD) with Require.js in my definition of my custom binidng provider. In the definition of my conventionBindingProvider, you will see that it has only one parameter. I am using a single options parameter that allows the user to define the options behavior for this provider.  Although the default behavior for these options should handle most of the scenarios you want, I like to have the ability to provide my own changes by just passing in an options parameter.

The attribute parameter defaults to data-name but you change this to any attribute you choose. Please note that not all attributes are the same. I wrote above that I originally tried to use the name attribute and ran into issues that not all elements have this attribute. As with Knockout’s default binding using data-bind, I chose to use data-name for my convention.

Next, you have the valueUpdate parameter that defaults to change event. You can use keyup to allow for changes to be triggered when the key is released.

The other option parameters are used for defining the elements for which this binding provider supports. You don’t need to override this but it does give you the flexibility to extend the default behavior without actually modifying the JavaScript file.

When creating a custom binding provider for Knockout, you need to implement two functions: nodeHasBindings and getBindings.

The nodeHasBindings function is used to determine if the corresponding node has the custom binding we with to use. Since we want to support the standard data-bind attribute we return existing binding provider which is the standard provider. Next, we check for the existence of the attribute we are using in the provider. We also check for a hard-coded attribute called, data-focus.

The getBindings function does the same things as the previous function, by checking to see if we want to pass the standard binding attribute to the existing provider. After this, we call a helper function mapBindings which does the heavy lifting as far as creating the binding expression. Finally, we check to see if an data-focus attribute exists. If it does, we call the focus function on the node. This is a nice convention in that it allows us to define what element we want to set focus.

Let’s take a look at the mapBindings function. This function basically creates the binding statement for Knockout. We start with applying the valueUpdate binding.

Next we start evaluating all of the nodes we are going to support with this convention. For the anchor types, we simply create a click binding.

The button types follows this same convention but also creates a binding on enable property if a function exists with a prefix of ‘can’.

The next three conditions are straight forward and simply bind against the checked, value and text.

The last binding targets the image tag. This one is a little special in that it sets the binding against the attr and then the src attribute.

With each of these condition checks we see that we are using another helper function called, isBinding. This function takes a string and one of the arrays representing the elements. This function also uses a helper jQuery function inArray to determine if the string value is contained in the array.

Finally, the provider sets the conventionBindingProvider on the Knockout object.

All you need to do is reference this JavaScript file and then add the following code to your viewmodel:

 var ViewModel = function () {
    this.firstName = ko.observable("Matthew");
    this.middleName = ko.observable("Kevin");
    this.lastName = ko.observable("Duffield");
    this.password = ko.observable("");
    this.isActive = ko.observable(false);
    this.canrun = ko.computed(function () {
        return this.isActive();
    }, this);
    this.run = function () {
        alert("You hit run!");
    };
};

var options = { attribute: "data-name", valueUpdate: 'keyup' };
//tell Knockout that we want to use an alternate binding provider
ko.bindingProvider.instance = new ko.conventionBindingProvider(options);

ko.applyBindings(new ViewModel());​

The source for this provider is located here.

Play with the sample I created on jsFiddle and change the options parameter.

Hope this helps…

Advertisements

Knockoutjs and Convention Over Configuration

September 30, 2012 1 comment

I am a big fan of getting rid of as much ceremony as possible in my development life-cycle. I also really like the paradigm for rich databinding that we were able to do in XAML technologies. Here comes Knockout.js giving us the same richness of declarative data-binding. Knockout.js is very nice but still requires a bit of scaffolding to get our markup data bound correctly.

Here is a sample of standard Knockout.js markup:

<span>First Name:</span>
<input data-bind="value: firstName" /><br />
<span>Middle Name:</span>
<input data-bind="value: middleName" type="text" /><br />
<span>Last Name:</span>
<input data-bind="value: lastName" /><br />
<span>Password:</span>
<input data-bind="value: password" type="password" /><br />
<span>Is Active:</span>
<input data-bind="checked: isActive" type="checkbox" /><br />
<button data-bind="click: run">Run</button>

The following is the same markup but now using a using a simple convention:

<span>First Name:</span>
<input data-name="firstName" /><br />
<span>Middle Name:</span>
<input data-name="middleName" /><br />
<span>Last Name:</span>
<input data-name="lastName" /><br />
<span>Password:</span>
<input data-name="password" type="password" /><br />
<span>Is Active:</span>
<input data-name="isActive" type="checkbox" /><br />
<button data-name="run">Run</button>

Now, this may not seem like a very big deal but I would say that the less markup I have to write the better. One nice thing about this approach is that it will always honor data-bind markup first and only fallback to the convention when no standard binding exists.

Here is the output for the second markup:

If you notice carefully, you will see that the button is disabled. This is because the enable property is tied to the Is Active checkbox. You can see how the button is enabled when the Is Active checkbox is checked:

Finally, here is the viewmodel that we were bound to:

var ViewModel = function () {
    var self = this;

    self.firstName = ko.observable("Matthew");
    self.middleName = ko.observable("Kevin");
    self.lastName = ko.observable("Duffield");
    self.password = ko.observable("");
    self.isActive = ko.observable(false);

    self.canrun = ko.computed(function () {
        return self.isActive();
    }, self);
    self.run = function () {
        alert("You hit run!");
    };
};

As you can see, the convention basically allows us to take the value from the data-name attribute and find the corresponding Knockout observable on the viewmodel. We also have a default convention that takes the value from the data-name attribute and finds a corresponding function with the same name. The convention also pre-pends the word “can” to the value from the data-name attribute and binds the enable property of the button to a function with that name if it exists.

In the next post, I will walk you through building this custom convention binding provider for Knockout.

Carolina Code Camp 2012

I had a great time speaking at our annual Carolina Code Camp this year! We had a great turn out. I spoke on the following two topics:

Compiler as a Service – Introducing Roslyn

Have you ever wanted to perform dynamic eval operations C# just like you can in JavaScript or Ruby? Well, look no further! With Roslyn we have to power to build our own Read-Evaluate-Print-Loop (REPL) engine. We will also take a look at how you can use Roslyn to build your own template engine like Razor.

Client Side JavaScript Applications a.k.a. Single Page Applications

In this session we are going to take a look at Knockout.js, Infuser.js, Path.js, and a whole new approach for building client-side JavaScript applications. We will look at the overall architecture and see what we can do to manage the whole development process. One area of importance is loading HTML view templates on demand instead of the traditional Script reference. We will also address some limitations like Search Engine Optimization (SEO) that aren’t supported out of the box.

I had a great time and appreciate all who attended my sessions.