Skip to content

Commit 787026b

Browse files
committed
WIP for supporting dataLoader use for a computed #208
1 parent f397d6f commit 787026b

File tree

5 files changed

+101
-39
lines changed

5 files changed

+101
-39
lines changed

grails-app/assets/javascripts/forms-knockout-bindings.js

+16
Original file line numberDiff line numberDiff line change
@@ -1160,5 +1160,21 @@
11601160
}
11611161
};
11621162

1163+
ko.extenders.dataLoader = function(target, options) {
1164+
1165+
var dataLoader = new ecodata.forms.dataLoader(target.context, target.config);
1166+
var dataLoaderConfig = target.get('computed');
1167+
if (!dataLoaderConfig) {
1168+
throw "This extender can only be used with the metadata extender";
1169+
}
1170+
var dependencyTracker = ko.computed(function () {
1171+
return dataLoader.prepop(dataLoaderConfig).done( function(data) {
1172+
target(data);
1173+
});
1174+
});
1175+
//dependencyTracker.subscribe(function() { console.log("bananas")})
1176+
return target;
1177+
}
1178+
11631179
})();
11641180

grails-app/assets/javascripts/forms.js

+28-10
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,14 @@ function orEmptyArray(v) {
329329
var expressionCache = {};
330330

331331
function evaluateInternal(expression, context) {
332-
var parsedExpression = expressionCache[expression];
333-
if (!parsedExpression) {
334-
parsedExpression = parser.parse(expression);
335-
expressionCache[expression] = parsedExpression;
336-
}
332+
var parsedExpression = expressionCache[expression];
333+
if (!parsedExpression) {
334+
parsedExpression = parser.parse(expression);
335+
expressionCache[expression] = parsedExpression;
336+
}
337337

338-
var variables = parsedExpression.variables();
339-
var boundVariables = bindVariables(variables, context);
338+
var variables = parsedExpression.variables();
339+
var boundVariables = bindVariables(variables, context);
340340

341341
var result;
342342
try {
@@ -655,18 +655,23 @@ function orEmptyArray(v) {
655655
self.getPrepopData = function (conf) {
656656
var source = conf.source;
657657
if (source.url) {
658+
var failedValidation = false;
658659
var url = (config.prepopUrlPrefix || window.location.href) + source.url;
659660
var params = [];
660661
_.each(source.params || [], function(param) {
661662
var value;
662663
if (param.type && param.type == 'computed') {
663664
// evaluate the expression against the context.
664665
value = ecodata.forms.expressionEvaluator.evaluateUntyped(param.expression, context);
666+
if (param.required && !value) {
667+
failedValidation = true;
668+
}
665669
}
666670
else {
667671
// Treat it as a literal
668672
value = param.value;
669673
}
674+
670675
// Unroll the array to prevent jQuery appending [] to the array typed parameter name.
671676
if (_.isArray(value)) {
672677
for (var i=0; i<value.length; i++) {
@@ -677,6 +682,9 @@ function orEmptyArray(v) {
677682
params.push({name:param.name, value: value});
678683
}
679684
});
685+
if (failedValidation) {
686+
return $.Deferred().resolve(source.defaultValue || null);
687+
}
680688
return $.ajax(url, {data:params, dataType:source.dataType || 'json'});
681689
}
682690
var deferred = $.Deferred();
@@ -690,6 +698,14 @@ function orEmptyArray(v) {
690698
else if (source && source.hasOwnProperty('literal')) {
691699
data = source['literal'];
692700
}
701+
702+
// Support for converting array typed values to lookup tables.
703+
// e.g. MERIT has a list of data sets that we want to be selectable from a list then
704+
// the full data set to be available for lookup by id (using the lookupTable data type)
705+
if (source['index-by']) {
706+
data = _.indexBy(data, source['index-by'])
707+
}
708+
693709
deferred.resolve(data);
694710
return deferred;
695711
};
@@ -844,6 +860,7 @@ function orEmptyArray(v) {
844860
}
845861
}
846862
else if (metadata.constraints.type == 'pre-populated') {
863+
console.log("******************** Encountered pre-pop constraints for "+self.getName()+"***************************************")
847864
var defaultConstraints = metadata.constraints.defaults || [];
848865
var constraintsObservable = ko.observableArray(defaultConstraints);
849866
if (!includeExcludeDefined) {
@@ -898,14 +915,15 @@ function orEmptyArray(v) {
898915
self.displayOptions = metadata.displayOptions;
899916
}
900917
self.load = function(data) {
918+
console.log("Loading data for "+self.getName()+" : "+data)
919+
self(data);
901920
if (constraintsInititaliser) {
902921
constraintsInititaliser.always(function() {
922+
console.log("Re-Loading data for "+self.getName()+" : "+data)
903923
self(data);
904924
})
905925
}
906-
else {
907-
self(data);
908-
}
926+
909927
}
910928
};
911929

grails-app/assets/javascripts/knockout-utils.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,16 @@
9191
return (typeof root.modelAsJSON === 'function') ? root.modelAsJSON() : ko.toJSON(root);
9292
};
9393
var _initialState = ko.observable(getRepresentation());
94+
console.log("****************** Initial state ******************")
95+
console.log( _initialState())
9496

9597
result.isDirty = ko.pureComputed(function () {
98+
9699
var dirty = _isInitiallyDirty() || _initialState() !== getRepresentation();
100+
if (dirty) {
101+
console.log("******************* new state *******************")
102+
console.log(getRepresentation())
103+
}
97104
return dirty;
98105
});
99106
if (rateLimit) {
@@ -102,6 +109,8 @@
102109

103110
result.reset = function () {
104111
_initialState(getRepresentation());
112+
console.log("****************** Reset initial state ******************")
113+
console.log( _initialState())
105114
_isInitiallyDirty(false);
106115
};
107116

@@ -137,14 +146,16 @@
137146

138147
//just for subscriptions
139148
getRepresentation();
140-
149+
console.log("****************** Initial state ******************")
150+
console.log(getRepresentation())
141151
//next time return true and avoid ko.toJS
142152
_initialized(true);
143153

144154
//on initialization this flag is not dirty
145155
return false;
146156
}
147-
157+
console.log("****************** New state ******************")
158+
console.log(getRepresentation())
148159
//on subsequent changes, flag is now dirty
149160
return true;
150161
});

grails-app/taglib/au/org/ala/ecodata/forms/ModelJSTagLib.groovy

+5-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class ModelJSTagLib {
136136
else if (mod.dataType == 'time') {
137137
timeViewModel(ctx)
138138
}
139-
else if (mod.computed) {
139+
else if (mod.computed && !mod.computed.source) {
140140
computedModel(ctx)
141141
}
142142
else if (mod.dataType == 'text') {
@@ -572,6 +572,9 @@ class ModelJSTagLib {
572572
if (requiresMetadataExtender(ctx.dataModel)) {
573573
extenders.add("{metadata:{metadata:self.dataModel['${ctx.fieldName()}'], context:self.\$context, config:config}}")
574574
}
575+
if (ctx.dataModel.computed?.source) {
576+
extenders.add("{dataLoader:true}")
577+
}
575578

576579
extenders.each {
577580
extenderJS += ".extend(${it})"
@@ -581,7 +584,7 @@ class ModelJSTagLib {
581584
}
582585

583586
private boolean requiresMetadataExtender(Map dataModel) {
584-
dataModel.dataType == 'feature' || dataModel.behaviour || dataModel.warning || dataModel.constraints || dataModel.displayOptions || (dataModel.validate instanceof List) || dataModel.scores
587+
dataModel.dataType == 'feature' || dataModel.behaviour || dataModel.warning || dataModel.constraints || dataModel.displayOptions || (dataModel.validate instanceof List) || dataModel.scores || dataModel.computed?.source
585588

586589
}
587590

src/main/groovy/au/org/ala/ecodata/forms/ComputedValueRenderer.groovy

+39-25
Original file line numberDiff line numberDiff line change
@@ -47,40 +47,54 @@ class ComputedValueRenderer {
4747
}
4848

4949
def computedObservable(model, propertyContext, dependantContext, out) {
50-
out << INDENT*5 << "${propertyContext}.${model.name} = ko.computed(function () {\n"
50+
51+
boolean needsDefer = model.computed.defer
52+
String endFunction = "});\n"
53+
String computedParams = "function ()"
54+
if (needsDefer) {
55+
computedParams = "{deferEvaluation:true, read:function ()"
56+
endFunction = "}});\n"
57+
}
58+
59+
out << INDENT*5 << "${propertyContext}.${model.name} = ko.computed($computedParams {\n"
5160

5261
if (model.computed.expression) {
5362
renderJSExpression(model, "self", out)
5463
}
5564
else {
56-
// must be at least one dependant
57-
def numbers = []
58-
def checkNumberness = []
59-
model.computed.dependents.each {
60-
def ref = it
61-
def path = dependantContext
62-
if (ref.startsWith('$')) {
63-
ref = ref[1..-1]
64-
path = "self.data"
65-
}
66-
numbers << "Number(${path}.${ref}())"
67-
checkNumberness << "isNaN(Number(${path}.${ref}()))"
68-
}
69-
out << INDENT * 6 << "if (" + checkNumberness.join(' || ') + ") { return 0; }\n"
70-
if (model.computed.operation == 'divide') {
71-
// can't divide by zero
72-
out << INDENT * 6 << "if (${numbers[-1]} === 0) { return 0; }\n"
73-
}
74-
def expression = numbers.join(" ${operators[model.computed.operation]} ")
75-
if (model.computed.rounding) {
76-
expression = "neat_number(${expression},${model.computed.rounding})"
77-
}
78-
out << INDENT * 6 << "return " + expression + ";\n"
65+
renderLegacyComputed(model, dependantContext, out)
7966
}
80-
out << INDENT * 5 << "});\n"
8167

68+
out << INDENT*5 << endFunction
8269
}
8370

71+
private void renderLegacyComputed(model, dependantContext, out) {
72+
// must be at least one dependant
73+
def numbers = []
74+
def checkNumberness = []
75+
model.computed.dependents.each {
76+
def ref = it
77+
def path = dependantContext
78+
if (ref.startsWith('$')) {
79+
ref = ref[1..-1]
80+
path = "self.data"
81+
}
82+
numbers << "Number(${path}.${ref}())"
83+
checkNumberness << "isNaN(Number(${path}.${ref}()))"
84+
}
85+
out << INDENT * 6 << "if (" + checkNumberness.join(' || ') + ") { return 0; }\n"
86+
if (model.computed.operation == 'divide') {
87+
// can't divide by zero
88+
out << INDENT * 6 << "if (${numbers[-1]} === 0) { return 0; }\n"
89+
}
90+
def expression = numbers.join(" ${operators[model.computed.operation]} ")
91+
if (model.computed.rounding) {
92+
expression = "neat_number(${expression},${model.computed.rounding})"
93+
}
94+
out << INDENT * 6 << "return " + expression + ";\n"
95+
}
96+
97+
8498
def computedViewModel(out, attrs, model, propertyContext, dependantContext) {
8599
computedViewModel(out, attrs, model, propertyContext, dependantContext, null)
86100
}

0 commit comments

Comments
 (0)