Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item-level formula evaluation basic implementation #266

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/dg/components/case_table/hier_table_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,9 @@ DG.HierTableView = SC.ScrollView.extend( (function() {
*/
refresh: function() {
this.get('contentView').refreshTables();

// For when the table is updated by internal value change (no UI event)
this.refreshDividers();
},

/**
Expand Down
13 changes: 12 additions & 1 deletion apps/dg/controllers/data_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -682,8 +682,12 @@ DG.DataContext = SC.Object.extend((function() // closure
collection.casesController.endPropertyChanges();
}

// Always regenerate the collection to ensure proper table structure with formulas.
// TODO: Only regenerate when dependency attributes change.
var casesAffected = this.regenerateCollectionCases(this.get('collections'));

// invalidate dependents; aggregate functions may need to recalculate
this.invalidateAttrsOfCollections([collection], iChange);
this.invalidateAttrsOfCollections(casesAffected.collections, iChange);

return result;
},
Expand Down Expand Up @@ -1028,6 +1032,9 @@ DG.DataContext = SC.Object.extend((function() // closure
});
this.invalidateDependentsAndNotify(attrNodes, iChange);

// Formula attribute may require regenerating the hierarchical structure.
this.regenerateCollectionCases(this.get('collections'));

return { success: true, caseIDs: iChange.caseIDs };
},
/**
Expand Down Expand Up @@ -2358,6 +2365,10 @@ DG.DataContext = SC.Object.extend((function() // closure
var attr = DG.Attribute.getAttributeByID(attrID),
attrNodes = [{ type: DG.DEP_TYPE_ATTRIBUTE, id: attrID, name: attr.get('name') }];
this.invalidateDependentsAndNotify(attrNodes);

// Always regenerate collection to ensure the proper table structure.
var casesAffected = this.regenerateCollectionCases(this.get('collections'));
this.invalidateAttrsOfCollections(casesAffected.collections);
},

/**
Expand Down
25 changes: 12 additions & 13 deletions apps/dg/formula/collection_formula_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,19 +355,18 @@ DG.CollectionFormulaContext = DG.GlobalFormulaContext.extend((function() {
// evaluation time.
// Client is responsible for putting '_case_' into evaluation context.
var attributeID = attribute.get('id');
this.attrFns[ iName] = isValidAttrReference()
? function(iEvalContext) {
var tCase = iEvalContext._caseMap_
? iEvalContext._caseMap_[collectionID]
: iEvalContext._case_,
tValue = tCase && tCase.getRawValue(attributeID);
// Propagate error values immediately
if( tValue instanceof Error) throw tValue;
return tValue;
}
: function(iEvalContext) {
throw new DG.HierReferenceError(iName);
};

// Previously, we had limited the formula attribute reference to be inside a collection (except for aggregates.)
// We have since removed this restriction to always evaluate at item level.
this.attrFns[ iName] = function(iEvalContext) {
var tCase = iEvalContext._caseMap_
? iEvalContext._caseMap_[collectionID]
: iEvalContext._case_,
tValue = tCase && tCase.getRawValue(attributeID);
// Propagate error values immediately
if( tValue instanceof Error) throw tValue;
return tValue;
};

// Track the number of attribute references for each collection
// This needs to be tracked per aggregate function instance so
Expand Down
32 changes: 12 additions & 20 deletions apps/dg/models/case_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,31 +148,23 @@ DG.Case = DG.BaseModel.extend((function() {
* @returns {Number | String | null}
*/
getRawValue: function( iAttrID) {

if( !SC.none( iAttrID)) {

// If we have an attribute formula, we must evaluate it.
var tAttr = DG.Attribute.getAttributeByID( iAttrID);
var valuesMap = this.get('_valuesMap');
if (tAttr && tAttr.get('hasFormula') &&
// we only do the evaluation if it's one of this case's attributes
(this.getPath('collection.id') === tAttr.getPath('collection.id'))) {
return tAttr.evalFormula( this);
}

// If we have a cached value, simply return it

if( tAttr && valuesMap && (!SC.none(valuesMap[iAttrID])) &&
// we only do the evaluation if it's one of this case's attributes
(this.getPath('collection.id') === tAttr.getPath('collection.id'))) {
return valuesMap[iAttrID];
// If we have an attribute formula, we must evaluate it.
if (tAttr && tAttr.get('hasFormula')) {
var calculatedValue = tAttr.evalFormula(this);

// If formula is invalid, return the cached value.
if (SC.empty(calculatedValue) && !SC.none(valuesMap[iAttrID])) {
return valuesMap[iAttrID];
} else {
return calculatedValue;
}
} else {
return valuesMap[iAttrID]; // Otherwise return the cached item value for iAttrID.
}

// one last chance if we've got a parent and it has 'getValue'
// Need for this seems to have been brought about by a particular import of boundary file data. Can't hurt.
var tParent = this.get('parent');
if( tParent /*&& tParent.getValue*/)
return tParent.getRawValue( iAttrID);
}
// if we get here, return value is undefined
},
Expand Down
27 changes: 19 additions & 8 deletions apps/dg/models/collection_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -563,18 +563,12 @@ DG.Collection = DG.BaseModel.extend( (function() // closure
var childCollection = this.children[0];
var itemGroups = {}; // a hash value representing the values for the
// attributes of this collection
var attrs = [];

if (SC.none(items)) {
items = this.dataSet.getDataItems()
.filter(function (item) { return !item.deleted; });
}

attrs = this.attrs.filter(function (attr) {
return SC.empty(attr.formula);
});


// We are going to walk the itemGroups hash-map creating cases.
// If we are the base collection we will have a case for each item in the
// order defined by the item list.
Expand All @@ -590,7 +584,24 @@ DG.Collection = DG.BaseModel.extend( (function() // closure
// values for this collection's attributes and the values being arrays of items
items.forEach(function (item) {
if (!item.deleted) {
var values = attrs.map(function (attr) { return item.values[attr.id];}).join();
var values = this.attrs.map(function (attr) {
// Here, we pre-fill the item hash-map with evaluated formula values
// so the collection / table will be configured with the right number of cases.
if (attr.get('hasFormula')) {
// A temporary case (item) object to evaluate the formula.
// Note: We could try to reuse a single DG.Case instance,
// but we need a function for updating just the following fields.
var tCase = DG.Case.createCase({
collection: attr.get('collection'),
item: item
});
var calculatedValue = attr.evalFormula(tCase);
tCase.destroy();
return item.values[attr.id] = calculatedValue;
} else {
return item.values[attr.id];
}
}.bind(this)).join();
var list = itemGroups[values] || [];
if (list.length === 0) {
itemGroups[values] = list;
Expand All @@ -612,7 +623,7 @@ DG.Collection = DG.BaseModel.extend( (function() // closure
createdCases = createdCases.concat(childCases);
}.bind(this));
}
//this.updateCaseIDToIndexMap();
this.updateCaseIDToIndexMap();
return createdCases;
},

Expand Down