0

I'm developing application using backbone.js & jquery. I have following code in model:

runReport: function() {
   this.set({generatingReport: true});      
   //long computation...
   this.set({generatingReport: false});     
}

and following code in corresponding view (in initialize function):

...
var that = this;
...

this.model.bind("change:generatingReport", function() {                     
    if(that.model.get("generatingReport") === true) {
        $("#report").empty().append("<h1>Generating report...</h1>").show(0);
        console.log("begin");
    } else if(that.model.get("generatingReport") === false) {
        $("#report").empty().append("<h1>Report generated</h1>").show(0);
        console.log("end");
    }
});

and here is code in view that run the action:

...
events {
    "click #btn-run": "runReport"
}
...
runReport: function() {
    this.model.runReport();
}

My problem is that that the "Generatin report..." message is not shown at all (log messages are printed). When the report is generated "Report generated" appears.

If I do following (see added alert in IF branch):

this.model.bind("change:generatingReport", function() {                     
    if(that.model.get("generatingReport") === true) {
        $("#report").empty().append("<h1>Generating report...</h1>").show(0);
        console.log("begin");
        alert("stop!");
    } else if(that.model.get("generatingReport") === false) {
        $("#report").empty().append("<h1>Report generated</h1>").show(0);
        console.log("end");
    }
});

then "Generating report..." is shown. In the "long computation..." part there is no hide jquery call that could possibly hide the message.

Any idea what is happening here?

dgw
  • 13,418
  • 11
  • 56
  • 54
Radek Michna
  • 499
  • 3
  • 12

2 Answers2

1

I think what's probably happening here is that the browser never has a chance to update the UI. Your long computation is all happening in the event handler for a click event. The browser probably (I say "probably" a lot here because I'm not really an expert in browser threading models!) dispatches the click event to your Javascript code, and then waits for your event handler to finish before updating/redrawing the UI. In this case, you're not yielding control (finishing) until the long process is over, so the UI only redraws itself once at the end. The DOM's #report node is changing internally in response to your code, but it doesn't have a chance to redraw itself on screen until after your process finishes. I don't know if this is optimal, but something like this might help:

runReport: function() {
  var self = this;
  this.set({generatingReport: true});

  // By calling delay, runReport() will exit immediately so UI can redraw
  _.delay(function() {
    //long computation...
    self.set({generatingReport: false});
  }, 50);
}

I'm just using underscore's delay() function there to postpone the long processing for 50 milliseconds. By doing so, the runReport() function can immediately exit, the click event handler that started the whole chain of events can finish executing, and the UI can redraw itself to show the "Generating report..." message. After a very short (50 milliseconds) delay, the long computation will begin, and when it's done, generatingReport will set to false and the UI will update again.

heavi5ide
  • 1,599
  • 10
  • 11
  • Hi, this simple hack helped, now both messages are rendered (one millisecond is enough:) though, as you wrote, I don't think this is optimal solution. Anyway, thanks. – Radek Michna Mar 30 '12 at 04:49
  • Yes, it does seem somewhat hacky, but I think this is basically how you have to do it. Javascript (at least in the browser) is just single-threaded, so you can't really do a long-running task and also handle user input at the same time. I've seen others suggest that you code your long-running tasks (which probably do some sort of looping) so that they yield control back to the UI thread every so often: http://stackoverflow.com/questions/672732/prevent-long-running-javascript-from-locking-up-browser – heavi5ide Apr 02 '12 at 22:50
0

This is because the DOM manipulation is slower than your runReport function as @heavi5ide mentioned.

If your report generation is almost instant you don't need any "generating report..." message. You can also do something like :

runReport: function() {
  var self = this;
  var timer = setTimeout(function() {
       self.set({generatingReport: true});
  }, 500);

  //
  // report calculation code
  //

  if ( this.get("generatingReport") ) this.set({ generatingReport : false });

  clearTimeout(timer);

}

By doing this you will activate generatingReport message only if the report takes more than 0.5 seconds to compile.

drinchev
  • 19,201
  • 4
  • 67
  • 93