views:

352

answers:

3

The feature I'm implementing is not a really required, so I won't include an extra library of threads just for that. But if someone knows a workaround I will appreciate it.

So I have a grid of 256 rows and 3 columns; one those columns is a checkboxColumn (similar to the one used here). But that checkboxColumn plugin has being modified to show a checkbox in the header in order to allow a kind of Check-All and Check-None features.

Because there are so many rows, the check-all process is taking to long. So I decided to put a "Spinning wheel" (waiting gif-animation). And now the problem is that the animation doesn't work. So I put a defer call to allow the rendering of the animated icon, but it seems like that defer is not enough for the animation to run. Although this defer at least allows showing the first frame of the waiting gif, instead of showing the checked-state for about a minute until the unchecked-state is show when everything is done.

Here is the expensive method:

internalSetAllColumn: function(column, newValue) {
    column.masterValue = newValue;
    column.header = '<div class="x-grid3-check-col-td loading-indicator">&#160;</div>'; // loading icon
    this.grid.getView().updateHeaders();
    (function() {
        this.grid.getStore().each(function(rec) {
            if (this.isCellEditable(rec)) {
                rec.set(this.dataIndex, newValue);
            }
        }, this);
        column.renderHeaderCheck();
        this.grid.getView().updateHeaders();
    }).defer(50, this);
}

And you can see a working example at jsbin.com/checkallcolumn/3; and the editable code here. Click on the column checkbox (the one on the header) and see the gif that isn't spinning as expected.

Note: I have paging functionality in other places but I won't use it here as the 256 rows are always the same. So I rather lose the check all button than paging this.

A: 

Instead of using defer, trying using Ext.TaskMgr which allows you to create a task and run it in a multithreaded manner.

Example of a task.

var task = {
    run: function(){
        Ext.fly('clock').update(new Date().format('g:i:s A'));
    },
    interval: 1000 //1 second
}
Ext.TaskMgr.start(task);
David Young
Function.defer and TaskManager.start use native JS setTimeout/setInterval calls behind the scenes to execute a function on a separate thread, so I would doubt that there's any performance difference between the two (though I haven't tested that). TaskManager is mainly a utility for setting up repeating tasks in an OO way without having to write raw setTimeout code -- not to replace Function.defer.
bmoeskau
Nope, that didn't work either. I tested on jsbin replacing the `run` method for mine, setting the `scope` and playing with the properties (`interval` and `duration`) but the gif is still motionless. But thanks anyway. Because I just realized that it *might* work if I manage to partition the long process into small chunks. And hopefully the rendering would work and the entire process won't take too much longer than without this "threads".
Protron
+1  A: 

The problem is not the use of .defer(), it's that you're doing it at the wrong level. The tight loop over the records is what's causing the freeze, and you aren't actually offloading those UI updates to another thread, you're simply delaying execution of the loop. When the loop kicks off after 50 milliseconds, it's still a tight loop that takes a while to execute.

This is a quick fix that, while not optimal, is probably closer to what you want:

    this.grid.getStore().each(function(rec) {
       rec.set.defer(1, rec, [this.dataIndex, newValue]);
    }, this);
    column.renderHeaderCheck();
    this.grid.getView().updateHeaders();

A couple of notes. If you are using defer to simply execute code on another thread (and not truly because you need a specific delay) just use 1 ms. No need to wait longer for no reason.

Secondly, beware using defer like this -- it may work for this case, but this is not a general best practice. When you start deferring stuff (especially in a loop like this), it can potentially cause issues if other code executes during the middle of your loop that might also affect your underlying store. Ideally, you'd be able to find a more suitable solution like deferring only the grid's UI update, but I suspect doing so might require an override or two (not sure off the top of my head). So if this solves it for you, great -- but don't say I didn't warn you if some other weird bug pops up later ;)

BTW, if you are scrolled down in the grid, there will still be a noticeable delay to the user since the loop still executes top to bottom. You could get fancy and update the visible records first before moving on to the rest of the data set -- depends on how much effort you want to put into it.

EDIT: OK, after putting a little more thought into this, all you need to do to avoid UI updates is tell the store not to fire its update events during the loop, then manually refresh the grid after all data updates are done. With this approach you shouldn't even need a spinner in the header, and you avoid all the potential issues with defer. Try this:

internalSetAllColumn: function(column, newValue) {
    var store = this.grid.getStore(),
        gridView = this.grid.getView();

    column.masterValue = newValue;

    store.suspendEvents();
    store.each(function(rec) {
        rec.set(this.dataIndex, newValue);
    }, this);
    store.resumeEvents();

    column.renderHeaderCheck();
    gridView.refresh();
}
bmoeskau
Yes it works! Thank you, I will take into account your comments. I will talk to my team; I hope we can just remove the header-check, because it will probably imply more problems than solutions. But I will wait until Monday at least to see if anyone finds a safer workaround (but until now you are having the accepted answer).
Protron
I've got so excited that this was much closer to what I want... that I forgot that the waiting icon is not been shown at all. Anyway, this still might be a valid workaround.
Protron
Cool!!! your Edit works great!! Let me try it on Monday at work (to be sure that I don't need the events for the other plugins I have there), but I'm pretty sure that this is the best solution I can get. Thank you very much.
Protron
A: 

Too tricky probably, but it does show the animated spinning wheel, and seems to work just fine:

internalSetAllColumn: function(column, newValue) {
    column.masterValue = newValue;
    column.header = '<div class="x-grid3-check-col-td loading-indicator">&#160;</div>'; // loading icon
    this.grid.getView().updateHeaders();
    var store = this.grid.getStore();
    var count = store.getCount();
    var step = 5;
    var times = (count / step) >> 0;
    var task = {
        run: function() {
            var last = step * task.taskRunCount;
            var first = last - step;
            if (first <= count) {
                var recs = store.getRange(first, last);
                Ext.each(recs, function(rec) {
                    rec.set(this.dataIndex, newValue);
                }, this);
            }
            if (task.taskRunCount > times) {
                column.renderHeaderCheck();
                this.grid.getView().updateHeaders();
                return false;
            }
        },
        interval: 1,
        scope: this
    };
    Ext.TaskMgr.start(task);
}

You can see (and edit) the working solution at jsbin.com/checkallcolumn/5

Protron
Please see my edited answer.
bmoeskau