Martin
Martin

Reputation: 1001

knockoutjs update view during function execution

This is most probably a trivial problem. In my javascript I have a function that does a lot of actions and I want to update the view with the progress. I have a self.message = ko.observable() which I continuously update during the function execution.

This is bound to a div element on my view. I would expect the view to be updated every time the ko.observable is updated.

However, this does not happen. It only shows the last value of "message" when the function has completed execution.

How do I show progress update message using knockoutjs?

The code is as follows:

in js file

self.message = ko.observable("");

self.myfunction = function()
{
  self.message("start function");
....
  self.message("end function");
}

in the view

<div data-bind="text: vm.message"></div>

Thanks Martin

Upvotes: 1

Views: 345

Answers (1)

Hans Roerdinkholder
Hans Roerdinkholder

Reputation: 3000

The reason you're only seein the last value of self.message, is that your long-running JavaScript function probably executes synchronously. Your web app is working in a single thread, so the UI won't update until your JavaScript is done executing.

It seems from your description that your long running task is done in steps already, since in between you have moments where you (try to) update the UI. The easiest trick to give the UI a chance to redraw is to use setTimeout with a timeout of 1 ms. This will give the UI a chance to catch up.

Here's some ugly example code:

self.myfunction = function()
{
  self.message("start function");
  setTimeout(function () {
      subTask1();
      self.message("subtask1 complete");
      setTimeout(function () {
          subTask2();
          self.message("subtask2 complete");
          setTimeout(function () {
              subTask3();
              self.message("function complete");
          }, 1);
      }, 1);
  }, 1);
}

etc. You get the idea. You probably want to refactor this away into helper functions, and even better, leverage promises to chain the code. The whole point is that you need to create time in between your heavy-load task to update the UI.

Edit: code to do it in a for-loop

Here's some untested code that will run an array of tasks in batches. You can pass the amount of tasks you want to run in each batch. I haven't tested the code, but it should be pretty accurate. I've set up a few observables to use in the UI. the observable 'progress' will show a percentage, which you can use for a progress bar, for example.

var done = ko.observable(0),
    total = ko.observable(0),
    progress = ko.computed(function () {
        return Math.round(done() / total()) + '%';
    });
function executeBatch(tasks, amount, startAt) {
    // If startAt is not set, start at the first task
    startAt = startAt || 0;

    // Stop condition: all tasks are done
    if (tasks.length - 1 < startAt) {
        return;
    }

    // Execute the amount of tasks you want to run in 1 batch, or less if there aren't enough tasks left.
    for (var i = 0; i < Math.min(startAt + amount, tasks.length); i++) {
        doTask(tasks[i]);
    }

    // Update the UI
    done(startAt + amount);

    // Give the UI time to update, then recursively call this function to continue executing tasks
    setTimeout(function () {
        executeBatch(tasks, amount, startAt + amount);
    }, 1);
}
function executeAllTasks(tasks) {
    // Reset the observables
    done(0);
    total(tasks.length);

    // Start doing the work, 10 tasks at a time
    executeBatch(tasks, 10);
}

Upvotes: 1

Related Questions