Wednesday, March 21, 2012

Knockout JS AutoComplete Binding with multiple selections

The following custom binding can be used to bind data to the jQuery UI autocomplete widget and allow multiple selections;

    ko.bindingHandlers.autocomplete_multiselect = {
        init: function (element, params) {
            var options = params().split(' ');
            $(element).bind("focus", function () {
                $(element).change();
            });
            $(element).autocomplete({ source: function (request, response) {
                $.getJSON(options[0], { q: extractLast(request.term) }, function (data) {
                    response($.map(data, function (item) {
                        return { label: item[options[1]], value: item[options[2]] };
                    }));
                });
            },
                focus: function () {
                    // prevent value inserted on focus
                    return false;
                },
                select: function (event, ui) {
                    var terms = split(this.value);
                    // remove the current input
                    terms.pop();
                    // add the selected item
                    terms.push(ui.item.value);
                    // add placeholder to get the comma-and-space at the end
                    terms.push("");
                    this.value = terms.join(", ");
                    return false;
                }
            })
        .bind("focus", function () {
            $(element).change();
        });
        },
        update: function (element, params) {
        }
    };


Use it in the following way;
<input type="text" data-bind="value: Categories, autocomplete_multiselect: 'AutoCompleteDataSourceUrl ItemName ItemValue'" />
Where AutoCompleteDataSourceUrl should be replaced by the URL of the service returning autocomplete data. ItemName and ItemValue should be replaced by the names of the name and value fields in the returned json.



Knockout JS AutoComplete Binding

The following custom binding can be used to bind data to the jQuery UI autocomplete widget;

    ko.bindingHandlers.autocomplete = {
        init: function (element, params) {
            var options = params().split(' ');
            $(element).bind("focus", function () {
                $(element).change();
            });
            $(element).autocomplete({ source: function (request, response) {
                $.getJSON(options[0], { q: request.term }, function (data) {
                    response($.map(data, function (item) {
                        return { label: item[options[1]], value: item[options[2]] };
                    }));
                });
            }
            });
        },
        update: function (element, params) {
        }
    };

Use it in the following way;
<input type="text" data-bind="value: Category, autocomplete: 'AutoCompleteDataSourceUrl ItemName ItemValue'" />
Where AutoCompleteDataSourceUrl should be replaced by the URL of the service returning autocomplete data. ItemName and ItemValue should be replaced by the names of the name and value fields in the returned json.

Tuesday, March 20, 2012

System.Data.Entity.DbSet does not contain a definition for 'Where'

I came across the following error while trying to use a Linq query on a DbSet object in an ASP.NET MVC 4 Controller class;

System.Data.Entity.DbSet<MyApp.Models.MyModel>' does not contain a definition for 'Where' and no extension method 'Where' accepting a first argument of type System.Data.Entity.DbSet<MyApp.Models.MyModel>

The problem was resolved by adding  the System.Linq namespace to the controller. Where is an extension method and its definition lies in the System.Linq namespace.

Monday, March 19, 2012

Knockout JS DatePicker Binding

If you are binding to JSON data using Knockout.js, chances are that you may have encountered problems using the jquery ui datepicker control. The following knockout custom binding can be used to handle dates properly with knockout.js and the datepicker control.

    ko.bindingHandlers.datepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            try {
                var jsonDate = ko.utils.unwrapObservable(valueAccessor());
                var value = parseJsonDateString(jsonDate);
                var strDate = value.getMonth() + 1 + "/"
                                + value.getDate() + "/"
                                + value.getFullYear();
                element.setAttribute('value', strDate);
            }
            catch (exc) {
            }
            //initialize datepicker with some optional options
            $.datepicker.setDefaults({ dateFormat: 'mm/dd/yy' });
            var options = allBindingsAccessor().datepickerOptions || dateTimePickerOptions;
            $(element).datepicker(options);

            //handle the field changing
            ko.utils.registerEventHandler(element, "change", function () {
                var observable = valueAccessor();
                var dt = new Date($(element).val());
                observable('/Date(' + dt.getTime() + ')/');
            });

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).datepicker("destroy");
            });
        },
        update: function (element, valueAccessor) {
            var val = valueAccessor();
            var dt = new Date(element.getAttribute('value'));
            val('/Date(' + dt.getTime() + ')/');
        }
    };

var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;

var parseJsonDateString = function (value) {
    var arr = value && jsonDateRE.exec(value);
    if (arr) {
        return new Date(parseInt(arr[1]));
    }
    return value;
};

Saturday, March 10, 2012

Knockout.js jQuery UI Dialog Binding

jQuery UI dialogs are a very useful way of displaying detail data but if you have a few of them nested in your view then Knockout JS data-binding requires some planning. The ko.applyBindings function can take a html element object as a second parameter and then it applies the binding to every element with a data-bind attribute in that scope. jQuery UI dialogs are div elements that may be nested within other elements that display the higher level data items. Consider the following example;

<div id="OrdersDiv">
   <input id="OrderIdText" type="text" data-bind="value: OrderId"/>

   <input id="CustomerNameText" type="text" data-bind="value: CustomerName"/>
   <a id="DialogLink" href="#" >Click here to see order details</a>
   <div id="OrderDetailsDialog">
      <input id="ProductNameText" type="text" data-bind="value: ProductName"/>
   </div>
</div>
<script type="text/javascript">
   var OrderViewModel = function () {
      var OrderId = ko.observable();
      var CustomerName = ko.observable();
   }
   
   $(document).ready(function () {
      $("#OrderDetailsDialog").dialog();
      var order = new OrderViewModel ();
      ko.applyBindings (order, document.getElementById ("OrdersDiv"));
   }
</script>

This code won't work since the OrdersViewModel doesn't have a ProductName property. One way to handle this problem is to create a view model object that contains the order details data as well. But this may result in unnecessary data transfer. We can create a custom binding to prevent KO from binding to child elements and then bind the child elements to the details view model as below;

<div id="OrdersDiv">
   <input id="OrderIdText" type="text" data-bind="value: OrderId"/>

   <input id="CustomerNameText" type="text" data-bind="value: CustomerName"/>
   <a id="DialogLink" href="#" >Click here to see order details</a>
   <div id="DetailsDiv" data-bind="stopBindings: true">
      <div id="OrderDetailsDialog">
         <input id="ProductNameText" type="text" data-bind="value: ProductName"/>
      </div>
   </div>
</div>
<script type="text/javascript">
   var OrderViewModel = function () {
      var OrderId = ko.observable();
      var CustomerName = ko.observable();
   }
   var OrderDetailsViewModel = function () {
      var OrderId = ko.observable();
      var ProductName = ko.observable();      

   }
   $(document).ready(function () {
      $("#OrderDetailsDialog").dialog();
      ko.bindingHandlers.stopBindings = {
          init: function () {
              return { 'controlsDescendantBindings': true };
          }
      };

      var order = new OrderViewModel ();
      var orderDetail = new OrderDetailsViewModel();
      ko.applyBindings (order, document.getElementById ("OrdersDiv"));
      ko.applyBindings (orderDetail, document.getElementById ("OrderDetailsDialog"));

   }
</script>

Reference: Knockout ignore binding, for nested viewModels?

JSON dates and Knockout.js date binding

JSON data for date data types is returned as a string in the following format;

\/Date(1330731072583)\/

If you are using Knockout.js for client-side data binding, a simple value binding will result in the same date string being displayed on the data bound control. To properly handle this a custom date binding can be created.

ko.bindingHandlers.date = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        try {
            var jsonDate = ko.utils.unwrapObservable(valueAccessor());
            var value = parseJsonDateString(jsonDate);
            var strDate = value.getMonth() + 1 + "/"
                            + value.getDate() + "/" 
                            + value.getFullYear();
            element.setAttribute('value', strDate);
        }
        catch (exc) {
        }
        $(element).change(function () {
            var value = valueAccessor();
            value(element.getAttribute('value'));
        });
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var val = valueAccessor();
        val(element.getAttribute('value'));
    }
};
The parseJsonDateString function can be defined using a regular expression.

var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
var parseJsonDateString = function (value) {
    var arr = value && jsonDateRE.exec(value);
    if (arr) {
        return new Date(parseInt(arr[1]));
    }
    return value;
};

Use the date binding in the following way;
<input type="text" id="StartDateText" data-bind="date: StartDate" />