Event Delegation in KnockoutJS

http://www.knockmeout.net/2011/04/event-delegation-in-knockoutjs.html

by rniemeyer

HTML

<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.debug.js"></script>
<div data-bind="text: description">
</div>

<table data-bind="template: 'rowsTmpl', delegatedClick: [{callback: 'toggleShowDetails', selector: 'td a' }, { callback: deleteRow, selector: 'button' }]"></table>
<script id="rowsTmpl" type="text/html">
    <thead data-bind="delegatedEvent: [{ event: 'mouseover', callback: setDescription, selector: 'th'}, { event: 'mouseout', callback: clearDescription, selector: 'th'}]">
        <tr data-bind="template: { name: 'headTmpl', foreach: headers }"><th></th></tr>
    </thead>
    <tbody data-bind="template: { name: 'rowTmpl', foreach: rows }"></tbody>
</script>

<script id="rowTmpl" type="text/html">
    <tr data-bind="template: { name: 'cellTmpl', foreach: cells }">
        <td><button>Delete</button></td>
    </tr>
</script>

<script id="headTmpl" type="text/html">
    <th data-bind="text: name"></th>
</script>

<script id="cellTmpl" type="text/html">
    <td>
        <a href="javascript: void(0);" data-bind="text: name"></a> 
        <div data-bind="visible: showDetails, text: $data"></div>
    </td>
</script>

CSS

div { height: 20px; }
table { border: black solid 1px; }
td, th { padding: 1px; text-align: center; }

JavaScript

function Cell(name, x, y) {
    this.name = name;
    this.x = x;
    this.y = y;
    this.showDetails = ko.observable(false);
    this.toggleShowDetails = function() {
        this.showDetails(!this.showDetails());
    }.bind(this);
}

Cell.prototype.toString = function() {
    return "(" + this.x + "," + this.y + ")";
}

var viewModel = {
    counter: 0,
    headers: ko.observableArray([]),
    rows: ko.observableArray([]),
    deleteRow: function(row) {
        viewModel.rows.remove(row);
    },
    setDescription: function(header) {
        viewModel.description(header.description);
    },
    clearDescription: function() {
        viewModel.description("");
    },
    description: ko.observable("")
};

//setup a 20x20 grid of cells
for (var i = 0; i < 15; i++) {
    var row = {
        name: i,
        cells: []
    };
    for (var j = 0; j < 15; j++) {
        row.cells.push(new Cell(viewModel.counter++, i, j));
    }
    viewModel.rows.push(row);
    viewModel.headers.push({
        name: i,
        description: "This is a description for column " + i
    });
}

//binding to do event delegation for any event
ko.bindingHandlers.delegatedEvent = {
    init: function(element, valueAccessor, allBindings, viewModel) {
        var eventsToHandle = valueAccessor() || {};
        //if a single event was passed, then convert it to an array
        if (!$.isArray(eventsToHandle)) {
            eventsToHandle = [eventsToHandle];
        }
        ko.utils.arrayForEach(eventsToHandle, function(eventOptions) {
            var realCallback = function(event) {
                var element = event.target;
                var options = eventOptions;
                //verify that the element matches our selector
                if ($(element).is(options.selector)) {
                    //get real context
                    var context = $(event.target).tmplItem().data;
                    //if a string was passed for the function, then assume it is a function of the real...