Knockout custom binding does not show initial viewModel data properly
Asked Answered
S

1

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();
});
Shirl answered 20/12, 2011 at 20:39 Comment(0)
N
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/

Neilson answered 20/12, 2011 at 21:5 Comment(3)
I actually had tried this before. But the problem for me is that it would make the jQuery Editable plugin not working. The fiddle jsfiddle.net/justinc/VbeBA/9 shows the editable works properly on Firefox but not the initial binding, while 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?Shirl
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: jsfiddle.net/rniemeyer/VbeBA/11Neilson
Added one more update: 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.Neilson

© 2022 - 2024 — McMap. All rights reserved.