0

I use a third-party plugin to make a table editable. So I need to create a custom binding for <td> so that any changes to the text caused by the plugin would trigger a view model update. But the custom binding does not show correct data, as opposed to the built-in 'text' binding. Did I do anything wrong?

Please see: http://jsfiddle.net/VbeBA/5

HTML:

<table id="table1" cellspacing="0" cellpadding="0" border="0">
    <tr>
        <th style="width:150px">Product</th>
        <th>Price ($)</th>
        <th>Quantity</th>
        <th>Amount ($)</th>
    </tr>

    <tbody data-bind='template: {name: "orderTemplate", foreach: orders}'></tbody>
</table>

<script type="text/html" id="orderTemplate">
    <tr>
        <td data-bind="text: product">${product}</td>
        <td class="editable number" data-bind="dataCell: price"></td>
        <td class="editable number"data-bind="dataCell: quantity">${quantity}</td>
        <td class="number" data-bind="text: amount">${amount}</td>
    </tr>
</script>

CSS:

table 
{
    border: solid 1px #e8eef4;
    border-collapse: collapse;
}

table th
{
    padding: 6px 5px;
    background-color: #e8eef4; 
    border: solid 1px #e8eef4;   
}

table td 
{
    padding:0 3px 0 3px;
    margin: 0px;
    height: 20px;
    border: solid 1px #e8eef4;
}

td.number
{
    width: 100px;
    text-align:right;
}

td.editable
{
    background-color:#fff;
}

td.editable input
{
    font-family: Verdana, Helvetica, Sans-Serif;
    text-align: right;
    width: 100%;
    height: 100%;
    border: 0;
}

td.editing
{
    border: 2px solid Blue;
}

Script:

$(function () {
    ko.bindingHandlers.dataCell = {

        init: function (element, valueAccessor) {
            ko.utils.registerEventHandler(element, "change", function () {
                var value = valueAccessor();
                value($(element).text());
            });
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var value = valueAccessor();
            $(element).text(value);
        }

    };

    var order = function (product, price, quantity) {
        this.product = product;
        this.price = ko.observable(price);
        this.quantity = ko.observable(quantity);
        this.amount = ko.dependentObservable(function () {
            return this.price() * this.quantity();
        }, this);
    }

    var ordersModel = function () {
        this.orders = ko.observableArray([]);
    }


    var viewModel = new ordersModel();
    viewModel.orders = ko.observableArray([
            new order("Gala Apple", 0.79, 150),
            new order("Naval Orange", 0.29, 500)
        ]);

    ko.applyBindings(viewModel);

    $(".editable").change();
});
Sam
  • 7,252
  • 16
  • 46
  • 65

1 Answers1

3

In your update function you will want to unwrap the observable. valueAccessor() is going to give you the observable itself and then you would want to unwrap it (call it as a function) to get the value.

A safe way to do that is to use ko.utils.unwrapObservable as it will tolerate both observables and non-observables.

So, your update would look like:

    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).text(value);
    }

Additionally, in your fiddle you had jQuery.tmpl listed after Knockout, so KO did not register the jQuery template engine.

Here is an updated fiddle: http://jsfiddle.net/rniemeyer/VbeBA/8/

Updated: The final solution is at http://jsfiddle.net/rniemeyer/qQaUa/

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • I actually had tried this before. But the problem for me is that it would make the jQuery Editable plugin not working. The fiddle http://jsfiddle.net/justinc/VbeBA/9/ shows the editable works properly on Firefox but not the initial binding, while http://jsfiddle.net/justinc/VbeBA/10/ shows the editable does not work with unwrapped observable. Maybe there is an event timing conflict between Knockout and Editable? – Justin Chen Dec 20 '11 at 23:51
  • The issue was really that your templates were getting re-rendered based on the `${variable}` jQuery templates calls (which create dependencies). These were unnecessary, as you were already using bindings. Here is a sample: http://jsfiddle.net/rniemeyer/VbeBA/11/ – RP Niemeyer Dec 21 '11 at 16:07
  • 1
    Added one more update: http://jsfiddle.net/rniemeyer/qQaUa/. When you access the underlying value in the update function it creates a dependency on the observable, which is good. However, when updating the observable in the change handler, it would trigger the update function and ultimately cause the content to be empty. The change handler just needs to run its update after the current execution context is complete. – RP Niemeyer Dec 21 '11 at 16:48