0
votes

I have a CRUD single page using Knockout, everything works OK, I get the data from a JSON call, it fills an automapped observable array with the list of objects. I can add or edit single items in that array.

The issue comes with formatting a currency (numeric) column I show in the table with the list of objects. I have tried using js functions, in a lot of ways but the formatted currency amount of the table does not update when I update an item. If I use a binding for formatting the field then I can't edit it because it converts into a String.

What I need is a one-way binded (auto updated) formatted column for my currency column. But I can't create a computed column right away because I'm using an automapped array of objects. I tried adding the computed using the example in http://knockoutjs.com/documentation/plugins-mapping.html, but I don't know how to use it with an mapped array.

My viewmodel is something like this:

//--Accounts - Viewmodel Knockout
function accountViewModel() {
    var self = this;
    self.accounts = ko.observableArray();  //this is the list of objects
    self.account = ko.observable();  //single item for creating or editing

    //--get list------
    self.getAccounts = function () {
        $.postJSON(appURL + 'Accounts/GetList', function (data) {  
            ko.mapping.fromJS(data.Records, {}, self.accounts);

        });

    };

    self.getAccounts();
}   

Each account item have fields like: -Id -Name -Balance <-- this is the column I want to format

Using it in the page:

<table data-bind="visible: accounts().length > 0">
<thead>
    <tr>
        <th scope="col">Id</th>
        <th scope="col">Name</th>
        <th scope="col">Balance</th>
    </tr>
</thead>
<tbody id="accounts" data-bind="foreach: accounts">
<tr>
    <td><span data-bind="text: Id"></span></td>
    <td><a href="" data-bind="text: Name,  click: $root.getdetails" style="display:block;"></a></td>
    <td style="text-align:right;">
        <span data-bind="text: formatCurrency(Balance), css: { negative: Balance() < 0, positive: Balance() > 0 }"></span>

    </td>
</tr>
</tbody>
</table>

formatCurrency is just a js function for formatting the number:

formatCurrency = function (value) {
    debugger;
    if (value!=undefined)
        return "$" + withCommas(value().toFixed(2));
    //had to use value() instead of value, because of toFixed
}

Thanks!

1
Possible duplicate for how to use the mapping plugin and have a computed on a mapped object: stackoverflow.com/questions/15480316/… - Paul Manzotti

1 Answers

0
votes

When you set the text to the return value of a function (text: formatCurrency(Balance)) it only runs once. It's not an observable, so it's value will never update again. What you need is a true-blue computed observable. In order to get that, you'll have to customize the mapping. So simply create a view model for your individual account object, and then map your returned data onto that.

var SingleAccountViewModel = function (account) {
    var model = ko.mapping.fromJS(account);

    model.FormattedBalance = ko.computed(function () {
        return formattedCurrency(model.Balance);
    });

    return model;
}

Then when you get your data back from your AJAX response:

$.postJSON(appURL + 'Accounts/GetList', function (data) {  
    self.accounts($.map(data.Records, SingleAccountViewModel));
});

The jQuery.map method will iterate through the array and return a new array composed of the return values of the function passed. In this case, that's your view model, which will now have a FormattedBalance computed on it that you can bind to.