3

I have a grid of observables and computed observables. The first row contains percentage rates used as a multiplier against the values in all the rows below. The user can edit the percentage rate value and Knockout handles all the cascading updates. I also need a separate textbox where the user can enter a new percentage rate that will be applied to each of the percentage rates in the grid.

Initial binding works fine and updating 1 percentage rate works is fine too.

The javascript warning occurs when the user enters a value in the textbox and I loop through the view model updating each of the percentage rates to match. The grid columns are actually monthly values so the loop to update the percentage rate only executes 12 times.

I tried throttle extender but it didn't solve the problem. Any ideas?

Update: not sure it will help, but I added some code

$("#NewRate").change(function (e) {
    var newRate = parseFloat($(this).val());
    for (var i = 0; i < 12; i++) {
        viewModel.resourceCategory.monthAmounts[i].amount(newRate);
    }
});

function ConvertToDate(jsonDateString) {
    var re = /-?\d+/;
    var m = re.exec(jsonDateString);
    return new Date(parseInt(m[0]));
}

function MonthAmount(amount, dateKey) {
    var self = this;
    self.amount = ko.observable(amount).extend({ throttle: 1 }); //using the throttle to avoid "long running script" warning in IE
    self.dateKey = ConvertToDate(dateKey);
    self.monthIndex = self.dateKey.getMonth();
}

function ResourceCategory(name, monthAmounts) {
    var self = this;
    self.name = name;

    self.monthAmounts = ko.utils.arrayMap(monthAmounts, function (monthAmount) {
        return new MonthAmount(monthAmount.Amount, monthAmount.DateKey);
    });

    self.totalAmount = ko.computed(function () {
        var sum = 0;
        for (var i = 0; i < self.monthAmounts.length; i++) {
            sum += parseFloat(self.monthAmounts[i].amount());
        }
        return sum.toFixed(2);
    }).extend({ throttle: 1 }); //using the throttle to avoid "long running script" warning in IE

    self.averageAmount = ko.computed(function () {
        return (self.totalAmount() / self.monthAmounts.length).toFixed(2);
    }).extend({ throttle: 1 }); //using the throttle to avoid "long running script" warning in IE

}
function ResourceCategoriesMonthTotal(monthIndex, resourceCategories) {
    var self = this;
    self.monthIndex = monthIndex;
    self.dateKey = new Date(new Date().getFullYear(), monthIndex, 1);
    self.amount = ko.computed(function () {
        var val = 0;
        for (var i = 0; i < resourceCategories.length; i++) {
            val += parseFloat(resourceCategories[i].monthAmounts[self.monthIndex].amount());
        }
        return (val).toFixed(2);
    }).extend({ throttle: 1 }); //using the throttle to avoid "long running script" warning in IE
}

self.resourceCategoriesMonthTotals = new Array();
for (var monthIndex = 0; monthIndex < 12; monthIndex++) {
    self.resourceCategoriesMonthTotals.push(new ResourceCategoriesMonthTotal(monthIndex, self.resourceCategories));
}

self.resourceCategoriesTotal = ko.computed(function () {
    var val = 0;
    for (var i = 0; i < self.resourceCategoriesMonthTotals.length; i++) {
        val += parseFloat(self.resourceCategoriesMonthTotals[i].amount());
    }
    return (val / self.resourceCategoriesMonthTotals.length).toFixed(2);
}).extend({ throttle: 1 }); //using the throttle to avoid "long running script" warning in IE
Homer
  • 7,594
  • 14
  • 69
  • 109
  • 1
    You're going to have to post some code. There isn't much to go on here. – arb Apr 25 '12 at 13:46
  • 1
    The loop only executes 12 times but there is a lot of observables triggering again and again, like the totalAmount() one that changes after each of these 12 changes. Basically in these 12 iterations, you're probably updating the dom more than 100 times. I'm not sure exactly how, but it would be great if you could suspend all observable evaluations right before that for loop and resume after it. I'm sure it would run in a time manageable by IE then. – AlexG Apr 25 '12 at 19:10
  • You can find a solution here: http://stackoverflow.com/questions/9121330/slow-executing-js-in-ie-and-ff/10535843#10535843 –  May 11 '12 at 11:10

2 Answers2

2

Sounds like the problem is when you

loop through the view model updating each of the percentage rates to match

If so, then one answer for IE is to yield to the browser by using a timeout(0) call. When your JS resumes after the timeout, do the next iteration of the loop, then another timeout.

When I've done this, I only do the timeouts if I've sniffed that it is an IE browser. Otherwise, not needed.

Larry K
  • 47,808
  • 15
  • 87
  • 140
  • I tried putting this in the update loop but it didn't help: `setTimeout(function () { viewModel.costCenterOverheadRate.monthAmounts[i].amount(newRate); }, 0);` – Homer Apr 25 '12 at 14:55
  • That looks right. Sorry it didn't work. Try making other changes until you can isolate what causes the IE problem. – Larry K Apr 25 '12 at 16:40
  • I guess it still run all 12 calls in one synchronous block when doing that. Can you instead code it as a sequence where you always set the next amount in a subsequent setTimeout ? – AlexG Apr 25 '12 at 19:16
2

Ok, this stinks, but I think the problem is that I had this on my page:

<div data-bind="text: ko.toJSON($root)"></div>

After I removed that it didn't give me the “slow running script” warning.

Homer
  • 7,594
  • 14
  • 69
  • 109