Update UI with state of model calculation


#1

Hi there,

I was wondering how to provide user feedback on state of a longer-running calculation. I imagine the implementation will be via a progress property on the controller, which tracks the current progress vs. total number of calculations.

In trying to do this the way I thought would work, it just hangs on 0% complete, and then immediately jumps to 100% complete. I’m sure there’s something I’m doing wrong with tracking the variables, or with the promise implementation.

Any help would be appreciated.

View

<p>Progress: {{simulationProgress}}%</p>

Model

completedTrials: 0

simulate: (noTrials) ->
  promise = new Ember.RSVP.Promise (resolve, reject) =>
    for trialNumber in [0..noTrials]    
      aVeryHardCalculation()
      @set 'completedTrials', trialNumber
  return promise

Controller

numberOfSimulationTrials: 200
simulationProgress: @get('completedTrials') / @get('numberOfSimulationTrials')

actions:
    simulate: ->
      @get('model').simulate(@get 'numberOfSimulationTrials').then () =>
        console.log "Done!"


#2

In your controller, the simulationProgress property should listen to changes to the completedTrials property.

I haven’t done coffeescript in a while, so this might not be the right syntax:

simulationProgress: (
  @get('completedTrials') / @get('numberOfSimulationTrials')
).property('completedTrials')

#3

Just so you’re not confused, zogstrip made a small typo. simulationProgess should be a function wrapped in the .property().

simulationProgress: ( ->
  @get('completedTrials') / @get('numberOfSimulationTrials')
).property('completedTrials')

#4

Doh! I wasn’t watching the property… Ugh. Thanks guys.

As an aside - should the completedTrials live in the controller instead of the model?


#5

As an aside - should the completedTrials live in the controller instead of the model?

I think that depends on where you’re going to use it. Personally, I like to keep all computed properties out of models and in controllers. But if you were going to use that property in several places, it could become a pain to keep redefining aliases to copy the property across controllers. I would say this: try to keep things out of the models and in controllers, but if it becomes too much, put it in the model.


#6

Hmmm. @gordon_kristan @zogstrip - I’ve set it to watch the model property, but it still isn’t updating the UI as the calculation progresses. Is there something to do with the run loop here?

Model

simulate: ->
  # Randomly hard work
  fib = (n) -> if (n > 1) then (fib(n - 1) + fib(n - 2)) else n
  fib(30)

View

<p>Percent complete: {{percentageComplete}}%</p>
<p>On trial: {{completedTrials}}</p>

Controller

numberOfSimulationTrials: 200
completedTrials: 0
percentageComplete: Ember.computed ->
  @get('completedTrials') / @get('numberOfSimulationTrials') * 100
.property('numberOfSimulationTrials', 'completedTrials')

actions:

  simulate: ->
    for trialNumber in [1..@get('numberOfSimulationTrials')]
      Ember.run @, ->
        @get('model').simulate()
        @set 'completedTrials', trialNumber

It just hangs at 0 and 0% until all of the trials finish, then it hops to 200 and 100%. I’ve tried it with, and without the Ember.run (also tried Ember.run.next).


#7

It seems like your logic is right. Have you checked your Javascript output to ensure that Coffeescript is generating the code you intended?

Other than that, I would imagine that your issue has to do with the Ember.run in some way. From the documentation:

Runs the passed target and method inside of a RunLoop, ensuring any deferred actions including bindings and views updates are flushed at the end.

Since all of your code goes in the same run of the run loop, it seems like the bindings won’t be updated until the updates are flushed at the end. My initial guess would be to remove Ember.run altogether, as it really shouldn’t be necessary. But since you’ve already tried that, I would try running Ember.run.sync() at the end of the loop to flush the queue. You might incur a performance hit, but it should give you a good idea of what the issue is.


#8

Thanks for that @gordon_kristan . I wasn’t aware of Emer.run.sync(), I will give that shot. I had been having a bit of success with the strategy shown on this SO question (the bit about manual loops/timeouts through the run loop). It definitely has something to do with Ember coalescing all calls to set('completedTrials', X) to the end of the loop.