Savanaly
Savanaly

Reputation: 347

How to create a progress bar for saving models

I need to turn an array of raw data into Ember models. I can do that by implementing a forEach loop on the array and creating a model for each object in the array.

The issue is that I could potentially have hundreds or thousands of models to loop through, and that is going to take 10+ seconds. I'm alright with it taking that long as long as I have a way to show the users a progress bar so they know the app hasn't frozen up and how long they have to wait.

App.ApplesController = Ember.Controller.extend({
    // array with possibly thousands of objects in it
    applesJson: Ember.A(),

    // we'll be dumping the newly created models in here
    applesModels: Ember.A(),

    buildAppleModels: function () {
        var applesJson = this.get('applesJson'),
            applesModels = Ember.A();

        applesJson.forEach(function (appleJson) {
            var apple = self.store.createRecord('apple', appleJson);
            applesModels.push(apple);
            console.log('pushed an apple');
        });
    },

    ghettoProgressBar: function () {
        var complete = this.get('applesModels.length'),
            total = this.get('applesJson.length');

        return "Progress: " + complete + " out of " + total;
    }.property('applesModels.@each')
});

You can probably tell this isn't my real app, so don't worry about the details of the implementation of a progress bar or creating models etc.

The problem is that Ember tries to help by deferring all the DOM changes until after the forLoop is over (ten to twenty seconds after the process began), meaning that if I insert that ghettoProgressBar into a template, it will not tick up as I would hope, but instead stay at 0, then switch to complete when the process is all done.

Note the console log. This works as expected, ticking up little by little as models are created. That's because it fires right at the time rather than being batched by Ember for later.

Any ideas? I've been mulling over how to do this for quite awhile and would appreciate any help. I'm hoping there is some way to force Ember to flush the queue early or something, but haven't found anything looking around the API/guides.

Upvotes: 0

Views: 679

Answers (1)

panta82
panta82

Reputation: 2721

As you've correctly noticed, you need to break up the loop and give Ember a chance to update the DOM.

It's the same pattern we see again and again in user interface libraries. User code runs in one context, UI in another. Occasionally, these two must meet and synchronize. More often this happens, slower the progress becomes. Less often, more sluggish the UI feels.

Your approach here is "all cycles to work, nothing to UI". The reverse extreme would be something like this:

buildAppleModels: function () {
    var applesJson = this.get('applesJson'),
    applesModels = this.get('applesModels'),
    index = 0;

    return buildNext();  

    function buildNext() {
        var appleJson = applesJson[index];
        if (!appleJson) {
            return;
        }
        var apple = Ember.Object.create(appleJson);
        applesModels.addObject(apple);
        console.log('pushed an apple');
        index++;
        Ember.run.next(buildNext);
    }
}

This will update UI instantly, but slow down the process tenfold or more.

The correct approach would be something in the middle, where you do batches of work, and then sleep for a cycle or two to let the UI catch up.

That said, I'm not excluding Ember has some way to jolt the DOM update in a synchronous manner (you can try digging through their Ember.run docs), but even if it did, I doubt that would be a very good idea.

Upvotes: 1

Related Questions