@glowgreen/gg-questionnaire-v2 0.0.1
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/bundles/glowgreen-gg-questionnaire-v2.umd.js +2102 -0
- package/bundles/glowgreen-gg-questionnaire-v2.umd.js.map +1 -0
- package/bundles/glowgreen-gg-questionnaire-v2.umd.min.js +2 -0
- package/bundles/glowgreen-gg-questionnaire-v2.umd.min.js.map +1 -0
- package/esm2015/glowgreen-gg-questionnaire-v2.js +11 -0
- package/esm2015/lib/enums/condition-relationship.enum.js +11 -0
- package/esm2015/lib/enums/condition-type.enum.js +15 -0
- package/esm2015/lib/enums/input-type.enum.js +20 -0
- package/esm2015/lib/enums/question-type.enum.js +17 -0
- package/esm2015/lib/enums/repeater-question-type.enum.js +11 -0
- package/esm2015/lib/enums/validation-type.enum.js +15 -0
- package/esm2015/lib/gg-questionnaire-v2.module.js +41 -0
- package/esm2015/lib/interfaces/attachment.js +15 -0
- package/esm2015/lib/interfaces/checklist-item.js +15 -0
- package/esm2015/lib/interfaces/condition-group.js +15 -0
- package/esm2015/lib/interfaces/condition.js +17 -0
- package/esm2015/lib/interfaces/question.js +41 -0
- package/esm2015/lib/interfaces/questionnaire-options.js +21 -0
- package/esm2015/lib/interfaces/questionnaire.js +15 -0
- package/esm2015/lib/interfaces/repeater-question.js +27 -0
- package/esm2015/lib/interfaces/section.js +19 -0
- package/esm2015/lib/interfaces/select-option.js +15 -0
- package/esm2015/lib/interfaces/validator.js +15 -0
- package/esm2015/lib/services/filter.service.js +258 -0
- package/esm2015/lib/services/form-constructor.service.js +245 -0
- package/esm2015/lib/services/questionnaire.service.js +644 -0
- package/esm2015/public_api.js +16 -0
- package/esm5/glowgreen-gg-questionnaire-v2.js +11 -0
- package/esm5/lib/enums/condition-relationship.enum.js +11 -0
- package/esm5/lib/enums/condition-type.enum.js +15 -0
- package/esm5/lib/enums/input-type.enum.js +20 -0
- package/esm5/lib/enums/question-type.enum.js +17 -0
- package/esm5/lib/enums/repeater-question-type.enum.js +11 -0
- package/esm5/lib/enums/validation-type.enum.js +15 -0
- package/esm5/lib/gg-questionnaire-v2.module.js +49 -0
- package/esm5/lib/interfaces/attachment.js +15 -0
- package/esm5/lib/interfaces/checklist-item.js +15 -0
- package/esm5/lib/interfaces/condition-group.js +15 -0
- package/esm5/lib/interfaces/condition.js +17 -0
- package/esm5/lib/interfaces/question.js +41 -0
- package/esm5/lib/interfaces/questionnaire-options.js +21 -0
- package/esm5/lib/interfaces/questionnaire.js +15 -0
- package/esm5/lib/interfaces/repeater-question.js +27 -0
- package/esm5/lib/interfaces/section.js +19 -0
- package/esm5/lib/interfaces/select-option.js +15 -0
- package/esm5/lib/interfaces/validator.js +15 -0
- package/esm5/lib/services/filter.service.js +445 -0
- package/esm5/lib/services/form-constructor.service.js +460 -0
- package/esm5/lib/services/questionnaire.service.js +943 -0
- package/esm5/public_api.js +16 -0
- package/fesm2015/glowgreen-gg-questionnaire-v2.js +1194 -0
- package/fesm2015/glowgreen-gg-questionnaire-v2.js.map +1 -0
- package/fesm5/glowgreen-gg-questionnaire-v2.js +1897 -0
- package/fesm5/glowgreen-gg-questionnaire-v2.js.map +1 -0
- package/glowgreen-gg-questionnaire-v2.d.ts +6 -0
- package/glowgreen-gg-questionnaire-v2.metadata.json +1 -0
- package/lib/enums/condition-relationship.enum.d.ts +4 -0
- package/lib/enums/condition-type.enum.d.ts +8 -0
- package/lib/enums/input-type.enum.d.ts +13 -0
- package/lib/enums/question-type.enum.d.ts +10 -0
- package/lib/enums/repeater-question-type.enum.d.ts +4 -0
- package/lib/enums/validation-type.enum.d.ts +8 -0
- package/lib/gg-questionnaire-v2.module.d.ts +5 -0
- package/lib/interfaces/attachment.d.ts +4 -0
- package/lib/interfaces/checklist-item.d.ts +4 -0
- package/lib/interfaces/condition-group.d.ts +6 -0
- package/lib/interfaces/condition.d.ts +6 -0
- package/lib/interfaces/question.d.ts +24 -0
- package/lib/interfaces/questionnaire-options.d.ts +7 -0
- package/lib/interfaces/questionnaire.d.ts +5 -0
- package/lib/interfaces/repeater-question.d.ts +15 -0
- package/lib/interfaces/section.d.ts +8 -0
- package/lib/interfaces/select-option.d.ts +4 -0
- package/lib/interfaces/validator.d.ts +5 -0
- package/lib/services/filter.service.d.ts +62 -0
- package/lib/services/form-constructor.service.d.ts +78 -0
- package/lib/services/questionnaire.service.d.ts +163 -0
- package/package.json +21 -0
- package/public_api.d.ts +17 -0
|
@@ -0,0 +1,1194 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
2
|
+
import { cloneDeep } from 'lodash';
|
|
3
|
+
import { Injectable, NgModule, defineInjectable, inject, EventEmitter, Inject } from '@angular/core';
|
|
4
|
+
import { FormBuilder, Validators, FormsModule, ReactiveFormsModule, FormArray } from '@angular/forms';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview added by tsickle
|
|
8
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
9
|
+
*/
|
|
10
|
+
/** @enum {string} */
|
|
11
|
+
const QuestionType = {
|
|
12
|
+
Input: 'input',
|
|
13
|
+
Select: 'select',
|
|
14
|
+
Textarea: 'textarea',
|
|
15
|
+
Repeater: 'repeater',
|
|
16
|
+
Checklist: 'checklist',
|
|
17
|
+
Attachment: 'attachment',
|
|
18
|
+
Upload: 'upload',
|
|
19
|
+
Signature: 'signature',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @fileoverview added by tsickle
|
|
24
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
25
|
+
*/
|
|
26
|
+
/** @enum {string} */
|
|
27
|
+
const ConditionType = {
|
|
28
|
+
EqualTo: 'equalTo',
|
|
29
|
+
NotEqualTo: 'notEqualTo',
|
|
30
|
+
LessThan: 'lessThan',
|
|
31
|
+
GreaterThan: 'greaterThan',
|
|
32
|
+
Includes: 'includes',
|
|
33
|
+
Excludes: 'excludes',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @fileoverview added by tsickle
|
|
38
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
39
|
+
*/
|
|
40
|
+
/** @enum {string} */
|
|
41
|
+
const ConditionRelationship = {
|
|
42
|
+
Every: 'every',
|
|
43
|
+
Any: 'any',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @fileoverview added by tsickle
|
|
48
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
49
|
+
*/
|
|
50
|
+
class FilterService {
|
|
51
|
+
constructor() { }
|
|
52
|
+
/**
|
|
53
|
+
* Filters an array of sections based on their condition groups.
|
|
54
|
+
* @author Will Poulson
|
|
55
|
+
* @param {?} sections The array of sections to filter.
|
|
56
|
+
* @param {?} currentQuestionnaireForm The current form data, can be null/undefined.
|
|
57
|
+
* @return {?} An array of filtered sections with their filtered questions.
|
|
58
|
+
*/
|
|
59
|
+
filterSections(sections, currentQuestionnaireForm) {
|
|
60
|
+
/** @type {?} */
|
|
61
|
+
const filteredSections = [];
|
|
62
|
+
for (const section of sections) {
|
|
63
|
+
if (this.meetsConditionGroups(section.conditionGroups, currentQuestionnaireForm)) {
|
|
64
|
+
/** @type {?} */
|
|
65
|
+
const filteredQuestions = [];
|
|
66
|
+
for (const question of section.questions) {
|
|
67
|
+
if (this.meetsConditionGroups(question.conditionGroups, currentQuestionnaireForm)) {
|
|
68
|
+
if (question.repeaterQuestions) {
|
|
69
|
+
for (const repeaterQuestion of question.repeaterQuestions) {
|
|
70
|
+
if (this.meetsConditionGroups(repeaterQuestion.conditionGroups, currentQuestionnaireForm)) ;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
filteredQuestions.push(question);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
section.questions = filteredQuestions;
|
|
77
|
+
filteredSections.push(section);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return filteredSections;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Checks if each condition groups conditions have been met.
|
|
84
|
+
* @author Will Poulson
|
|
85
|
+
* @private
|
|
86
|
+
* @param {?} conditionGroups The array of condition groups.
|
|
87
|
+
* @param {?} currentQuestionnaireForm The current form data, can be null/undefined.
|
|
88
|
+
* @return {?} A boolean stating if the condition groups conditions has been met.
|
|
89
|
+
*/
|
|
90
|
+
meetsConditionGroups(conditionGroups, currentQuestionnaireForm) {
|
|
91
|
+
if (!conditionGroups || conditionGroups.length === 0) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return conditionGroups.every((/**
|
|
95
|
+
* @param {?} x
|
|
96
|
+
* @return {?}
|
|
97
|
+
*/
|
|
98
|
+
(x) => {
|
|
99
|
+
return this.meetsConditionGroup(x, currentQuestionnaireForm) === true;
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Checks if a condition groups conditions have been met.
|
|
104
|
+
* @author Will Poulson
|
|
105
|
+
* @private
|
|
106
|
+
* @param {?} conditionGroup The condition group to check.
|
|
107
|
+
* @param {?} currentQuestionnaireForm The current form data, can be null/undefined.
|
|
108
|
+
* @return {?} A boolean stating if the condition group conditions has been met.
|
|
109
|
+
*/
|
|
110
|
+
meetsConditionGroup(conditionGroup, currentQuestionnaireForm) {
|
|
111
|
+
if (!conditionGroup.conditions || conditionGroup.conditions.length === 0) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
/** @type {?} */
|
|
115
|
+
const results = [];
|
|
116
|
+
for (const condition of conditionGroup.conditions) {
|
|
117
|
+
results.push(this.meetsCondition(condition, currentQuestionnaireForm));
|
|
118
|
+
}
|
|
119
|
+
switch (conditionGroup.relationship) {
|
|
120
|
+
case ConditionRelationship.Every:
|
|
121
|
+
return results.every((/**
|
|
122
|
+
* @param {?} x
|
|
123
|
+
* @return {?}
|
|
124
|
+
*/
|
|
125
|
+
(x) => {
|
|
126
|
+
return x === true;
|
|
127
|
+
}));
|
|
128
|
+
case ConditionRelationship.Any:
|
|
129
|
+
return !!results.find((/**
|
|
130
|
+
* @param {?} x
|
|
131
|
+
* @return {?}
|
|
132
|
+
*/
|
|
133
|
+
(x) => {
|
|
134
|
+
return x === true;
|
|
135
|
+
}));
|
|
136
|
+
default:
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Checks if a condition has been met.
|
|
142
|
+
* @author Will Poulson
|
|
143
|
+
* @private
|
|
144
|
+
* @param {?} condition The condition to check.
|
|
145
|
+
* @param {?} currentQuestionnaireForm The current form data, can be null/undefined.
|
|
146
|
+
* @return {?} A boolean stating if the condition group conditions has been met.
|
|
147
|
+
*/
|
|
148
|
+
meetsCondition(condition, currentQuestionnaireForm) {
|
|
149
|
+
/** @type {?} */
|
|
150
|
+
const v1s = this.getValuesForArray(condition.v1s, currentQuestionnaireForm);
|
|
151
|
+
/** @type {?} */
|
|
152
|
+
const v2s = this.getValuesForArray(condition.v2s, currentQuestionnaireForm);
|
|
153
|
+
/** @type {?} */
|
|
154
|
+
let result = false;
|
|
155
|
+
switch (condition.type) {
|
|
156
|
+
case ConditionType.Excludes:
|
|
157
|
+
result = v1s.filter((/**
|
|
158
|
+
* @param {?} v1
|
|
159
|
+
* @return {?}
|
|
160
|
+
*/
|
|
161
|
+
(v1) => {
|
|
162
|
+
return v2s.indexOf(v1) > -1;
|
|
163
|
+
})).length === 0;
|
|
164
|
+
break;
|
|
165
|
+
case ConditionType.Includes:
|
|
166
|
+
result = v1s.filter((/**
|
|
167
|
+
* @param {?} v1
|
|
168
|
+
* @return {?}
|
|
169
|
+
*/
|
|
170
|
+
(v1) => {
|
|
171
|
+
return v2s.indexOf(v1) > -1;
|
|
172
|
+
})).length !== 0;
|
|
173
|
+
break;
|
|
174
|
+
default:
|
|
175
|
+
for (let v2 of v2s) {
|
|
176
|
+
for (let v1 of v1s) {
|
|
177
|
+
switch (condition.type) {
|
|
178
|
+
case ConditionType.EqualTo:
|
|
179
|
+
v1 = this.getStringForValue(v1);
|
|
180
|
+
v2 = this.getStringForValue(v2);
|
|
181
|
+
if (v1 === v2) {
|
|
182
|
+
result = true;
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case ConditionType.NotEqualTo:
|
|
186
|
+
v1 = this.getStringForValue(v1);
|
|
187
|
+
v2 = this.getStringForValue(v2);
|
|
188
|
+
if (v1 !== v2) {
|
|
189
|
+
result = true;
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
case ConditionType.GreaterThan:
|
|
193
|
+
v1 = parseFloat(v1);
|
|
194
|
+
v2 = parseFloat(v2);
|
|
195
|
+
if (v1 >= v2) {
|
|
196
|
+
result = true;
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
case ConditionType.LessThan:
|
|
200
|
+
v1 = parseFloat(v1);
|
|
201
|
+
v2 = parseFloat(v2);
|
|
202
|
+
if (v1 <= v2) {
|
|
203
|
+
result = true;
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* @private
|
|
214
|
+
* @param {?} value
|
|
215
|
+
* @return {?}
|
|
216
|
+
*/
|
|
217
|
+
getStringForValue(value) {
|
|
218
|
+
switch (value) {
|
|
219
|
+
case null:
|
|
220
|
+
return 'null';
|
|
221
|
+
default:
|
|
222
|
+
return value.toString();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Gets the current values for an array of strings.
|
|
227
|
+
* @author Will Poulson
|
|
228
|
+
* @private
|
|
229
|
+
* @param {?} paths The array of strings to check.
|
|
230
|
+
* @param {?} currentQuestionnaireForm The current form data, can be null/undefined.
|
|
231
|
+
* @return {?} An array of strings/values
|
|
232
|
+
*/
|
|
233
|
+
getValuesForArray(paths, currentQuestionnaireForm) {
|
|
234
|
+
if (currentQuestionnaireForm) {
|
|
235
|
+
/** @type {?} */
|
|
236
|
+
const values = [];
|
|
237
|
+
for (const path of paths) {
|
|
238
|
+
/** @type {?} */
|
|
239
|
+
const value = this.getValueForString(path, currentQuestionnaireForm);
|
|
240
|
+
values.push(value);
|
|
241
|
+
}
|
|
242
|
+
return values;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
return paths;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Gets a current value for a string.
|
|
250
|
+
* @author Will Poulson
|
|
251
|
+
* @private
|
|
252
|
+
* @param {?} path The string value to check.
|
|
253
|
+
* @param {?} currentQuestionnaireForm The current form data, can be null/undefined.
|
|
254
|
+
* @return {?} A string/value.
|
|
255
|
+
*/
|
|
256
|
+
getValueForString(path, currentQuestionnaireForm) {
|
|
257
|
+
if (currentQuestionnaireForm) {
|
|
258
|
+
/** @type {?} */
|
|
259
|
+
const control = currentQuestionnaireForm.get(path);
|
|
260
|
+
return control ? control.value : this.convertStringToValue(path);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
return this.convertStringToValue(path);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Converts a string to a value.
|
|
268
|
+
* Eg. true/false.
|
|
269
|
+
* @author Will Poulson
|
|
270
|
+
* @private
|
|
271
|
+
* @param {?} path The string value to check for conversion;
|
|
272
|
+
* @return {?} Either a string or the converted value.
|
|
273
|
+
*/
|
|
274
|
+
convertStringToValue(path) {
|
|
275
|
+
switch (path) {
|
|
276
|
+
case 'true':
|
|
277
|
+
return true;
|
|
278
|
+
case 'false':
|
|
279
|
+
return false;
|
|
280
|
+
case 'null':
|
|
281
|
+
return null;
|
|
282
|
+
default:
|
|
283
|
+
return path;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
FilterService.decorators = [
|
|
288
|
+
{ type: Injectable, args: [{
|
|
289
|
+
providedIn: 'root'
|
|
290
|
+
},] }
|
|
291
|
+
];
|
|
292
|
+
/** @nocollapse */
|
|
293
|
+
FilterService.ctorParameters = () => [];
|
|
294
|
+
/** @nocollapse */ FilterService.ngInjectableDef = defineInjectable({ factory: function FilterService_Factory() { return new FilterService(); }, token: FilterService, providedIn: "root" });
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @fileoverview added by tsickle
|
|
298
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
299
|
+
*/
|
|
300
|
+
/** @enum {string} */
|
|
301
|
+
const ValidationType = {
|
|
302
|
+
Required: 'required',
|
|
303
|
+
Boolean: 'boolean',
|
|
304
|
+
Number: 'number',
|
|
305
|
+
String: 'string',
|
|
306
|
+
Email: 'email',
|
|
307
|
+
Telephone: 'telephone',
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @fileoverview added by tsickle
|
|
312
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
313
|
+
*/
|
|
314
|
+
class FormConstructorService {
|
|
315
|
+
/**
|
|
316
|
+
* @param {?} fb
|
|
317
|
+
* @param {?} filterService
|
|
318
|
+
*/
|
|
319
|
+
constructor(fb, filterService) {
|
|
320
|
+
this.fb = fb;
|
|
321
|
+
this.filterService = filterService;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Constructs a form group based on the questionnaire data.
|
|
325
|
+
* This form group has been filtered down based on the conditions of the sections and questions.
|
|
326
|
+
* @author Will Poulson
|
|
327
|
+
* @param {?} questionnaire The questionnaire data to build the form group on.
|
|
328
|
+
* @param {?} currentQuestionnaireForm
|
|
329
|
+
* @param {?=} skipFilter
|
|
330
|
+
* @return {?} A filted form group.
|
|
331
|
+
*/
|
|
332
|
+
generateFormsForQuestionnaire(questionnaire, currentQuestionnaireForm, skipFilter) {
|
|
333
|
+
/** @type {?} */
|
|
334
|
+
const sections = skipFilter ?
|
|
335
|
+
questionnaire.sections :
|
|
336
|
+
this.filterService.filterSections(questionnaire.sections, currentQuestionnaireForm);
|
|
337
|
+
/** @type {?} */
|
|
338
|
+
const questionnaireForms = this.generateFormsForSections(sections, currentQuestionnaireForm);
|
|
339
|
+
return { sections: sections, forms: questionnaireForms };
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* @private
|
|
343
|
+
* @param {?} sections
|
|
344
|
+
* @param {?} currentQuestionnaireForm
|
|
345
|
+
* @return {?}
|
|
346
|
+
*/
|
|
347
|
+
generateFormsForSections(sections, currentQuestionnaireForm) {
|
|
348
|
+
/** @type {?} */
|
|
349
|
+
const questionnaireForms = this.fb.group({});
|
|
350
|
+
for (const section of sections) {
|
|
351
|
+
/** @type {?} */
|
|
352
|
+
const sectionForms = this.generateFormsForSection(section, currentQuestionnaireForm);
|
|
353
|
+
questionnaireForms.addControl(section.name, sectionForms);
|
|
354
|
+
}
|
|
355
|
+
return questionnaireForms;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Constructs a form group based on the section data.
|
|
359
|
+
* This form group has been filtered down based on the conditions of the questions.
|
|
360
|
+
* @author Will Poulson
|
|
361
|
+
* @private
|
|
362
|
+
* @param {?} section The section data to build the form group on.
|
|
363
|
+
* @param {?} currentQuestionnaireForm
|
|
364
|
+
* @return {?} A filtered form group.
|
|
365
|
+
*/
|
|
366
|
+
generateFormsForSection(section, currentQuestionnaireForm) {
|
|
367
|
+
/** @type {?} */
|
|
368
|
+
const sectionForms = this.fb.group({});
|
|
369
|
+
for (const question of section.questions) {
|
|
370
|
+
/** @type {?} */
|
|
371
|
+
const questionControl = this.generateControlForQuestion(question, section, currentQuestionnaireForm);
|
|
372
|
+
sectionForms.addControl(question.name, questionControl);
|
|
373
|
+
}
|
|
374
|
+
return sectionForms;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Constructs an astract form control based on the question data.
|
|
378
|
+
* @author Will Poulson
|
|
379
|
+
* @private
|
|
380
|
+
* @param {?} question The question data to build the abstract control on.
|
|
381
|
+
* @param {?} section
|
|
382
|
+
* @param {?} currentQuestionnaireForm
|
|
383
|
+
* @return {?} An abstract control.
|
|
384
|
+
*/
|
|
385
|
+
generateControlForQuestion(question, section, currentQuestionnaireForm) {
|
|
386
|
+
/** @type {?} */
|
|
387
|
+
const convertedValidators = this.convertValidators(question.validators);
|
|
388
|
+
switch (question.type) {
|
|
389
|
+
case QuestionType.Repeater:
|
|
390
|
+
/** @type {?} */
|
|
391
|
+
const currentArray = currentQuestionnaireForm ? ((/** @type {?} */ (currentQuestionnaireForm.get([section.name, question.name])))) : false;
|
|
392
|
+
/** @type {?} */
|
|
393
|
+
const newArray = currentArray ? currentArray.controls : [];
|
|
394
|
+
return this.fb.array(newArray, convertedValidators);
|
|
395
|
+
case QuestionType.Checklist:
|
|
396
|
+
return this.generateGroupForChecklist(question, convertedValidators);
|
|
397
|
+
default:
|
|
398
|
+
return this.fb.control(null, { validators: convertedValidators, updateOn: 'blur' });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Generates the form group for a checklist question.
|
|
403
|
+
* @author Will Poulson
|
|
404
|
+
* @private
|
|
405
|
+
* @param {?} question The question. Must be of type checklist or else null is returned.
|
|
406
|
+
* @param {?} convertedValidators
|
|
407
|
+
* @return {?} A form group.
|
|
408
|
+
*/
|
|
409
|
+
generateGroupForChecklist(question, convertedValidators) {
|
|
410
|
+
if (question.type !== QuestionType.Checklist || question.checklistItems.length === 0) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
/** @type {?} */
|
|
414
|
+
const checklistForms = this.fb.group({}, convertedValidators);
|
|
415
|
+
for (const checklistItem of question.checklistItems) {
|
|
416
|
+
/** @type {?} */
|
|
417
|
+
const checklistItemControl = this.fb.control(false, { updateOn: 'blur' });
|
|
418
|
+
checklistForms.addControl(checklistItem.name, checklistItemControl);
|
|
419
|
+
}
|
|
420
|
+
return checklistForms;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Generates the form template for a repeater question.
|
|
424
|
+
* @author Will Poulson
|
|
425
|
+
* @param {?} question The question. Must be of type repeater or else null is returned.
|
|
426
|
+
* @return {?} A form group.
|
|
427
|
+
*/
|
|
428
|
+
generateFormsForRepeater(question) {
|
|
429
|
+
if (question.type !== QuestionType.Repeater || question.repeaterQuestions.length === 0) {
|
|
430
|
+
console.log('Question isnt a repeater or has no questions, returning null');
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
/** @type {?} */
|
|
434
|
+
const repeaterForms = this.fb.group({});
|
|
435
|
+
for (const repeaterQuestion of question.repeaterQuestions) {
|
|
436
|
+
/** @type {?} */
|
|
437
|
+
const repeaterQuestionControl = this.generateControlForRepeaterQuestion(repeaterQuestion);
|
|
438
|
+
repeaterForms.addControl(repeaterQuestion.name, repeaterQuestionControl);
|
|
439
|
+
}
|
|
440
|
+
return repeaterForms;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Generates a control for a repeaters question.
|
|
444
|
+
* @author Will Poulson
|
|
445
|
+
* @private
|
|
446
|
+
* @param {?} repeaterQuestion A repeater question.
|
|
447
|
+
* @return {?} An abstract control.
|
|
448
|
+
*/
|
|
449
|
+
generateControlForRepeaterQuestion(repeaterQuestion) {
|
|
450
|
+
/** @type {?} */
|
|
451
|
+
const convertedValidators = this.convertValidators(repeaterQuestion.validators);
|
|
452
|
+
return this.fb.control(null, { validators: convertedValidators, updateOn: 'blur' });
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Converts an array of validators into form validators.
|
|
456
|
+
* @author Will Poulson
|
|
457
|
+
* @private
|
|
458
|
+
* @param {?} validators The uncoverted array of validators.
|
|
459
|
+
* @return {?} A convered array of form validators.
|
|
460
|
+
*/
|
|
461
|
+
convertValidators(validators) {
|
|
462
|
+
if (!validators || validators.length === 0) {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
/** @type {?} */
|
|
466
|
+
const convertedValidators = [];
|
|
467
|
+
for (const validator of validators) {
|
|
468
|
+
/** @type {?} */
|
|
469
|
+
const convertedValidator = this.convertValidator(validator);
|
|
470
|
+
if (convertedValidator) {
|
|
471
|
+
convertedValidators.push(convertedValidator);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return convertedValidators;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Converts a single validator into a form validator.
|
|
478
|
+
* @author Will Poulson
|
|
479
|
+
* @private
|
|
480
|
+
* @param {?} validator The unconverted validator.
|
|
481
|
+
* @return {?} A converted form validator.
|
|
482
|
+
*/
|
|
483
|
+
convertValidator(validator) {
|
|
484
|
+
switch (validator.type) {
|
|
485
|
+
case ValidationType.Required:
|
|
486
|
+
return Validators.required;
|
|
487
|
+
default:
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Constructs the forms for repeaters from saved state.
|
|
493
|
+
* @param {?} savedState The saved state to load.
|
|
494
|
+
* @param {?} sections The sections already generated by the form constructor.
|
|
495
|
+
* @param {?} currentQuestionnaireForm
|
|
496
|
+
* @return {?}
|
|
497
|
+
*/
|
|
498
|
+
constructRepeaterFromsFromState(savedState, sections, currentQuestionnaireForm) {
|
|
499
|
+
for (const section of sections) {
|
|
500
|
+
for (const question of section.questions) {
|
|
501
|
+
if (question.type !== QuestionType.Repeater) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
/** @type {?} */
|
|
505
|
+
const repeaterArray = savedState[section.name][question.name];
|
|
506
|
+
if (!repeaterArray || repeaterArray.length === 0) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
/** @type {?} */
|
|
510
|
+
const repeaterFormArray = (/** @type {?} */ (currentQuestionnaireForm.get([section.name, question.name])));
|
|
511
|
+
if (repeaterFormArray.controls.length === repeaterArray.length) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
for (const repeaterItem of repeaterArray) {
|
|
515
|
+
/** @type {?} */
|
|
516
|
+
const repeaterItemTemplate = this.generateFormsForRepeater(question);
|
|
517
|
+
repeaterFormArray.push(repeaterItemTemplate);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
FormConstructorService.decorators = [
|
|
524
|
+
{ type: Injectable, args: [{
|
|
525
|
+
providedIn: 'root'
|
|
526
|
+
},] }
|
|
527
|
+
];
|
|
528
|
+
/** @nocollapse */
|
|
529
|
+
FormConstructorService.ctorParameters = () => [
|
|
530
|
+
{ type: FormBuilder },
|
|
531
|
+
{ type: FilterService }
|
|
532
|
+
];
|
|
533
|
+
/** @nocollapse */ FormConstructorService.ngInjectableDef = defineInjectable({ factory: function FormConstructorService_Factory() { return new FormConstructorService(inject(FormBuilder), inject(FilterService)); }, token: FormConstructorService, providedIn: "root" });
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* @fileoverview added by tsickle
|
|
537
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
538
|
+
*/
|
|
539
|
+
class QuestionnaireService {
|
|
540
|
+
/**
|
|
541
|
+
* @param {?} formConstuctor
|
|
542
|
+
* @param {?} options
|
|
543
|
+
*/
|
|
544
|
+
constructor(formConstuctor, options) {
|
|
545
|
+
this.formConstuctor = formConstuctor;
|
|
546
|
+
this.options = options;
|
|
547
|
+
this.dataChangedEvent = new EventEmitter();
|
|
548
|
+
this.skippable = false;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Loads a questionnaire from data.
|
|
552
|
+
* @author Will Poulson
|
|
553
|
+
* @param {?} questionnaire The questionnaire data to load, often parsed JSON from the editor.
|
|
554
|
+
* @param {?=} savedState The saved state of the questionnaire, often from localstorage.
|
|
555
|
+
* @return {?} null
|
|
556
|
+
*/
|
|
557
|
+
loadQuestionnaire(questionnaire, savedState) {
|
|
558
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
559
|
+
this.originalQuestionnaire = cloneDeep(questionnaire);
|
|
560
|
+
this.currentQuestionnaire = cloneDeep(questionnaire);
|
|
561
|
+
if (savedState && Object.keys(savedState).length > 0) {
|
|
562
|
+
/** @type {?} */
|
|
563
|
+
const newData = this.formConstuctor.generateFormsForQuestionnaire(cloneDeep(this.originalQuestionnaire), this.currentQuestionnaireForm, true);
|
|
564
|
+
this.formConstuctor.constructRepeaterFromsFromState(savedState, newData.sections, newData.forms);
|
|
565
|
+
this.currentQuestionnaireForm = newData.forms;
|
|
566
|
+
this.currentQuestionnaireForm.patchValue(savedState);
|
|
567
|
+
}
|
|
568
|
+
yield this.updateQuestionnaire(savedState);
|
|
569
|
+
for (const section of this.currentQuestionnaire.sections) {
|
|
570
|
+
for (const question of section.questions) {
|
|
571
|
+
/** @type {?} */
|
|
572
|
+
const control = this.currentQuestionnaireForm.get([section.name, question.name]);
|
|
573
|
+
if (!control.value && question.defaultValue) {
|
|
574
|
+
control.setValue(question.defaultValue);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (this.options.skipToFirstSection) {
|
|
579
|
+
this.currentSectionName = Object.keys(this.currentQuestionnaireForm.controls)[0];
|
|
580
|
+
}
|
|
581
|
+
if (this.options.skipToFirstQuestion) {
|
|
582
|
+
this.currentQuestionName = Object.keys(this.currentSection.formGroup.controls)[0];
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Updates the current questionnaire, used when a value is updated to check conditions.
|
|
589
|
+
* @author Will Poulson
|
|
590
|
+
* @param {?=} savedState The saved state of the questionnaire, often from localstorage.
|
|
591
|
+
* @return {?} null
|
|
592
|
+
*/
|
|
593
|
+
updateQuestionnaire(savedState) {
|
|
594
|
+
/** @type {?} */
|
|
595
|
+
const newData = this.formConstuctor.generateFormsForQuestionnaire(cloneDeep(this.originalQuestionnaire), this.currentQuestionnaireForm);
|
|
596
|
+
this.currentQuestionnaireForm = newData.forms;
|
|
597
|
+
this.currentQuestionnaire.sections = newData.sections;
|
|
598
|
+
if (savedState && Object.keys(savedState).length > 0) {
|
|
599
|
+
this.currentQuestionnaireForm.patchValue(savedState);
|
|
600
|
+
}
|
|
601
|
+
if (this.questionnaireValueChangeSubscription) {
|
|
602
|
+
this.questionnaireValueChangeSubscription.unsubscribe();
|
|
603
|
+
}
|
|
604
|
+
if (this.questionValueChangeSubscription) {
|
|
605
|
+
this.questionValueChangeSubscription.unsubscribe();
|
|
606
|
+
}
|
|
607
|
+
this.questionnaireValueChangeSubscription = this.currentQuestionnaireForm.valueChanges.subscribe((/**
|
|
608
|
+
* @return {?}
|
|
609
|
+
*/
|
|
610
|
+
() => {
|
|
611
|
+
this.dataChangedEvent.emit();
|
|
612
|
+
this.updateQuestionnaire(this.currentQuestionnaireForm.getRawValue());
|
|
613
|
+
}));
|
|
614
|
+
if (this.currentQuestion) {
|
|
615
|
+
this.questionValueChangeSubscription = this.currentQuestion.formControl.valueChanges.subscribe((/**
|
|
616
|
+
* @return {?}
|
|
617
|
+
*/
|
|
618
|
+
() => {
|
|
619
|
+
this.clearFields(this.currentQuestion.data.clearfields);
|
|
620
|
+
}));
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* @return {?}
|
|
626
|
+
*/
|
|
627
|
+
get isValid() {
|
|
628
|
+
return this.currentQuestionnaireForm.valid;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* @param {?} name
|
|
632
|
+
* @return {?}
|
|
633
|
+
*/
|
|
634
|
+
sectionValid(name) {
|
|
635
|
+
return this.currentQuestionnaireForm.get(name).valid;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Clears all the fields parsed
|
|
639
|
+
* @author Will Poulson
|
|
640
|
+
* @private
|
|
641
|
+
* @param {?} clearfields An array of strings, paths to clear.
|
|
642
|
+
* @return {?}
|
|
643
|
+
*/
|
|
644
|
+
clearFields(clearfields) {
|
|
645
|
+
if (!clearfields || clearfields.length === 0) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
for (const clearfield of clearfields) {
|
|
649
|
+
/** @type {?} */
|
|
650
|
+
const control = this.currentQuestionnaireForm.get(clearfield);
|
|
651
|
+
if (control) {
|
|
652
|
+
control.reset(undefined);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Unloads the current questionnaire.
|
|
658
|
+
* Useful when exiting a questionnaire page or completing a questionnaire.
|
|
659
|
+
* @author Will Poulson
|
|
660
|
+
* @return {?}
|
|
661
|
+
*/
|
|
662
|
+
unloadQuestionnaire() {
|
|
663
|
+
this.originalQuestionnaire = undefined;
|
|
664
|
+
this.currentQuestionnaireForm = undefined;
|
|
665
|
+
this.currentQuestionnaire = undefined;
|
|
666
|
+
this.currentSectionName = undefined;
|
|
667
|
+
this.currentQuestionName = undefined;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Gets the current selected section.
|
|
671
|
+
* @author Will Poulson
|
|
672
|
+
* @return {?} An object containing the section data and the related form group.
|
|
673
|
+
*/
|
|
674
|
+
get currentSection() {
|
|
675
|
+
if (!this.currentSectionName) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
/** @type {?} */
|
|
679
|
+
const data = this.currentQuestionnaire.sections.find((/**
|
|
680
|
+
* @param {?} x
|
|
681
|
+
* @return {?}
|
|
682
|
+
*/
|
|
683
|
+
(x) => {
|
|
684
|
+
return x.name === this.currentSectionName;
|
|
685
|
+
}));
|
|
686
|
+
if (!data) {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
data: data,
|
|
691
|
+
formGroup: (/** @type {?} */ (this.currentQuestionnaireForm.get(data.name)))
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Gets all the avaialble sections.
|
|
696
|
+
* @author Will Poulson
|
|
697
|
+
* @return {?} An array of all avaialble sections.
|
|
698
|
+
*/
|
|
699
|
+
get availableSections() {
|
|
700
|
+
if (!this.currentQuestionnaire || !this.currentQuestionnaire.sections) {
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
return this.currentQuestionnaire.sections;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Gets the current data for the entire questionnaire.
|
|
707
|
+
* @author Will Poulson
|
|
708
|
+
* @return {?} An object of data.
|
|
709
|
+
*/
|
|
710
|
+
get currentData() {
|
|
711
|
+
return this.currentQuestionnaireForm.getRawValue();
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Gets the current selected question.
|
|
715
|
+
* @author Will Poulson
|
|
716
|
+
* @return {?} An object containing the question data and the related form control.
|
|
717
|
+
*/
|
|
718
|
+
get currentQuestion() {
|
|
719
|
+
if (!this.currentQuestionName) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
/** @type {?} */
|
|
723
|
+
const data = this.currentSection.data.questions.find((/**
|
|
724
|
+
* @param {?} x
|
|
725
|
+
* @return {?}
|
|
726
|
+
*/
|
|
727
|
+
(x) => {
|
|
728
|
+
return x.name === this.currentQuestionName;
|
|
729
|
+
}));
|
|
730
|
+
if (!data) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
data: data,
|
|
735
|
+
formControl: (/** @type {?} */ (this.currentSection.formGroup.get(data.name)))
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Navigates to the next question.
|
|
740
|
+
* If there is no question to navigate to it will reject with a out of bounds exception.
|
|
741
|
+
* If allowSkipRequiredField is false then it will reject if the current question is invalid.
|
|
742
|
+
* If emitSectionFinishEvent is true then it will emit an event if it's navigating past the length of questions.
|
|
743
|
+
* @author Will Poulson
|
|
744
|
+
* @return {?} A promise which will resolve when the question has been navigated to.
|
|
745
|
+
*/
|
|
746
|
+
nextQuestion() {
|
|
747
|
+
return new Promise((/**
|
|
748
|
+
* @param {?} resolve
|
|
749
|
+
* @param {?} reject
|
|
750
|
+
* @return {?}
|
|
751
|
+
*/
|
|
752
|
+
(resolve, reject) => {
|
|
753
|
+
if (!this.options.allowSkipRequiredField) {
|
|
754
|
+
if (!this.currentQuestion.formControl.valid) {
|
|
755
|
+
return reject('Current question is invalid');
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
this.getCurrentQuestionIndex().then((/**
|
|
759
|
+
* @param {?} currentIndex
|
|
760
|
+
* @return {?}
|
|
761
|
+
*/
|
|
762
|
+
(currentIndex) => {
|
|
763
|
+
/** @type {?} */
|
|
764
|
+
const newIndex = currentIndex + 1;
|
|
765
|
+
this.navigateToQuestion(newIndex).then((/**
|
|
766
|
+
* @return {?}
|
|
767
|
+
*/
|
|
768
|
+
() => {
|
|
769
|
+
return resolve();
|
|
770
|
+
})).catch((/**
|
|
771
|
+
* @param {?} error
|
|
772
|
+
* @return {?}
|
|
773
|
+
*/
|
|
774
|
+
(error) => {
|
|
775
|
+
return reject(error);
|
|
776
|
+
}));
|
|
777
|
+
})).catch((/**
|
|
778
|
+
* @param {?} error
|
|
779
|
+
* @return {?}
|
|
780
|
+
*/
|
|
781
|
+
(error) => {
|
|
782
|
+
return reject(error);
|
|
783
|
+
}));
|
|
784
|
+
}));
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Navigates to the previous question.
|
|
788
|
+
* If there is no question to navigate to it will reject with a out of bounds exception.
|
|
789
|
+
* If allowNavigateBackwards is false then it will reject.
|
|
790
|
+
* @author Will Poulson
|
|
791
|
+
* @return {?} A promise which will resolve when the question has been navigated to.
|
|
792
|
+
*/
|
|
793
|
+
prevQuestion() {
|
|
794
|
+
return new Promise((/**
|
|
795
|
+
* @param {?} resolve
|
|
796
|
+
* @param {?} reject
|
|
797
|
+
* @return {?}
|
|
798
|
+
*/
|
|
799
|
+
(resolve, reject) => {
|
|
800
|
+
if (!this.options.allowNavigateBackwards) {
|
|
801
|
+
return reject('This questionnaire does not allow for backwards navigation');
|
|
802
|
+
}
|
|
803
|
+
this.getCurrentQuestionIndex().then((/**
|
|
804
|
+
* @param {?} currentIndex
|
|
805
|
+
* @return {?}
|
|
806
|
+
*/
|
|
807
|
+
(currentIndex) => {
|
|
808
|
+
/** @type {?} */
|
|
809
|
+
const newIndex = currentIndex - 1;
|
|
810
|
+
this.navigateToQuestion(newIndex).then((/**
|
|
811
|
+
* @return {?}
|
|
812
|
+
*/
|
|
813
|
+
() => {
|
|
814
|
+
return resolve();
|
|
815
|
+
})).catch((/**
|
|
816
|
+
* @param {?} error
|
|
817
|
+
* @return {?}
|
|
818
|
+
*/
|
|
819
|
+
(error) => {
|
|
820
|
+
return reject(error);
|
|
821
|
+
}));
|
|
822
|
+
})).catch((/**
|
|
823
|
+
* @param {?} error
|
|
824
|
+
* @return {?}
|
|
825
|
+
*/
|
|
826
|
+
(error) => {
|
|
827
|
+
return reject(error);
|
|
828
|
+
}));
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Gets the current questions index.
|
|
833
|
+
* @author Will Poulson
|
|
834
|
+
* @private
|
|
835
|
+
* @return {?} A promise which resolves a number.
|
|
836
|
+
*/
|
|
837
|
+
getCurrentQuestionIndex() {
|
|
838
|
+
return new Promise((/**
|
|
839
|
+
* @param {?} resolve
|
|
840
|
+
* @param {?} reject
|
|
841
|
+
* @return {?}
|
|
842
|
+
*/
|
|
843
|
+
(resolve, reject) => {
|
|
844
|
+
/** @type {?} */
|
|
845
|
+
const currentIndex = this.currentSection.data.questions.indexOf(this.currentQuestion.data);
|
|
846
|
+
if (currentIndex === -1) {
|
|
847
|
+
return reject('Could not find current question');
|
|
848
|
+
}
|
|
849
|
+
return resolve(currentIndex);
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Checks a question is in bounds then navigates to it.
|
|
854
|
+
* @author Will Poulson
|
|
855
|
+
* @param {?} index The index to navigate to.
|
|
856
|
+
* @return {?} A promise which will resolve when the question has been navigated to.
|
|
857
|
+
*/
|
|
858
|
+
navigateToQuestion(index) {
|
|
859
|
+
return new Promise((/**
|
|
860
|
+
* @param {?} resolve
|
|
861
|
+
* @param {?} reject
|
|
862
|
+
* @return {?}
|
|
863
|
+
*/
|
|
864
|
+
(resolve, reject) => {
|
|
865
|
+
if (this.currentSection.data.questions.length - 1 < index || index < 0) {
|
|
866
|
+
return reject('Out of bounds exception');
|
|
867
|
+
}
|
|
868
|
+
this.currentQuestionName = this.currentSection.data.questions[index].name;
|
|
869
|
+
return resolve();
|
|
870
|
+
}));
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Navigates to the next section.
|
|
874
|
+
* @author Will Poulson
|
|
875
|
+
* @return {?} A promise which will resolve when the section has been navigated to.
|
|
876
|
+
*/
|
|
877
|
+
nextSection() {
|
|
878
|
+
return new Promise((/**
|
|
879
|
+
* @param {?} resolve
|
|
880
|
+
* @param {?} reject
|
|
881
|
+
* @return {?}
|
|
882
|
+
*/
|
|
883
|
+
(resolve, reject) => {
|
|
884
|
+
this.getCurrentSectionIndex().then((/**
|
|
885
|
+
* @param {?} currentIndex
|
|
886
|
+
* @return {?}
|
|
887
|
+
*/
|
|
888
|
+
(currentIndex) => {
|
|
889
|
+
/** @type {?} */
|
|
890
|
+
const newIndex = currentIndex + 1;
|
|
891
|
+
this.navigateToSection(newIndex).then((/**
|
|
892
|
+
* @return {?}
|
|
893
|
+
*/
|
|
894
|
+
() => {
|
|
895
|
+
return resolve();
|
|
896
|
+
})).catch((/**
|
|
897
|
+
* @param {?} error
|
|
898
|
+
* @return {?}
|
|
899
|
+
*/
|
|
900
|
+
(error) => {
|
|
901
|
+
return reject(error);
|
|
902
|
+
}));
|
|
903
|
+
})).catch((/**
|
|
904
|
+
* @param {?} error
|
|
905
|
+
* @return {?}
|
|
906
|
+
*/
|
|
907
|
+
(error) => {
|
|
908
|
+
return reject(error);
|
|
909
|
+
}));
|
|
910
|
+
}));
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Navigates to the previous section.
|
|
914
|
+
* @author Will Poulson
|
|
915
|
+
* @return {?} A promise which will resolve when the section has been navigated to.
|
|
916
|
+
*/
|
|
917
|
+
prevSection() {
|
|
918
|
+
return new Promise((/**
|
|
919
|
+
* @param {?} resolve
|
|
920
|
+
* @param {?} reject
|
|
921
|
+
* @return {?}
|
|
922
|
+
*/
|
|
923
|
+
(resolve, reject) => {
|
|
924
|
+
this.getCurrentSectionIndex().then((/**
|
|
925
|
+
* @param {?} currentIndex
|
|
926
|
+
* @return {?}
|
|
927
|
+
*/
|
|
928
|
+
(currentIndex) => {
|
|
929
|
+
/** @type {?} */
|
|
930
|
+
const newIndex = currentIndex - 1;
|
|
931
|
+
this.navigateToQuestion(newIndex).then((/**
|
|
932
|
+
* @return {?}
|
|
933
|
+
*/
|
|
934
|
+
() => {
|
|
935
|
+
return resolve();
|
|
936
|
+
})).catch((/**
|
|
937
|
+
* @param {?} error
|
|
938
|
+
* @return {?}
|
|
939
|
+
*/
|
|
940
|
+
(error) => {
|
|
941
|
+
return reject(error);
|
|
942
|
+
}));
|
|
943
|
+
})).catch((/**
|
|
944
|
+
* @param {?} error
|
|
945
|
+
* @return {?}
|
|
946
|
+
*/
|
|
947
|
+
(error) => {
|
|
948
|
+
return reject(error);
|
|
949
|
+
}));
|
|
950
|
+
}));
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Gets the current section index.
|
|
954
|
+
* @author Will Poulson
|
|
955
|
+
* @private
|
|
956
|
+
* @return {?} A promise which resolves a number.
|
|
957
|
+
*/
|
|
958
|
+
getCurrentSectionIndex() {
|
|
959
|
+
return new Promise((/**
|
|
960
|
+
* @param {?} resolve
|
|
961
|
+
* @param {?} reject
|
|
962
|
+
* @return {?}
|
|
963
|
+
*/
|
|
964
|
+
(resolve, reject) => {
|
|
965
|
+
/** @type {?} */
|
|
966
|
+
const currentIndex = this.currentQuestionnaire.sections.indexOf(this.currentSection.data);
|
|
967
|
+
if (currentIndex === -1) {
|
|
968
|
+
return reject('Could not find current section');
|
|
969
|
+
}
|
|
970
|
+
return resolve(currentIndex);
|
|
971
|
+
}));
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Checks a section is in bounds then navigates to it.
|
|
975
|
+
* @author Will Poulson
|
|
976
|
+
* @param {?} index The index to navigate to.
|
|
977
|
+
* @return {?} A promise which will resolve when the section has been navigated to.
|
|
978
|
+
*/
|
|
979
|
+
navigateToSection(index) {
|
|
980
|
+
return new Promise((/**
|
|
981
|
+
* @param {?} resolve
|
|
982
|
+
* @param {?} reject
|
|
983
|
+
* @return {?}
|
|
984
|
+
*/
|
|
985
|
+
(resolve, reject) => {
|
|
986
|
+
if (this.currentQuestionnaire.sections.length - 1 < index || index < 0) {
|
|
987
|
+
return reject('Out of bounds exception');
|
|
988
|
+
}
|
|
989
|
+
this.currentSectionName = this.currentQuestionnaire.sections[index].name;
|
|
990
|
+
return resolve();
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Answers the current question with the parsed value.
|
|
995
|
+
* @author Will Poulson
|
|
996
|
+
* @param {?} answer The value to answer the question with.
|
|
997
|
+
* @param {?=} sendToNextQuestion
|
|
998
|
+
* @return {?}
|
|
999
|
+
*/
|
|
1000
|
+
answerCurrentQuestion(answer, sendToNextQuestion) {
|
|
1001
|
+
this.currentQuestion.formControl.setValue(answer);
|
|
1002
|
+
if (sendToNextQuestion && this.skippable) { // Timeout to prevent skipping glitch.
|
|
1003
|
+
this.skippable = false;
|
|
1004
|
+
setTimeout((/**
|
|
1005
|
+
* @return {?}
|
|
1006
|
+
*/
|
|
1007
|
+
() => {
|
|
1008
|
+
this.nextQuestion();
|
|
1009
|
+
this.skippable = true;
|
|
1010
|
+
}), 150);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Adds a repeater item to the current question.
|
|
1015
|
+
* Current question must be of type Repeater.
|
|
1016
|
+
* @author Will Poulson
|
|
1017
|
+
* @return {?} A promise which resolves when the repeater item has been added.
|
|
1018
|
+
*/
|
|
1019
|
+
addRepeaterItem() {
|
|
1020
|
+
return new Promise((/**
|
|
1021
|
+
* @param {?} resolve
|
|
1022
|
+
* @param {?} reject
|
|
1023
|
+
* @return {?}
|
|
1024
|
+
*/
|
|
1025
|
+
(resolve, reject) => {
|
|
1026
|
+
if (this.currentQuestion.data.type !== QuestionType.Repeater) {
|
|
1027
|
+
return reject(`Current question isn't a repeater`);
|
|
1028
|
+
}
|
|
1029
|
+
/** @type {?} */
|
|
1030
|
+
const repeaterForm = this.formConstuctor.generateFormsForRepeater(this.currentQuestion.data);
|
|
1031
|
+
if (!repeaterForm) {
|
|
1032
|
+
return reject(`Repeater template failed to generate. May be due to the repeater having no items in the editor.`);
|
|
1033
|
+
}
|
|
1034
|
+
if (!(this.currentQuestion.formControl instanceof FormArray)) {
|
|
1035
|
+
return reject(`The current questions control isn't of type FormArray`);
|
|
1036
|
+
}
|
|
1037
|
+
this.currentQuestion.formControl.push(repeaterForm);
|
|
1038
|
+
return resolve();
|
|
1039
|
+
}));
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Removes a repeater item on the current question.
|
|
1043
|
+
* Current question must be of type Repeater.
|
|
1044
|
+
* @author Will Poulson
|
|
1045
|
+
* @param {?} index The index at which to remove.
|
|
1046
|
+
* @return {?} A promise which resolves when the repeater item has been removed.
|
|
1047
|
+
*/
|
|
1048
|
+
removeRepeaterItem(index) {
|
|
1049
|
+
return new Promise((/**
|
|
1050
|
+
* @param {?} resolve
|
|
1051
|
+
* @param {?} reject
|
|
1052
|
+
* @return {?}
|
|
1053
|
+
*/
|
|
1054
|
+
(resolve, reject) => {
|
|
1055
|
+
if (this.currentQuestion.data.type !== QuestionType.Repeater) {
|
|
1056
|
+
return reject(`Current question isn't a repeater`);
|
|
1057
|
+
}
|
|
1058
|
+
if (!(this.currentQuestion.formControl instanceof FormArray)) {
|
|
1059
|
+
return reject(`The current questions control isn't of type FormArray`);
|
|
1060
|
+
}
|
|
1061
|
+
this.currentQuestion.formControl.removeAt(index);
|
|
1062
|
+
return resolve();
|
|
1063
|
+
}));
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Returns the display text for a given repeater item
|
|
1067
|
+
* @author Will Poulson
|
|
1068
|
+
* @param {?} index The index at which to get the label for.
|
|
1069
|
+
* @return {?} A string which is the display text.
|
|
1070
|
+
*/
|
|
1071
|
+
getRepeaterItemLabel(index) {
|
|
1072
|
+
if (!this.currentQuestion.formControl || !(this.currentQuestion.formControl instanceof FormArray)) {
|
|
1073
|
+
return '';
|
|
1074
|
+
}
|
|
1075
|
+
/** @type {?} */
|
|
1076
|
+
const repeaterItem = this.currentQuestion.formControl.controls[index];
|
|
1077
|
+
if (!repeaterItem) {
|
|
1078
|
+
return '';
|
|
1079
|
+
}
|
|
1080
|
+
/** @type {?} */
|
|
1081
|
+
const template = this.currentQuestion.data.repeaterDisplayName;
|
|
1082
|
+
/** @type {?} */
|
|
1083
|
+
const splitTemplate = template.split(' ');
|
|
1084
|
+
/** @type {?} */
|
|
1085
|
+
const displayArray = [];
|
|
1086
|
+
for (const templateItem of splitTemplate) {
|
|
1087
|
+
if (templateItem.match(/\[.*?\]/)) {
|
|
1088
|
+
/** @type {?} */
|
|
1089
|
+
const path = templateItem.replace(/[\[\]']+/g, '');
|
|
1090
|
+
/** @type {?} */
|
|
1091
|
+
const item = repeaterItem.get(path);
|
|
1092
|
+
/** @type {?} */
|
|
1093
|
+
const value = item.value ? item.value : '?';
|
|
1094
|
+
displayArray.push(value);
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
displayArray.push(templateItem);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
/** @type {?} */
|
|
1101
|
+
const display = displayArray.join(' ');
|
|
1102
|
+
return display;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
QuestionnaireService.decorators = [
|
|
1106
|
+
{ type: Injectable, args: [{
|
|
1107
|
+
providedIn: 'root'
|
|
1108
|
+
},] }
|
|
1109
|
+
];
|
|
1110
|
+
/** @nocollapse */
|
|
1111
|
+
QuestionnaireService.ctorParameters = () => [
|
|
1112
|
+
{ type: FormConstructorService },
|
|
1113
|
+
{ type: undefined, decorators: [{ type: Inject, args: ['options',] }] }
|
|
1114
|
+
];
|
|
1115
|
+
/** @nocollapse */ QuestionnaireService.ngInjectableDef = defineInjectable({ factory: function QuestionnaireService_Factory() { return new QuestionnaireService(inject(FormConstructorService), inject("options")); }, token: QuestionnaireService, providedIn: "root" });
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* @fileoverview added by tsickle
|
|
1119
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
1120
|
+
*/
|
|
1121
|
+
class GgQuestionnaireV2Module {
|
|
1122
|
+
/**
|
|
1123
|
+
* @param {?} options
|
|
1124
|
+
* @return {?}
|
|
1125
|
+
*/
|
|
1126
|
+
static forRoot(options) {
|
|
1127
|
+
return {
|
|
1128
|
+
ngModule: GgQuestionnaireV2Module,
|
|
1129
|
+
providers: [
|
|
1130
|
+
QuestionnaireService,
|
|
1131
|
+
{
|
|
1132
|
+
provide: 'options',
|
|
1133
|
+
useValue: options
|
|
1134
|
+
}
|
|
1135
|
+
]
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
GgQuestionnaireV2Module.decorators = [
|
|
1140
|
+
{ type: NgModule, args: [{
|
|
1141
|
+
declarations: [],
|
|
1142
|
+
providers: [
|
|
1143
|
+
FormConstructorService,
|
|
1144
|
+
FilterService
|
|
1145
|
+
],
|
|
1146
|
+
imports: [
|
|
1147
|
+
FormsModule,
|
|
1148
|
+
ReactiveFormsModule
|
|
1149
|
+
],
|
|
1150
|
+
},] }
|
|
1151
|
+
];
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* @fileoverview added by tsickle
|
|
1155
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
1156
|
+
*/
|
|
1157
|
+
/** @enum {string} */
|
|
1158
|
+
const InputType = {
|
|
1159
|
+
Text: 'text',
|
|
1160
|
+
Number: 'number',
|
|
1161
|
+
Email: 'email',
|
|
1162
|
+
Telephone: 'tel',
|
|
1163
|
+
Password: 'password',
|
|
1164
|
+
Color: 'color',
|
|
1165
|
+
Date: 'date',
|
|
1166
|
+
DateTimeLocal: 'datetime-local',
|
|
1167
|
+
Time: 'time',
|
|
1168
|
+
Month: 'month',
|
|
1169
|
+
Week: 'week',
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* @fileoverview added by tsickle
|
|
1174
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
1175
|
+
*/
|
|
1176
|
+
/** @enum {string} */
|
|
1177
|
+
const RepeaterQuestionType = {
|
|
1178
|
+
Input: 'input',
|
|
1179
|
+
Select: 'select',
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* @fileoverview added by tsickle
|
|
1184
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
1185
|
+
*/
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* @fileoverview added by tsickle
|
|
1189
|
+
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
|
1190
|
+
*/
|
|
1191
|
+
|
|
1192
|
+
export { QuestionnaireService, GgQuestionnaireV2Module, ConditionRelationship, ConditionType, QuestionType, ValidationType, InputType, RepeaterQuestionType, FilterService as ɵb, FormConstructorService as ɵa };
|
|
1193
|
+
|
|
1194
|
+
//# sourceMappingURL=glowgreen-gg-questionnaire-v2.js.map
|