Mats Bryntse
30 January 2013

Managing stores in a complex component

The Ext Gantt chart deals with a lot of observable data stores: Tasks, Dependencies, Assignments, Resources etc. A typical view […]

The Ext Gantt chart deals with a lot of observable data stores: Tasks, Dependencies, Assignments, Resources etc. A typical view component will observe its data stores and react to their changes. When building a view component that consumes data from one or more stores, it’s important to remember to de-register any store listeners in the Component destructor. If you don’t, you can end up with hard-to-trace errors after creating multiple views using the same store. Another common case where this happens is if you have a view component in a window that can be destroyed/closed. Once it’s re-opened, a new component instance is created and is tied to the same original data store. Consider this simple piece of code.

[crayon striped=”false” lang=”javascript” nums=”false” toolbar=”false”]

var taskStore = new Gnt.data.TaskStore();

var gantt = new Gnt.panel.Gantt({
taskStore : taskStore
});

var gantt2 = new Gnt.panel.Gantt({
taskStore : taskStore
});

gantt.destroy();

taskStore.getById(123).set(‘Name’, ‘Foo’);

[/crayon]

If the task store listeners aren’t cleaned up properly in the Gantt chart destructor, then code may be triggered in the first Gantt panel instance by a store listener even though the panel itself is destroyed. These errors are often tricky to debug and a good idea is to test this, it’s even quite easy. This is what we use for our tests to make sure we don’t ‘leak’ any listeners.

[crayon striped=”false” lang=”javascript” nums=”false” toolbar=”false”]
StartTest(function(t) {
t.diag(‘Gantt not rendered’);

var assignmentStore = t.getAssignmentStore();
var resourceStore = t.getResourceStore();
var dependencyStore = t.getDependencyStore();

var taskStore = t.getTaskStore({
dependencyStore : dependencyStore,
resourceStore : resourceStore,
assignmentStore : assignmentStore
});

t.snapShotListeners(taskStore, ‘taskStore’);
t.snapShotListeners(taskStore.nodeStore, ‘nodeStore’);
t.snapShotListeners(dependencyStore, ‘dependencyStore’);
t.snapShotListeners(resourceStore, ‘resourceStore’);
t.snapShotListeners(assignmentStore, ‘assignmentStore’);

var g = t.getGantt({
taskStore : taskStore,
assignmentStore : assignmentStore,
resourceStore : resourceStore,
dependencyStore : dependencyStore
});

// Should clean all listeners
g.destroy();

t.verifyListeners(taskStore, ‘taskStore’, ‘Listeners cleaned up on taskStore’);
t.verifyListeners(assignmentStore, ‘assignmentStore’, ‘Listeners cleaned up on assignmentStore’);
t.verifyListeners(dependencyStore, ‘dependencyStore’, ‘Listeners cleaned up on dependencyStore’);
t.verifyListeners(resourceStore, ‘resourceStore’, ‘Listeners cleaned up on resourceStore’);
t.verifyListeners(taskStore.nodeStore, ‘nodeStore’, ‘Listeners cleaned up on nodeStore’);

t.diag(‘Gantt rendered then destroyed’);

g = t.getGantt({
renderTo : Ext.getBody(),
columnLines : true,
taskStore : taskStore,
assignmentStore : assignmentStore,
resourceStore : resourceStore,
dependencyStore : dependencyStore
});

g.destroy();

t.verifyListeners(taskStore, ‘taskStore’, ‘Rendered: Listeners cleaned up on taskStore’);
t.verifyListeners(assignmentStore, ‘assignmentStore’, ‘Rendered: Listeners cleaned up on assignmentStore’);
t.verifyListeners(dependencyStore, ‘dependencyStore’, ‘Rendered: Listeners cleaned up on dependencyStore’);
t.verifyListeners(resourceStore, ‘resourceStore’, ‘Rendered: Listeners cleaned up on resourceStore’);
t.verifyListeners(taskStore.nodeStore, ‘nodeStore’, ‘Rendered: Listeners cleaned up on nodeStore’);
});

[/crayon]

Above you can see that we verify that no listeners are left for 5 different stores, both when not rendering the panel and when rendering the panel (as we shouldn’t assume when the listeners are added). In our TestClass for Siesta we have added these two useful methods for Observables:

[crayon striped=”false” lang=”javascript” nums=”false” toolbar=”false”]
Class(‘Bryntum.Test’, {

isa: Siesta.Test.ExtJS,

methods: {

snapShotListeners : function(observable, name) {
this._observableData = this._observableData || {};

if (!name) throw ‘Must provide a name for the observable’;

this._observableData[name] = this.global.Ext.apply({}, observable.hasListeners);

// Delete new Ext JS 4.2 properties
if (‘_decr_’ in this._observableData[name]) {
delete this._observableData[name]._decr_;
delete this._observableData[name]._incr_;
}
},

verifyListeners : function(observable, name, description) {
var needListeners = this._observableData[name];

this.isDeeply(observable.hasListeners, needListeners, description);
}
}
// eof methods
})
[/crayon]

Hope this helps you test your Observables!

Mats Bryntse

Development