0 Comments

Introduction

I've been using Knockout JS for quite a few years and it's a great that library that 'helps you to create rich, responsive display and editor user interfaces with a clean underlying data model.'[1] However, one thing I really dislike is the use of the data-bind attribute (see Figure 1), for three reasons:

  1. It mixes markup and code - databinding shouldn't be expressed in markup, in my opinion (separation of concerns);
  2. It bloats the markup; and
  3. It can be a laborious task, adding all of the bindings.
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"></span>!</h2>
Figure 1

Wouldn't it be easier and cleaner if Knockout JS was able to bind automatically? For example:

<p>First name: <input name="firstName" /></p>
<p>Last name: <input name="lastName" /></p>
<h2>Hello, <span id="fullName"></span>!</h2>
Figure 2

The knockout.unobtrusiveBindingProvider

"knockout.unobtrusiveBindingProvider is an unobtrusive, convention-based binding provider for Knockout JS that enables a clean separation of HTML and Knockout databindings."[2]

How does it work? As Knockout traverses the DOM, the unobtrusiveBindingProvider analyses each HTML element and:

  1. Using the id, name (for <input> or <select> tags) or a class name, attempts to map to a member of the current $data object; and
  2. Based on the HTML element type and the type of the member, determines what binding should be used (see Table 1).
HTML elementMember typeBinding
*Number, Stringtext
*Booleanvisible
*Object, Observablewith
*Array, ObservableArrayforeach
*Functionclick
inputNumber, String, Observablevalue
input[type="checkbox|radio"]Booleanchecked
selectObject, Number, String, Observablevalue
selectArray, ObservableArrayselectedOptions
Table 1
  • If the appropriate member isn't found, in the current context, the unobtrusiveBindingProvider will attempt to find one by bubbling up through the parent contexts.
  • Where an element has multiple classes and a member hasn't already been mapped, the unobtrusiveBindingProvider will attempt to map each class until a member has been found.
  • If the target (id, name or class) is hyphonated and a member hasn't already been mapped, then the unobtrusiveBindingProvider will regard this as a path to a member in the object graph and will attempt to navigate through the object graph until it reaches the destination member. However, if any part of the hyphonated target doesn't match a path in the object graph, the mapping will be terminated. If any part of the path is an observable then the parentheses should be omitted.

But what happens if we don't want to use the binding that is mapped in Table 1?

The binding extender

In the scenario where you need to use a different binding the unobtrusiveBindingProvider includes a binding extender (see Figure 3), which will override the mapping.

this.firstName = ko.observable("Bob").extend({ binding: "textInput" });
Figure 3

The bindings extender

Quite often we need to declare additional bindings. The unobtrusiveBindingProvider enables this through the use of the bindings extender. For example:

<select name="hairColour"></select>

this.hairColours = ko.observableArray(["Black","Blonde","Brunette","Grey","Red","White"]);
this.hairColour = ko.observable("Blonde").extend({ bindings: "options:hairColours" });
Figure 4

Binding Arrays and Functions

If the member of the model is an array or function then the ko.utils.extend method can be used to add bindings. For example:

<button id="sayHello">Say Hello</button>

this.hairColours = ko.utils.extend([...], { bindings: "attr:{title:'colours'}" });
this.sayHello = ko.utils.extend(function(){...}, { bindings: "enable:firstName() && lastName()" });
Figure 5

The binding of a function can be overridden with an event name. For example:

this.sayHello = ko.utils.extend(function(){...}, { binding: "mouseover" });
Figure 6

The ignore extender

Where the id, name or a class matches a member but the member shouldn't be bound to the view, the unobtrusiveBindingProvider includes an ignore extender. For example:

this.fullName = ko.pureComputed(...).extend({ ignore: true });
Figure 7

The ko.bindings property

Views will often declare bindings that don't map to a member of the model. In this scenario, the unobtrusiveBindingProvider will use a property, on the ko object, called bindings. This should be an object with property names that map to the id or a class of a HTML element. NB: The CSS convention, when a selector comprises multiple words, is to hyphonate the words. Bindings names can be hyphonated, as long as they are surrounded with quotation marks. For example:

ko.bindings = { "say-hello": "click:sayHello" };
Figure 8

Overriding model members

Bindings declared in the ko.bindings object can override bindings declared on a member. To do this the property must be assigned an object, which includes a bindings property and an override property (set to true). For example:

ko.binding = { "say-hello": { bindings: "click:sayGoodbye", override: true } };
Figure 9

The ko.debug property

One of the reasons for the unobtrusiveBindingProvider is to eliminate the data-bind attribute. However, it can be useful to see what the bindings are, during debugging. The ko.debug property allows us to define the conditions under which the data-bind attribute should be added. The ko.debug property is automatically set to true if the hostname is "localhost" or the protocol is "file:" but this can be overridden. For example:

ko.debug = location.hostname === "stevenbey.com";
Figure 10

Conclusion

The unobtrusiveBindingProvider enables you to maintain clean and simple HTML, whilst observing separation of concerns.

Nuget


Example

0 Comments

Available on NuGet

Including ELFAR (MVC) and ELFAR (Web API) in your web application is extremely easy.  However, if you're anything like me then you'll hate the idea of having to create two instances of the error log provider (assuming that you've referenced the same providers).  You can manually combine the files but, to save you the bother, I've done the leg work for you:

0 Comments

Introduction

There has been a steady uptake of Error Logging Filter and Route (ELFAR) for ASP.NET MVC since I introduced it, back in February.  Just a few days before that post, Microsoft released ASP.NET MVC 4 Beta (now RC), which includes ASP.NET Web API.  Although part of ASP.NET MVC 4, ASP.NET Web API uses completely different namespaces (and can be hosted independently), which means that ELFAR couldn't be plugged in automatically, so I decided to see if it was possible to adapt ELFAR for ASP.NET Web API.

Adapting ELFAR

As the two technologies follow the same architecture, it was a simple process to create the Web API ErrorLogFilter and ErrorLogRoute.  They are almost identical to the MVC ErrorLogFilter and ErrorLogRoute, apart from the base classes that they derive from (and the Web API ErrorLogRoute doesn't need to override the GetRouteData or GetVirtualPath methods).  However, to maintain the separation of concerns between ASP.NET MVC and ASP.NET Web API, the common interfaces and classes needed to be moved out of the MVC class library and into their own class library (Elfar.Core).  With that, Error Logging Filter and Route (ELFAR) for ASP.NET Web API was created.

Like ELFAR (MVC), add ELFAR (Web API) to your website/web application with just 4 lines of code (usually in the Application_Start method):

var config = GlobalConfiguration.Configuration;
var provider = new ErrorLogProvider();
config.Filters.Add(new ErrorLogFilter(provider));
config.Routes.Add("elfar", new ErrorLogRoute(provider));
Figure 1

The same but different

Whilst the filters behave identically, the routes provide very different functionality.  Where the MVC version provides the web GUI, for viewing the error logs, the Web API version acts as an ErrorLogProvider by returning either the list of error logs or a single error log.

Available on NuGet

ELFAR (Web API) uses the same error log providers as ELFAR (MVC):

0 Comments

Erik Funkenbusch posted a question, on the ELFAR discussion board, regarding the logging of handled exceptions. He managed to figure out a solution and kindly posted it:

FYI, I found a solution that works pretty well.

try
{
    throw new NotImplementedException();
}
catch (Exception e)
{
    foreach (var filter in GlobalFilters.Filters)
    {
        if (filter.Instance is ErrorLogFilter)
        {
            var f = filter.Instance as ErrorLogFilter;
            f.OnException(new ExceptionContext(ControllerContext, e));
        }
    }
}

I actually took this one step further and created a .Log() extension method on Exception.

public static class ExceptionExtensions
{
    public static void Log(this Exception exception, ControllerContext context)
    {
        foreach (var filter in GlobalFilters.Filters)
        {
            if (filter.Instance is ErrorLogFilter)
            {
                var f = filter.Instance as ErrorLogFilter;
                f.OnException(new ExceptionContext(context, exception));
            }
        }
    }
}

Then you just need to use it as such:

try
{
    throw new NotImplementedException();
}
catch (Exception e)
{
    //...
    e.Log(ControllerContext);
}

However, there is an alternative solution: throw the exception and let ELFAR automatically handle it (Figure 1).

try
{
    throw new NotImplementedException();
}
catch (Exception)
{
    //...
    throw;
}
Figure 1

Update:

Erik responded with some good points:

Rethrowing the exception will result in an unhandle exception propogating to the client...The whole point is to handle th[e] exception, but log it so it can be investigated. I find this particularly useful with exceptions for external resources (Web requests, file access, etc..). There is no point in passing this on to the user if you can continue.

0 Comments

Yesterday I needed to access a querystring parameter, from JavaScript. There is no in-built object or property but there are plenty of examples of how to do this. However, I've written a neat self executing function (Figure 1) that gets the key/value pairs, for each querystring parameter, and creates a querystring property on the window.location object.

(function(location){
var querystring = {};
  location.search.replace(/([^?=&]+)(=([^&]*))?/g, function($0, $1, $2, $3){
querystring[$1] = $3;
});
location.querystring = querystring;
})(window.location);
Figure 1