Code:
/*
Aggregate data from another store. Will end-up with less rows, but with bigger numbers.
Use fields declared in AggregatorStore to know which field to care about
store: 'StoreWithDateToAggregate',
groupField : 'day',
fields : [ {
name : 'cost',
type : 'int',
summaryFn : Ext.Array.sum
}, {
name : 'extraCosts',
mapping : 'superCosts', // match different name in store we depend on
summaryFn : Ext.Array.sum
} ]
// We have two asynchronous cases we try to handles while we may be called and both didn't yet happen.
// The binding of our dependent store may not have yet happened, happen often if it come from a viewmodel, the store will show up only after parameters available
// Then, while the store may be bind, it may still not have been loaded
*/
Ext.define('Nextt.store.AggregatorStore', {
extend : 'Ext.data.Store',
uses : [ 'Nextt.Promise' ],
mixins : [ 'Ext.util.StoreHolder' ],
config : {
store : null
},
constructor : function(config) {
this.callParent(arguments);
this.initConfig(config);
},
setStore : function(store) {
this.bindStore(store);
},
onBindStore : function(store, initial) {
this.initialLoad();
},
initialTranslateDoneDeferred : new Ext.Deferred(),
initialLoad : function() {
var me = this;
// maybe called more than one time, but should do something only when store is ready
if (me.getCount() > 0) {
// already done
return;
}
var promiseStoreLoaded = Nextt.Promise.getPromiseStoreLoaded(me.getStore());
Ext.promise.Promise.all(promiseStoreLoaded).then(function(values) {
me.translateAll();
me.initialTranslateDoneDeferred.resolve();
});
me.getStore().on({
update : me.onUpdate,
scope : me
});
},
onUpdate : function(store, record, operation, modifiedFieldNames, details, eOpts) {
// this could be optimized but I it's unlikely the user will notice we recalculate the entire store, since this is likely happening when the user modify a different
// store/grid
var me = this;
me.removeAll();
me.translateAll();
},
translateAll : function() {
var me = this;
var model = me.getModel();
var fields = model.getFields();
var fieldMap = model.getFieldsMap();
var dependentStore = me.getStore();
var recordsToBeAdded = [];
dependentStore.getGroups().each(function(group) {
var aggregations = {};
Ext.each(fields, function(field) {
Ext.each(group.items, function(record) {
var fieldValue = record.get(field.getMapping() || field.getName());
var fieldKey = field.getName();
var values = aggregations[fieldKey];
if (values == undefined) {
values = [];
aggregations[fieldKey] = values;
}
values.push(fieldValue);
});
});
var recordData = {};
Ext.Object.each(aggregations, function(key, values, myself) {
var summaryFn = fieldMap[key].summaryFn;
var aggregatedValue;
if (!summaryFn) {
// take the first value
aggregatedValue = values[0];
} else {
aggregatedValue = summaryFn(values);
}
recordData[key] = aggregatedValue;
});
recordsToBeAdded.push(recordData);
});
me.add(recordsToBeAdded);
}
});
Ext.define('Nextt.Promise', {
extend : 'Ext.data.Model',
statics : {
storeLoaded : function(storeParam) {
return function(){
var store = Ext.data.StoreManager.lookup(storeParam);
return Nextt.Promise.getDeferredLoaded(store);
}
},
getPromiseStoreLoaded : function(store) {
var deferred = new Ext.Deferred();
if (store.isLoaded() && !store.isLoading() && !store.hasPendingLoad() ) {
deferred.resolve(store.getCount());
} else {
store.on("load", function(store, records, successful, operation, eOpts) {
if (successful) {
deferred.resolve(store.getCount());
} else {
deferred.resolve('failed ' + operation);
}
}, this, {
single: true
});
}
return deferred.promise;
}
}
}, function(){
var deferred = new Ext.Deferred();
deferred.resolve(true);
Nextt.Promise.done = deferred.promise;
});