Skip to content
Permalink
Browse files

feat(jexl): add cross-form jexl resolution (#154)

* feat(jexl): add cross-form jexl resolution

This allows specifying cross-form jexl expressing like that:

- "question1"|answer("sub1") // will look for answer to "question1" in child form "sub1"
- "question1"|answer("parent") // will look for answer to "question1" in parent document
- "question1"|answer("parent.sub1") // will look answer to "question1" in sibiling document "sub1"

* Testing

* refactor API: integrate path into question slug

See projectcaluma/caluma#398 (comment)
  • Loading branch information...
czosel committed Apr 29, 2019
1 parent 17afa26 commit c137e29f85bdcae42b2bfc4c2f171e1970e15c92
@@ -46,9 +46,7 @@ export default EmberObject.extend({
this.set(this._valueKey, value);
}

next(this, () =>
this.document.trigger("valueChanged", this.question.slug, value)
);
next(this, () => this.field.trigger("valueChanged", value));

return value;
}
@@ -1,7 +1,6 @@
import EmberObject, { computed } from "@ember/object";
import { assert } from "@ember/debug";
import { getOwner } from "@ember/application";
import Evented, { on } from "@ember/object/evented";
import Field from "ember-caluma/lib/field";
import jexl from "jexl";
import { atob } from "ember-caluma/helpers/atob";
@@ -15,7 +14,7 @@ const STATE_PRECEDENCE = ["invalid", "unfinished", "untouched", "valid"];
*
* @class Document
*/
export default EmberObject.extend(Evented, {
export default EmberObject.extend({
documentStore: service(),

async init() {
@@ -70,20 +69,44 @@ export default EmberObject.extend(Evented, {
questionJexl: computed(function() {
const questionJexl = new jexl.Jexl();

questionJexl.addTransform("answer", slug => this.findAnswer(slug));
questionJexl.addTransform("answer", slugWithPath =>
this.findAnswer(slugWithPath)
);

return questionJexl;
}),

findAnswer(slug) {
const result = this.findField(slug);
findAnswer(slugWithPath) {
const result = this.findField(slugWithPath);
return result && result.answer.value;
},

findField(slug) {
// Here it would be possible to extend the search range
// over the entire tree by calling findFieldInTree()
return this.fields.find(field => field.question.slug === slug);
findField(slugWithPath) {
const segments = slugWithPath.split(".");
const slug = segments.pop();
const doc = this.resolveDocument(segments);
return doc && doc.fields.find(field => field.question.slug === slug);
},

resolveDocument(segments) {
if (!segments) {
return this;
}
let _document = this;
for (let segment of segments) {
if (segment === "parent") {
_document = _document.parentDocument;
} else {
const formField = _document.fields.find(
field => field.question.slug === segment
);
if (!formField) {
return null;
}
_document = formField.childDocument;
}
}
return _document;
},

fields: computed(() => []).readOnly(),
@@ -130,14 +153,5 @@ export default EmberObject.extend(Evented, {
return STATE_PRECEDENCE.find(state =>
[this.get("childState"), this.get("ownState")].includes(state)
);
}),

updateHidden: on("valueChanged", "hiddenChanged", function(slug) {
const dependentFields = this.fields.filter(field =>
field.question.dependsOn.includes(slug)
);

// update hidden state of those fields
dependentFields.forEach(field => field.question.hiddenTask.perform());
})
});
@@ -7,6 +7,7 @@ import { camelize } from "@ember/string";
import { task } from "ember-concurrency";
import { all } from "rsvp";
import { validate } from "ember-validators";
import Evented, { on } from "@ember/object/evented";

import Answer from "ember-caluma/lib/answer";
import Question from "ember-caluma/lib/question";
@@ -36,7 +37,7 @@ const TYPE_MAP = {
*
* @class Field
*/
export default EmberObject.extend({
export default EmberObject.extend(Evented, {
saveDocumentFloatAnswerMutation,
saveDocumentIntegerAnswerMutation,
saveDocumentStringAnswerMutation,
@@ -107,6 +108,7 @@ export default EmberObject.extend({

this.setProperties({
_errors: [],
dependentFields: [],
question,
answer
});
@@ -124,6 +126,16 @@ export default EmberObject.extend({
return `Document:${this.document.id}:Question:${this.question.slug}`;
}).readOnly(),

updateHidden: on("valueChanged", "hiddenChanged", function() {
this.dependentFields.forEach(field => field.question.hiddenTask.perform());
}),

registerDependentField(field) {
if (!this.dependentFields.find(f => f.id === field.id)) {
this.dependentFields.push(field);
}
},

/**
* Whether the field is valid.
*
@@ -3,8 +3,6 @@ import { next } from "@ember/runloop";
import { lastValue } from "ember-caluma/utils/concurrency";
import { getAST, getTransforms } from "ember-caluma/utils/jexl";
import { task } from "ember-concurrency";
import { assert } from "@ember/debug";
// import { findFieldInTree } from "ember-caluma/utils/tree";

/**
* Object which represents a question in context of a field
@@ -43,13 +41,24 @@ export default EmberObject.extend({
.map(transform => transform.subject.value)
)
];
dependents.forEach(slug => {
assert(
`Field "${slug}" is not present in this document`,
this.document.findField(slug)
);

return dependents.map(slugWithPath => {
const field = this.document.findField(slugWithPath);
if (!field) {
if (slugWithPath.includes(".")) {
const path = slugWithPath.split(".");
const slug = path.pop();
throw new Error(
`Field "${slug}" is not present in document "${path}"`
);
}
throw new Error(
`Field "${slugWithPath}" is not present in this document`
);
}
field.registerDependentField(this.field);
return field;
});
return dependents;
}).readOnly(),

/**
@@ -63,22 +72,18 @@ export default EmberObject.extend({
* @return {Boolean}
*/
hiddenTask: task(function*() {
let hidden = this.document.fields
.filter(field => this.dependsOn.includes(field.question.slug))
.some(
field =>
field.question.hidden ||
field.answer.value === null ||
field.answer.value === undefined
);
let hidden = this.dependsOn.some(
field =>
field.question.hidden ||
field.answer.value === null ||
field.answer.value === undefined
);

hidden =
hidden || (yield this.field.document.questionJexl.eval(this.isHidden));

if (this.get("hiddenTask.lastSuccessful.value") !== hidden) {
next(this, () =>
this.document.trigger("hiddenChanged", this.slug, hidden)
);
next(this, () => this.field.trigger("hiddenChanged"));
}

return hidden;

This file was deleted.

0 comments on commit c137e29

Please sign in to comment.
You can’t perform that action at this time.