@projectcaluma/ember-form 10.0.2 → 11.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1112 -0
- package/addon/components/cf-content.hbs +36 -39
- package/addon/components/cf-content.js +48 -29
- package/addon/components/cf-field/info.hbs +1 -1
- package/addon/components/cf-field/input/action-button.hbs +1 -1
- package/addon/components/cf-field/input/action-button.js +9 -7
- package/addon/components/cf-field/input/checkbox.hbs +6 -2
- package/addon/components/cf-field/input/checkbox.js +9 -29
- package/addon/components/cf-field/input/file.hbs +1 -1
- package/addon/components/cf-field/input/file.js +8 -9
- package/addon/components/cf-field/input/float.hbs +4 -4
- package/addon/components/cf-field/input/integer.hbs +5 -5
- package/addon/components/cf-field/input/radio.hbs +4 -1
- package/addon/components/cf-field/input/table.hbs +7 -7
- package/addon/components/cf-field/input/table.js +12 -10
- package/addon/components/cf-field/input/text.hbs +5 -5
- package/addon/components/cf-field/input/textarea.hbs +6 -5
- package/addon/components/cf-field/input.hbs +10 -1
- package/addon/components/cf-field/input.js +1 -1
- package/addon/components/cf-field/label.hbs +1 -1
- package/addon/components/cf-field-value.hbs +1 -1
- package/addon/components/cf-field-value.js +8 -13
- package/addon/components/cf-field.hbs +2 -2
- package/addon/components/cf-field.js +2 -3
- package/addon/components/cf-navigation-item.hbs +2 -2
- package/addon/components/cf-navigation.hbs +4 -1
- package/addon/components/document-validity.js +1 -1
- package/addon/gql/fragments/field.graphql +22 -0
- package/addon/gql/mutations/save-document-table-answer.graphql +1 -1
- package/addon/gql/mutations/save-document.graphql +1 -0
- package/addon/gql/queries/{get-document-answers.graphql → document-answers.graphql} +2 -1
- package/addon/gql/queries/{get-document-forms.graphql → document-forms.graphql} +2 -1
- package/addon/gql/queries/{get-document-used-dynamic-options.graphql → document-used-dynamic-options.graphql} +2 -1
- package/addon/gql/queries/{get-dynamic-options.graphql → dynamic-options.graphql} +2 -1
- package/addon/gql/queries/{get-fileanswer-info.graphql → fileanswer-info.graphql} +2 -1
- package/addon/helpers/get-widget.js +50 -0
- package/addon/lib/answer.js +108 -72
- package/addon/lib/base.js +32 -23
- package/addon/lib/dependencies.js +36 -71
- package/addon/lib/document.js +92 -96
- package/addon/lib/field.js +334 -401
- package/addon/lib/fieldset.js +46 -47
- package/addon/lib/form.js +27 -15
- package/addon/lib/navigation.js +211 -192
- package/addon/lib/question.js +103 -94
- package/addon/services/caluma-store.js +10 -6
- package/app/helpers/get-widget.js +4 -0
- package/blueprints/@projectcaluma/ember-form/index.js +1 -0
- package/package.json +27 -23
- package/addon/components/cf-navigation.js +0 -9
package/addon/lib/field.js
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
import { getOwner } from "@ember/application";
|
2
2
|
import { assert } from "@ember/debug";
|
3
|
-
import {
|
4
|
-
import { equal, not, reads } from "@ember/object/computed";
|
3
|
+
import { associateDestroyableChild } from "@ember/destroyable";
|
5
4
|
import { inject as service } from "@ember/service";
|
6
5
|
import { camelize } from "@ember/string";
|
6
|
+
import { tracked } from "@glimmer/tracking";
|
7
7
|
import { queryManager } from "ember-apollo-client";
|
8
|
-
import {
|
8
|
+
import { restartableTask, lastValue, dropTask } from "ember-concurrency";
|
9
9
|
import { validate } from "ember-validators";
|
10
|
-
import cloneDeep from "lodash.clonedeep";
|
11
10
|
import isEqual from "lodash.isequal";
|
12
|
-
import {
|
11
|
+
import { cached } from "tracked-toolbox";
|
13
12
|
|
14
13
|
import { decodeId } from "@projectcaluma/ember-core/helpers/decode-id";
|
15
14
|
import saveDocumentDateAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-date-answer.graphql";
|
@@ -19,12 +18,9 @@ import saveDocumentIntegerAnswerMutation from "@projectcaluma/ember-form/gql/mut
|
|
19
18
|
import saveDocumentListAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-list-answer.graphql";
|
20
19
|
import saveDocumentStringAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-string-answer.graphql";
|
21
20
|
import saveDocumentTableAnswerMutation from "@projectcaluma/ember-form/gql/mutations/save-document-table-answer.graphql";
|
22
|
-
import getDocumentUsedDynamicOptionsQuery from "@projectcaluma/ember-form/gql/queries/
|
21
|
+
import getDocumentUsedDynamicOptionsQuery from "@projectcaluma/ember-form/gql/queries/document-used-dynamic-options.graphql";
|
23
22
|
import Base from "@projectcaluma/ember-form/lib/base";
|
24
|
-
import
|
25
|
-
nestedDependencyParents,
|
26
|
-
dependencies,
|
27
|
-
} from "@projectcaluma/ember-form/lib/dependencies";
|
23
|
+
import dependencies from "@projectcaluma/ember-form/lib/dependencies";
|
28
24
|
|
29
25
|
export const TYPE_MAP = {
|
30
26
|
TextQuestion: "StringAnswer",
|
@@ -42,10 +38,20 @@ export const TYPE_MAP = {
|
|
42
38
|
DateQuestion: "DateAnswer",
|
43
39
|
};
|
44
40
|
|
45
|
-
const
|
41
|
+
const MUTATION_MAP = {
|
42
|
+
FloatAnswer: saveDocumentFloatAnswerMutation,
|
43
|
+
IntegerAnswer: saveDocumentIntegerAnswerMutation,
|
44
|
+
StringAnswer: saveDocumentStringAnswerMutation,
|
45
|
+
ListAnswer: saveDocumentListAnswerMutation,
|
46
|
+
FileAnswer: saveDocumentFileAnswerMutation,
|
47
|
+
DateAnswer: saveDocumentDateAnswerMutation,
|
48
|
+
TableAnswer: saveDocumentTableAnswerMutation,
|
49
|
+
};
|
50
|
+
|
51
|
+
const fieldIsHiddenOrEmpty = (field) => {
|
46
52
|
return (
|
47
53
|
field.hidden ||
|
48
|
-
(field.question.
|
54
|
+
(!field.question.isTable &&
|
49
55
|
(field.answer.value === null || field.answer.value === undefined))
|
50
56
|
);
|
51
57
|
};
|
@@ -55,61 +61,45 @@ const fieldIsHidden = (field) => {
|
|
55
61
|
*
|
56
62
|
* @class Field
|
57
63
|
*/
|
58
|
-
export default Base
|
59
|
-
|
60
|
-
|
61
|
-
saveDocumentStringAnswerMutation,
|
62
|
-
saveDocumentListAnswerMutation,
|
63
|
-
saveDocumentFileAnswerMutation,
|
64
|
-
saveDocumentDateAnswerMutation,
|
65
|
-
saveDocumentTableAnswerMutation,
|
66
|
-
|
67
|
-
intl: service(),
|
68
|
-
calumaStore: service(),
|
69
|
-
validator: service(),
|
70
|
-
|
71
|
-
apollo: queryManager(),
|
72
|
-
|
73
|
-
init(...args) {
|
74
|
-
assert("A document must be passed", this.document);
|
75
|
-
|
76
|
-
defineProperty(this, "pk", {
|
77
|
-
writable: false,
|
78
|
-
value: `${this.document.pk}:Question:${this.raw.question.slug}`,
|
79
|
-
});
|
64
|
+
export default class Field extends Base {
|
65
|
+
@service intl;
|
66
|
+
@service validator;
|
80
67
|
|
81
|
-
|
68
|
+
@queryManager apollo;
|
82
69
|
|
83
|
-
|
84
|
-
|
70
|
+
constructor({ fieldset, ...args }) {
|
71
|
+
assert("`fieldset` must be passed as an argument", fieldset);
|
85
72
|
|
86
|
-
|
87
|
-
},
|
73
|
+
super({ fieldset, ...args });
|
88
74
|
|
89
|
-
|
90
|
-
this._super(...args);
|
75
|
+
this.fieldset = fieldset;
|
91
76
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
77
|
+
this.pushIntoStore();
|
78
|
+
|
79
|
+
this._createQuestion();
|
80
|
+
this._createAnswer();
|
81
|
+
}
|
96
82
|
|
97
83
|
_createQuestion() {
|
84
|
+
const owner = getOwner(this);
|
85
|
+
|
98
86
|
const question =
|
99
87
|
this.calumaStore.find(`Question:${this.raw.question.slug}`) ||
|
100
|
-
|
101
|
-
.
|
102
|
-
|
88
|
+
new (owner.factoryFor("caluma-model:question").class)({
|
89
|
+
raw: this.raw.question,
|
90
|
+
owner,
|
91
|
+
});
|
103
92
|
|
104
93
|
if (question.isDynamic) {
|
105
94
|
question.loadDynamicOptions.perform();
|
106
95
|
}
|
107
96
|
|
108
|
-
this.
|
109
|
-
}
|
97
|
+
this.question = question;
|
98
|
+
}
|
110
99
|
|
111
100
|
_createAnswer() {
|
112
|
-
const
|
101
|
+
const owner = getOwner(this);
|
102
|
+
const Answer = owner.factoryFor("caluma-model:answer").class;
|
113
103
|
let answer;
|
114
104
|
|
115
105
|
// no answer passed, create an empty one
|
@@ -121,182 +111,177 @@ export default Base.extend({
|
|
121
111
|
return;
|
122
112
|
}
|
123
113
|
|
124
|
-
answer =
|
114
|
+
answer = new Answer({
|
125
115
|
raw: {
|
126
116
|
__typename: answerType,
|
127
117
|
question: { slug: this.raw.question.slug },
|
128
118
|
[camelize(answerType.replace(/Answer$/, "Value"))]: null,
|
129
119
|
},
|
130
120
|
field: this,
|
121
|
+
owner,
|
131
122
|
});
|
132
123
|
} else {
|
133
124
|
answer =
|
134
125
|
this.calumaStore.find(`Answer:${decodeId(this.raw.answer.id)}`) ||
|
135
|
-
|
126
|
+
new Answer({ raw: this.raw.answer, field: this, owner });
|
136
127
|
}
|
137
128
|
|
138
|
-
this.
|
139
|
-
}
|
129
|
+
this.answer = associateDestroyableChild(this, answer);
|
130
|
+
}
|
140
131
|
|
141
132
|
/**
|
142
133
|
* The question to this field
|
143
134
|
*
|
144
135
|
* @property {Question} question
|
145
|
-
* @accessor
|
146
136
|
*/
|
147
|
-
question
|
137
|
+
question = null;
|
148
138
|
|
149
139
|
/**
|
150
140
|
* The answer to this field. It is possible for this to be `null` if the
|
151
141
|
* question is of the static question type.
|
152
142
|
*
|
153
143
|
* @property {Answer} answer
|
154
|
-
* @accessor
|
155
144
|
*/
|
156
|
-
answer
|
145
|
+
answer = null;
|
146
|
+
|
147
|
+
/**
|
148
|
+
* The raw error objects which are later translated to readable messages.
|
149
|
+
*
|
150
|
+
* @property {Object[]} _errors
|
151
|
+
* @private
|
152
|
+
*/
|
153
|
+
@tracked _errors = [];
|
154
|
+
|
155
|
+
/**
|
156
|
+
* The primary key of the field. Consists of the document and question primary
|
157
|
+
* keys.
|
158
|
+
*
|
159
|
+
* @property {String} pk
|
160
|
+
*/
|
161
|
+
@cached
|
162
|
+
get pk() {
|
163
|
+
return `${this.document.pk}:Question:${this.raw.question.slug}`;
|
164
|
+
}
|
157
165
|
|
158
166
|
/**
|
159
167
|
* Whether the field is valid.
|
160
168
|
*
|
161
169
|
* @property {Boolean} isValid
|
162
|
-
* @accessor
|
163
170
|
*/
|
164
|
-
isValid
|
171
|
+
get isValid() {
|
172
|
+
return this.errors.length === 0;
|
173
|
+
}
|
165
174
|
|
166
175
|
/**
|
167
176
|
* Whether the field is invalid.
|
168
177
|
*
|
169
178
|
* @property {Boolean} isInvalid
|
170
|
-
* @accessor
|
171
179
|
*/
|
172
|
-
isInvalid
|
180
|
+
get isInvalid() {
|
181
|
+
return !this.isValid;
|
182
|
+
}
|
173
183
|
|
174
184
|
/**
|
175
185
|
* Whether the field is new (never saved to the backend service or empty)
|
176
186
|
*
|
177
187
|
* @property {Boolean} isNew
|
178
|
-
* @accessor
|
179
188
|
*/
|
180
|
-
|
189
|
+
get isNew() {
|
181
190
|
return !this.answer || this.answer.isNew;
|
182
|
-
}
|
191
|
+
}
|
183
192
|
|
184
193
|
/**
|
185
194
|
* Only table values, this is used for certain computed property keys.
|
186
195
|
*
|
187
196
|
* @property {Document[]} tableValue
|
188
|
-
* @accessor
|
189
197
|
*/
|
190
|
-
tableValue
|
198
|
+
get tableValue() {
|
191
199
|
return this.question.isTable ? this.value : [];
|
192
|
-
}
|
200
|
+
}
|
193
201
|
|
194
202
|
/**
|
195
203
|
* Whether the field has the defined default answer of the question as value.
|
196
204
|
*
|
197
205
|
* @property {Boolean} isDefault
|
198
|
-
* @accessor
|
199
206
|
*/
|
200
|
-
isDefault
|
201
|
-
|
202
|
-
"tableValue.@each.flatAnswerMap",
|
203
|
-
"question.{isTable,defaultValue}",
|
204
|
-
function () {
|
205
|
-
if (!this.value || !this.question.defaultValue) return false;
|
207
|
+
get isDefault() {
|
208
|
+
if (!this.value || !this.question.defaultValue) return false;
|
206
209
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
+
const value = this.question.isTable
|
211
|
+
? this.tableValue.map((doc) => doc.flatAnswerMap)
|
212
|
+
: this.value;
|
210
213
|
|
211
|
-
|
212
|
-
|
213
|
-
),
|
214
|
+
return isEqual(value, this.question.defaultValue);
|
215
|
+
}
|
214
216
|
|
215
217
|
/**
|
216
218
|
* Whether the field is dirty. This will be true, if there is a value on the
|
217
219
|
* answer which differs from the default value of the question.
|
218
220
|
*
|
219
221
|
* @property {Boolean} isDirty
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
"isNew",
|
225
|
-
"question.isCalculated",
|
226
|
-
"validate.lastSuccessful",
|
227
|
-
function () {
|
228
|
-
if (this.question.isCalculated || this.isDefault) {
|
229
|
-
return false;
|
230
|
-
}
|
231
|
-
|
232
|
-
return Boolean(this.validate.lastSuccessful) || !this.isNew;
|
222
|
+
*/
|
223
|
+
get isDirty() {
|
224
|
+
if (this.question.isCalculated || this.isDefault) {
|
225
|
+
return false;
|
233
226
|
}
|
234
|
-
|
227
|
+
|
228
|
+
return Boolean(this.validate.lastSuccessful) || !this.isNew;
|
229
|
+
}
|
235
230
|
|
236
231
|
/**
|
237
232
|
* The type of the question
|
238
233
|
*
|
239
234
|
* @property {String} questionType
|
240
|
-
* @accessor
|
241
235
|
*/
|
242
|
-
questionType
|
236
|
+
get questionType() {
|
237
|
+
return this.question.raw.__typename;
|
238
|
+
}
|
243
239
|
|
244
240
|
/**
|
245
241
|
* The document this field belongs to
|
246
242
|
*
|
247
243
|
* @property {Document} document
|
248
|
-
* @accessor
|
249
244
|
*/
|
250
|
-
document
|
245
|
+
get document() {
|
246
|
+
return this.fieldset.document;
|
247
|
+
}
|
251
248
|
|
252
249
|
/**
|
253
250
|
* The value of the field
|
254
251
|
*
|
255
252
|
* @property {*} value
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
"calculatedValue",
|
261
|
-
"answer.value",
|
262
|
-
function () {
|
263
|
-
if (this.question.isCalculated) {
|
264
|
-
return this.calculatedValue;
|
265
|
-
}
|
266
|
-
|
267
|
-
return this.answer?.value;
|
253
|
+
*/
|
254
|
+
get value() {
|
255
|
+
if (this.question.isCalculated) {
|
256
|
+
return this.calculatedValue;
|
268
257
|
}
|
269
|
-
|
258
|
+
|
259
|
+
return this.answer?.value;
|
260
|
+
}
|
270
261
|
|
271
262
|
/**
|
272
|
-
* The computed value of a
|
263
|
+
* The computed value of a calculated float question
|
273
264
|
*
|
274
265
|
* @property {*} calculatedValue
|
275
|
-
|
276
|
-
|
277
|
-
calculatedValue
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
!this.question.isCalculated ||
|
285
|
-
!this.calculatedDependencies.every((field) => !field.hidden)
|
286
|
-
) {
|
287
|
-
return null;
|
288
|
-
}
|
266
|
+
*/
|
267
|
+
@cached
|
268
|
+
get calculatedValue() {
|
269
|
+
if (
|
270
|
+
!this.question.isCalculated ||
|
271
|
+
!this.calculatedDependencies.every((field) => !field.hidden)
|
272
|
+
) {
|
273
|
+
return null;
|
274
|
+
}
|
289
275
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
}
|
276
|
+
try {
|
277
|
+
return this.document.jexl.evalSync(
|
278
|
+
this.question.raw.calcExpression,
|
279
|
+
this.jexlContext
|
280
|
+
);
|
281
|
+
} catch (error) {
|
282
|
+
return null;
|
298
283
|
}
|
299
|
-
|
284
|
+
}
|
300
285
|
|
301
286
|
/**
|
302
287
|
* Fetch all formerly used dynamic options for this question. This will be
|
@@ -306,7 +291,8 @@ export default Base.extend({
|
|
306
291
|
* @return {Object[]} Formerly used dynamic options
|
307
292
|
* @private
|
308
293
|
*/
|
309
|
-
|
294
|
+
@dropTask
|
295
|
+
*_fetchUsedDynamicOptions() {
|
310
296
|
if (!this.question.isDynamic) return null;
|
311
297
|
|
312
298
|
const edges = yield this.apollo.query(
|
@@ -325,16 +311,15 @@ export default Base.extend({
|
|
325
311
|
slug,
|
326
312
|
label,
|
327
313
|
}));
|
328
|
-
}
|
314
|
+
}
|
329
315
|
|
330
316
|
/**
|
331
317
|
* The formerly used dynamic options for this question.
|
332
318
|
*
|
333
319
|
* @property {Object[]} usedDynamicOptions
|
334
|
-
* @accessor
|
335
|
-
*
|
336
320
|
*/
|
337
|
-
|
321
|
+
@lastValue("_fetchUsedDynamicOptions")
|
322
|
+
usedDynamicOptions;
|
338
323
|
|
339
324
|
/**
|
340
325
|
* The available options for choice questions. This only works for the
|
@@ -348,79 +333,67 @@ export default Base.extend({
|
|
348
333
|
* This will also return the disabled state of the option. An option can only
|
349
334
|
* be disabled, if it is an old value used in a dynamic question.
|
350
335
|
*
|
351
|
-
* @property {
|
352
|
-
|
353
|
-
|
354
|
-
options
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
"value",
|
359
|
-
function () {
|
360
|
-
if (!this.question.isChoice && !this.question.isMultipleChoice) {
|
361
|
-
return null;
|
362
|
-
}
|
336
|
+
* @property {null|Object[]} options
|
337
|
+
*/
|
338
|
+
@cached
|
339
|
+
get options() {
|
340
|
+
if (!this.question.isChoice && !this.question.isMultipleChoice) {
|
341
|
+
return null;
|
342
|
+
}
|
363
343
|
|
364
|
-
|
365
|
-
|
344
|
+
const selected =
|
345
|
+
(this.question.isMultipleChoice ? this.value : [this.value]) || [];
|
366
346
|
|
367
|
-
|
368
|
-
|
369
|
-
|
347
|
+
const options = this.question.options.filter(
|
348
|
+
(option) => !option.disabled || selected.includes(option.slug)
|
349
|
+
);
|
370
350
|
|
371
|
-
|
372
|
-
|
373
|
-
|
351
|
+
const hasUnknownValue = !selected.every((slug) =>
|
352
|
+
options.find((option) => option.slug === slug)
|
353
|
+
);
|
374
354
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
}
|
380
|
-
|
381
|
-
return [
|
382
|
-
...options,
|
383
|
-
...(this.usedDynamicOptions || [])
|
384
|
-
.filter((used) => {
|
385
|
-
return (
|
386
|
-
selected.includes(used.slug) &&
|
387
|
-
!options.find((option) => option.slug === used.slug)
|
388
|
-
);
|
389
|
-
})
|
390
|
-
.map((used) => ({ ...used, disabled: true })),
|
391
|
-
];
|
355
|
+
if (this.question.isDynamic && hasUnknownValue) {
|
356
|
+
if (!this._fetchUsedDynamicOptions.lastSuccessful) {
|
357
|
+
// Fetch used dynamic options if not done yet
|
358
|
+
this._fetchUsedDynamicOptions.perform();
|
392
359
|
}
|
393
360
|
|
394
|
-
return
|
361
|
+
return [
|
362
|
+
...options,
|
363
|
+
...(this.usedDynamicOptions || [])
|
364
|
+
.filter((used) => {
|
365
|
+
return (
|
366
|
+
selected.includes(used.slug) &&
|
367
|
+
!options.find((option) => option.slug === used.slug)
|
368
|
+
);
|
369
|
+
})
|
370
|
+
.map((used) => ({ ...used, disabled: true })),
|
371
|
+
];
|
395
372
|
}
|
396
|
-
|
373
|
+
|
374
|
+
return options;
|
375
|
+
}
|
397
376
|
|
398
377
|
/**
|
399
378
|
* The currently selected option. This property is only used for choice
|
400
379
|
* questions. It can either return null if no value is selected yet, an
|
401
380
|
* object for single choices or an array of objects for multiple choices.
|
402
381
|
*
|
403
|
-
* @property {
|
404
|
-
* @accessor
|
382
|
+
* @property {null|Object|Object[]} selected
|
405
383
|
*/
|
406
|
-
selected
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
function () {
|
411
|
-
if (!this.question.isChoice && !this.question.isMultipleChoice) {
|
412
|
-
return null;
|
413
|
-
}
|
384
|
+
get selected() {
|
385
|
+
if (!this.question.isChoice && !this.question.isMultipleChoice) {
|
386
|
+
return null;
|
387
|
+
}
|
414
388
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
389
|
+
const selected = this.options.filter(({ slug }) =>
|
390
|
+
this.question.isMultipleChoice
|
391
|
+
? (this.value || []).includes(slug)
|
392
|
+
: this.value === slug
|
393
|
+
);
|
420
394
|
|
421
|
-
|
422
|
-
|
423
|
-
),
|
395
|
+
return this.question.isMultipleChoice ? selected : selected[0];
|
396
|
+
}
|
424
397
|
|
425
398
|
/**
|
426
399
|
* The field's JEXL context.
|
@@ -435,33 +408,25 @@ export default Base.extend({
|
|
435
408
|
* - `info.root.formMeta`: The new property for the root form meta.
|
436
409
|
*
|
437
410
|
* @property {Object} jexlContext
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
form: parent.slug,
|
458
|
-
formMeta: parent.meta,
|
459
|
-
}
|
460
|
-
: null,
|
461
|
-
},
|
462
|
-
};
|
463
|
-
}
|
464
|
-
),
|
411
|
+
*/
|
412
|
+
get jexlContext() {
|
413
|
+
const parent = this.fieldset.field?.fieldset.form;
|
414
|
+
|
415
|
+
return {
|
416
|
+
...this.document.jexlContext,
|
417
|
+
info: {
|
418
|
+
...this.document.jexlContext.info,
|
419
|
+
form: this.fieldset.form.slug,
|
420
|
+
formMeta: this.fieldset.form.raw.meta,
|
421
|
+
parent: parent
|
422
|
+
? {
|
423
|
+
form: parent.slug,
|
424
|
+
formMeta: parent.raw.meta,
|
425
|
+
}
|
426
|
+
: null,
|
427
|
+
},
|
428
|
+
};
|
429
|
+
}
|
465
430
|
|
466
431
|
/**
|
467
432
|
* Fields that are referenced in the `calcExpression` JEXL expression
|
@@ -470,14 +435,8 @@ export default Base.extend({
|
|
470
435
|
* expression needs to be re-evaluated.
|
471
436
|
*
|
472
437
|
* @property {Field[]} calculatedDependencies
|
473
|
-
* @accessor
|
474
438
|
*/
|
475
|
-
|
476
|
-
"question.calcExpression"
|
477
|
-
),
|
478
|
-
calculatedDependencies: dependencies("question.calcExpression", {
|
479
|
-
nestedParentsPath: "_calculatedNestedDependencyParents",
|
480
|
-
}),
|
439
|
+
@dependencies("question.raw.calcExpression") calculatedDependencies;
|
481
440
|
|
482
441
|
/**
|
483
442
|
* Fields that are referenced in the `isHidden` JEXL expression
|
@@ -486,12 +445,8 @@ export default Base.extend({
|
|
486
445
|
* expression needs to be re-evaluated.
|
487
446
|
*
|
488
447
|
* @property {Field[]} hiddenDependencies
|
489
|
-
* @accessor
|
490
448
|
*/
|
491
|
-
|
492
|
-
hiddenDependencies: dependencies("question.isHidden", {
|
493
|
-
nestedParentsPath: "_hiddenNestedDependencyParents",
|
494
|
-
}),
|
449
|
+
@dependencies("question.raw.isHidden") hiddenDependencies;
|
495
450
|
|
496
451
|
/**
|
497
452
|
* Fields that are referenced in the `isRequired` JEXL expression
|
@@ -500,14 +455,8 @@ export default Base.extend({
|
|
500
455
|
* expression needs to be re-evaluated.
|
501
456
|
*
|
502
457
|
* @property {Field[]} optionalDependencies
|
503
|
-
* @accessor
|
504
458
|
*/
|
505
|
-
|
506
|
-
"question.isRequired"
|
507
|
-
),
|
508
|
-
optionalDependencies: dependencies("question.isRequired", {
|
509
|
-
nestedParentsPath: "_optionalNestedDependencyParents",
|
510
|
-
}),
|
459
|
+
@dependencies("question.raw.isRequired") optionalDependencies;
|
511
460
|
|
512
461
|
/**
|
513
462
|
* The field's hidden state
|
@@ -515,99 +464,85 @@ export default Base.extend({
|
|
515
464
|
* A question is hidden if:
|
516
465
|
* - The form question field of the fieldset is hidden
|
517
466
|
* - All depending field (used in the expression) are hidden
|
518
|
-
* - The evaluated `question.isHidden` expression returns `true`
|
467
|
+
* - The evaluated `question.raw.isHidden` expression returns `true`
|
519
468
|
*
|
520
469
|
* @property {Boolean} hidden
|
521
470
|
*/
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
this.fieldset.field?.hidden ||
|
532
|
-
(this.hiddenDependencies.length &&
|
533
|
-
this.hiddenDependencies.every(fieldIsHidden))
|
534
|
-
) {
|
535
|
-
return true;
|
536
|
-
}
|
471
|
+
@cached
|
472
|
+
get hidden() {
|
473
|
+
if (
|
474
|
+
this.fieldset.field?.hidden ||
|
475
|
+
(this.hiddenDependencies.length &&
|
476
|
+
this.hiddenDependencies.every(fieldIsHiddenOrEmpty))
|
477
|
+
) {
|
478
|
+
return true;
|
479
|
+
}
|
537
480
|
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
}
|
481
|
+
try {
|
482
|
+
return this.document.jexl.evalSync(
|
483
|
+
this.question.raw.isHidden,
|
484
|
+
this.jexlContext
|
485
|
+
);
|
486
|
+
} catch (error) {
|
487
|
+
throw new Error(
|
488
|
+
`Error while evaluating \`isHidden\` expression on field \`${this.pk}\`: ${error.message}`
|
489
|
+
);
|
548
490
|
}
|
549
|
-
|
491
|
+
}
|
550
492
|
|
551
493
|
/**
|
552
494
|
* The field's optional state
|
553
495
|
*
|
554
496
|
* The field is optional if:
|
497
|
+
* - The question is of the type form or calculated float
|
555
498
|
* - The form question field of the fieldset is hidden
|
556
499
|
* - All depending fields (used in the expression) are hidden
|
557
|
-
* - The evaluated `question.isRequired` expression returns `false`
|
500
|
+
* - The evaluated `question.raw.isRequired` expression returns `false`
|
558
501
|
* - The question type is FormQuestion or CalculatedFloatQuestion
|
559
502
|
*
|
560
503
|
* @property {Boolean} optional
|
561
504
|
*/
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
["FormQuestion", "CalculatedFloatQuestion"].includes(
|
573
|
-
this.question.__typename
|
574
|
-
) ||
|
575
|
-
(this.optionalDependencies.length &&
|
576
|
-
this.optionalDependencies.every(fieldIsHidden))
|
577
|
-
) {
|
578
|
-
return true;
|
579
|
-
}
|
505
|
+
@cached
|
506
|
+
get optional() {
|
507
|
+
if (
|
508
|
+
["FormQuestion", "CalculatedFloatQuestion"].includes(this.questionType) ||
|
509
|
+
this.fieldset.field?.hidden ||
|
510
|
+
(this.optionalDependencies.length &&
|
511
|
+
this.optionalDependencies.every(fieldIsHiddenOrEmpty))
|
512
|
+
) {
|
513
|
+
return true;
|
514
|
+
}
|
580
515
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
}
|
516
|
+
try {
|
517
|
+
return !this.document.jexl.evalSync(
|
518
|
+
this.question.raw.isRequired,
|
519
|
+
this.jexlContext
|
520
|
+
);
|
521
|
+
} catch (error) {
|
522
|
+
throw new Error(
|
523
|
+
`Error while evaluating \`isRequired\` expression on field \`${this.pk}\`: ${error.message}`
|
524
|
+
);
|
591
525
|
}
|
592
|
-
|
526
|
+
}
|
593
527
|
|
594
528
|
/**
|
595
529
|
* Task to save a field. This uses a different mutation for every answer
|
596
530
|
* type.
|
597
531
|
*
|
598
|
-
* @method save
|
532
|
+
* @method save
|
599
533
|
* @return {Object} The response from the server
|
600
534
|
*/
|
601
|
-
|
535
|
+
@restartableTask
|
536
|
+
*save() {
|
602
537
|
if (this.question.isCalculated) {
|
603
538
|
return;
|
604
539
|
}
|
605
540
|
|
606
|
-
const type = this.
|
541
|
+
const type = this.answer.raw.__typename;
|
607
542
|
|
608
543
|
const response = yield this.apollo.mutate(
|
609
544
|
{
|
610
|
-
mutation:
|
545
|
+
mutation: MUTATION_MAP[type],
|
611
546
|
variables: {
|
612
547
|
input: {
|
613
548
|
question: this.question.slug,
|
@@ -621,49 +556,47 @@ export default Base.extend({
|
|
621
556
|
`saveDocument${type}.answer`
|
622
557
|
);
|
623
558
|
|
624
|
-
|
625
|
-
// if the answer was new we need to set a pk an push the answer to the
|
626
|
-
// store
|
627
|
-
this.answer.set("pk", `Answer:${decodeId(response.id)}`);
|
559
|
+
const wasNew = this.isNew;
|
628
560
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
// update the existing answer
|
633
|
-
this.answer.setProperties(response);
|
561
|
+
Object.entries(response).forEach(([key, value]) => {
|
562
|
+
this.answer.raw[key] = value;
|
563
|
+
});
|
634
564
|
|
635
|
-
|
636
|
-
|
565
|
+
if (wasNew) {
|
566
|
+
this.answer.pushIntoStore();
|
567
|
+
}
|
637
568
|
|
638
569
|
return response;
|
639
|
-
}
|
570
|
+
}
|
640
571
|
|
641
572
|
/**
|
642
|
-
* The error messages
|
573
|
+
* The translated error messages
|
643
574
|
*
|
644
575
|
* @property {String[]} errors
|
645
|
-
* @accessor
|
646
576
|
*/
|
647
|
-
|
577
|
+
@cached
|
578
|
+
get errors() {
|
648
579
|
return this._errors.map(({ type, context, value }) => {
|
649
580
|
return this.intl.t(
|
650
581
|
`caluma.form.validation.${type}`,
|
651
582
|
Object.assign({}, context, { value })
|
652
583
|
);
|
653
584
|
});
|
654
|
-
}
|
585
|
+
}
|
655
586
|
|
656
587
|
/**
|
657
588
|
* Validate the field. Every field goes through the required validation and
|
658
589
|
* the validation for the given question type. This mutates the `errors` on
|
659
590
|
* the field.
|
660
591
|
*
|
661
|
-
* @method validate
|
592
|
+
* @method validate
|
662
593
|
*/
|
663
|
-
|
664
|
-
|
594
|
+
@restartableTask
|
595
|
+
*validate() {
|
596
|
+
const specificValidation = this[`_validate${this.questionType}`];
|
597
|
+
|
665
598
|
assert(
|
666
|
-
`Missing validation function for ${this.
|
599
|
+
`Missing validation function for ${this.questionType}`,
|
667
600
|
specificValidation
|
668
601
|
);
|
669
602
|
|
@@ -672,7 +605,7 @@ export default Base.extend({
|
|
672
605
|
specificValidation,
|
673
606
|
];
|
674
607
|
|
675
|
-
const errors = (yield all(
|
608
|
+
const errors = (yield Promise.all(
|
676
609
|
validationFns.map(async (fn) => {
|
677
610
|
const res = await fn.call(this);
|
678
611
|
|
@@ -682,121 +615,121 @@ export default Base.extend({
|
|
682
615
|
.reduce((arr, e) => [...arr, ...e], []) // flatten the array
|
683
616
|
.filter((e) => typeof e === "object");
|
684
617
|
|
685
|
-
this.
|
686
|
-
}
|
618
|
+
this._errors = errors;
|
619
|
+
}
|
687
620
|
|
688
621
|
/**
|
689
622
|
* Method to validate if a question is required or not.
|
690
623
|
*
|
691
624
|
* @method _validateRequired
|
692
|
-
* @return {
|
693
|
-
* @
|
625
|
+
* @return {Boolean|Object} Returns an object if invalid or true if valid
|
626
|
+
* @private
|
694
627
|
*/
|
695
|
-
|
628
|
+
_validateRequired() {
|
696
629
|
return (
|
697
630
|
this.optional ||
|
698
|
-
validate("presence", this.
|
631
|
+
validate("presence", this.answer.value, { presence: true })
|
699
632
|
);
|
700
|
-
}
|
633
|
+
}
|
701
634
|
|
702
635
|
/**
|
703
636
|
* Method to validate a text question. This checks if the value longer than
|
704
637
|
* predefined by the question.
|
705
638
|
*
|
706
639
|
* @method _validateTextQuestion
|
707
|
-
* @return {Object
|
708
|
-
* @
|
640
|
+
* @return {Promise<Boolean|Object>} A promise which resolves into an object if invalid or true if valid
|
641
|
+
* @private
|
709
642
|
*/
|
710
643
|
async _validateTextQuestion() {
|
711
644
|
return [
|
712
645
|
...(await this.validator.validate(
|
713
|
-
this.
|
714
|
-
this.
|
646
|
+
this.answer.value,
|
647
|
+
this.question.raw.meta.formatValidators ?? []
|
715
648
|
)),
|
716
|
-
validate("length", this.
|
717
|
-
min: this.
|
718
|
-
max: this.
|
649
|
+
validate("length", this.answer.value, {
|
650
|
+
min: this.question.raw.textMinLength || 0,
|
651
|
+
max: this.question.raw.textMaxLength || Number.POSITIVE_INFINITY,
|
719
652
|
}),
|
720
653
|
];
|
721
|
-
}
|
654
|
+
}
|
722
655
|
|
723
656
|
/**
|
724
657
|
* Method to validate a textarea question. This checks if the value longer
|
725
658
|
* than predefined by the question.
|
726
659
|
*
|
727
660
|
* @method _validateTextareaQuestion
|
728
|
-
* @return {Object
|
729
|
-
* @
|
661
|
+
* @return {Promise<Boolean|Object>} A promise which resolves into an object if invalid or true if valid
|
662
|
+
* @private
|
730
663
|
*/
|
731
664
|
async _validateTextareaQuestion() {
|
732
665
|
return [
|
733
666
|
...(await this.validator.validate(
|
734
|
-
this.
|
735
|
-
this.
|
667
|
+
this.answer.value,
|
668
|
+
this.question.raw.meta.formatValidators ?? []
|
736
669
|
)),
|
737
|
-
validate("length", this.
|
738
|
-
min: this.
|
739
|
-
max: this.
|
670
|
+
validate("length", this.answer.value, {
|
671
|
+
min: this.question.raw.textareaMinLength || 0,
|
672
|
+
max: this.question.raw.textareaMaxLength || Number.POSITIVE_INFINITY,
|
740
673
|
}),
|
741
674
|
];
|
742
|
-
}
|
675
|
+
}
|
743
676
|
|
744
677
|
/**
|
745
678
|
* Method to validate an integer question. This checks if the value is bigger
|
746
679
|
* or less than the options provided by the question.
|
747
680
|
*
|
748
681
|
* @method _validateIntegerQuestion
|
749
|
-
* @return {Object
|
750
|
-
* @
|
682
|
+
* @return {Boolean|Object} Returns an object if invalid or true if valid
|
683
|
+
* @private
|
751
684
|
*/
|
752
685
|
_validateIntegerQuestion() {
|
753
|
-
return validate("number", this.
|
686
|
+
return validate("number", this.answer.value, {
|
754
687
|
integer: true,
|
755
|
-
gte: this.
|
756
|
-
lte: this.
|
688
|
+
gte: this.question.raw.integerMinValue || Number.NEGATIVE_INFINITY,
|
689
|
+
lte: this.question.raw.integerMaxValue || Number.POSITIVE_INFINITY,
|
757
690
|
});
|
758
|
-
}
|
691
|
+
}
|
759
692
|
|
760
693
|
/**
|
761
694
|
* Method to validate a float question. This checks if the value is bigger or
|
762
695
|
* less than the options provided by the question.
|
763
696
|
*
|
764
697
|
* @method _validateFloatQuestion
|
765
|
-
* @return {Object
|
766
|
-
* @
|
698
|
+
* @return {Boolean|Object} Returns an object if invalid or true if valid
|
699
|
+
* @private
|
767
700
|
*/
|
768
701
|
_validateFloatQuestion() {
|
769
|
-
return validate("number", this.
|
770
|
-
gte: this.
|
771
|
-
lte: this.
|
702
|
+
return validate("number", this.answer.value, {
|
703
|
+
gte: this.question.raw.floatMinValue || Number.NEGATIVE_INFINITY,
|
704
|
+
lte: this.question.raw.floatMaxValue || Number.POSITIVE_INFINITY,
|
772
705
|
});
|
773
|
-
}
|
706
|
+
}
|
774
707
|
|
775
708
|
/**
|
776
709
|
* Method to validate a radio question. This checks if the value is included
|
777
710
|
* in the provided options of the question.
|
778
711
|
*
|
779
712
|
* @method _validateChoiceQuestion
|
780
|
-
* @return {Object
|
781
|
-
* @
|
713
|
+
* @return {Boolean|Object} Returns an object if invalid or true if valid
|
714
|
+
* @private
|
782
715
|
*/
|
783
716
|
_validateChoiceQuestion() {
|
784
|
-
return validate("inclusion", this.
|
717
|
+
return validate("inclusion", this.answer.value, {
|
785
718
|
allowBlank: true,
|
786
719
|
in: (this.options || []).map(({ slug }) => slug),
|
787
720
|
});
|
788
|
-
}
|
721
|
+
}
|
789
722
|
|
790
723
|
/**
|
791
724
|
* Method to validate a checkbox question. This checks if the all of the
|
792
725
|
* values are included in the provided options of the question.
|
793
726
|
*
|
794
727
|
* @method _validateMultipleChoiceQuestion
|
795
|
-
* @return {
|
796
|
-
* @
|
728
|
+
* @return {Boolean|Object} Returns an object if invalid or true if valid
|
729
|
+
* @private
|
797
730
|
*/
|
798
731
|
_validateMultipleChoiceQuestion() {
|
799
|
-
const value = this.
|
732
|
+
const value = this.answer.value;
|
800
733
|
if (!value) {
|
801
734
|
return true;
|
802
735
|
}
|
@@ -805,34 +738,34 @@ export default Base.extend({
|
|
805
738
|
in: (this.options || []).map(({ slug }) => slug),
|
806
739
|
})
|
807
740
|
);
|
808
|
-
}
|
741
|
+
}
|
809
742
|
|
810
743
|
/**
|
811
744
|
* Method to validate a radio question. This checks if the value is included
|
812
745
|
* in the provided options of the question.
|
813
746
|
*
|
814
747
|
* @method _validateChoiceQuestion
|
815
|
-
* @return {Object
|
816
|
-
* @
|
748
|
+
* @return {Promise<Boolean|Object>} A promise which resolves into an object if invalid or true if valid
|
749
|
+
* @private
|
817
750
|
*/
|
818
751
|
async _validateDynamicChoiceQuestion() {
|
819
752
|
await this.question.loadDynamicOptions.perform();
|
820
753
|
|
821
|
-
return validate("inclusion", this.
|
754
|
+
return validate("inclusion", this.answer.value, {
|
822
755
|
in: (this.options || []).map(({ slug }) => slug),
|
823
756
|
});
|
824
|
-
}
|
757
|
+
}
|
825
758
|
|
826
759
|
/**
|
827
760
|
* Method to validate a checkbox question. This checks if the all of the
|
828
761
|
* values are included in the provided options of the question.
|
829
762
|
*
|
830
763
|
* @method _validateMultipleChoiceQuestion
|
831
|
-
* @return {
|
832
|
-
* @
|
764
|
+
* @return {Promise<Boolean[]|Object[]|Mixed[]>} A promise which resolves into an array of objects if invalid or true if valid
|
765
|
+
* @private
|
833
766
|
*/
|
834
767
|
async _validateDynamicMultipleChoiceQuestion() {
|
835
|
-
const value = this.
|
768
|
+
const value = this.answer.value;
|
836
769
|
|
837
770
|
if (!value) {
|
838
771
|
return true;
|
@@ -845,45 +778,45 @@ export default Base.extend({
|
|
845
778
|
in: (this.options || []).map(({ slug }) => slug),
|
846
779
|
});
|
847
780
|
});
|
848
|
-
}
|
781
|
+
}
|
849
782
|
|
850
783
|
/**
|
851
784
|
* Dummy method for the validation of file uploads.
|
852
785
|
*
|
853
786
|
* @method _validateFileQuestion
|
854
|
-
* @return {
|
787
|
+
* @return {Boolean} Always returns true
|
855
788
|
* @private
|
856
789
|
*/
|
857
790
|
_validateFileQuestion() {
|
858
|
-
return
|
859
|
-
}
|
791
|
+
return true;
|
792
|
+
}
|
860
793
|
|
861
794
|
/**
|
862
795
|
* Method to validate a date question.
|
863
796
|
*
|
864
797
|
* @method _validateDateQuestion
|
865
798
|
* @return {Object[]|Boolean[]|Mixed[]} Returns per value an object if invalid or true if valid
|
866
|
-
* @
|
799
|
+
* @private
|
867
800
|
*/
|
868
801
|
_validateDateQuestion() {
|
869
|
-
return validate("date", this.
|
802
|
+
return validate("date", this.answer.value, {
|
870
803
|
allowBlank: true,
|
871
804
|
});
|
872
|
-
}
|
805
|
+
}
|
873
806
|
|
874
807
|
/**
|
875
808
|
* Dummy method for the validation of table fields
|
876
809
|
*
|
877
810
|
* @method _validateTableQuestion
|
878
|
-
* @return {
|
811
|
+
* @return {Promise<Boolean|Object>} A promise which resolves into an object if invalid or true if valid
|
879
812
|
* @private
|
880
813
|
*/
|
881
814
|
async _validateTableQuestion() {
|
882
815
|
if (!this.value) return true;
|
883
816
|
|
884
|
-
const rowValidations = await all(
|
817
|
+
const rowValidations = await Promise.all(
|
885
818
|
this.value.map(async (row) => {
|
886
|
-
const validFields = await all(
|
819
|
+
const validFields = await Promise.all(
|
887
820
|
row.fields.map(async (field) => {
|
888
821
|
await field.validate.perform();
|
889
822
|
|
@@ -902,49 +835,49 @@ export default Base.extend({
|
|
902
835
|
value: null,
|
903
836
|
}
|
904
837
|
);
|
905
|
-
}
|
838
|
+
}
|
906
839
|
|
907
840
|
/**
|
908
841
|
* Dummy method for the validation of static fields
|
909
842
|
*
|
910
843
|
* @method _validateStaticQuestion
|
911
|
-
* @return {
|
844
|
+
* @return {Boolean} Always returns true
|
912
845
|
* @private
|
913
846
|
*/
|
914
847
|
_validateStaticQuestion() {
|
915
|
-
return
|
916
|
-
}
|
848
|
+
return true;
|
849
|
+
}
|
917
850
|
|
918
851
|
/**
|
919
852
|
* Dummy method for the validation of form fields
|
920
853
|
*
|
921
854
|
* @method _validateFormQuestion
|
922
|
-
* @return {
|
855
|
+
* @return {Boolean} Always returns true
|
923
856
|
* @private
|
924
857
|
*/
|
925
858
|
_validateFormQuestion() {
|
926
|
-
return
|
927
|
-
}
|
859
|
+
return true;
|
860
|
+
}
|
928
861
|
|
929
862
|
/**
|
930
863
|
* Dummy method for the validation of calculated float fields
|
931
864
|
*
|
932
865
|
* @method _validateCalculatedFloatQuestion
|
933
|
-
* @return {
|
866
|
+
* @return {Boolean} Always returns true
|
934
867
|
* @private
|
935
868
|
*/
|
936
869
|
_validateCalculatedFloatQuestion() {
|
937
|
-
return
|
938
|
-
}
|
870
|
+
return true;
|
871
|
+
}
|
939
872
|
|
940
873
|
/**
|
941
874
|
* Dummy method for the validation of work item button fields
|
942
875
|
*
|
943
876
|
* @method _validateActionButtonQuestion
|
944
|
-
* @return {
|
877
|
+
* @return {Boolean} Always returns true
|
945
878
|
* @private
|
946
879
|
*/
|
947
880
|
_validateActionButtonQuestion() {
|
948
|
-
return
|
949
|
-
}
|
950
|
-
}
|
881
|
+
return true;
|
882
|
+
}
|
883
|
+
}
|