@pie-element/match 10.0.1 → 11.0.3-esm.0
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/configure/package.json +6 -6
- package/controller/package.json +3 -3
- package/esm/configure.js +1396 -23562
- package/esm/configure.js.map +1 -1
- package/esm/controller.js +147 -218
- package/esm/controller.js.map +1 -1
- package/esm/element.js +103 -12199
- package/esm/element.js.map +1 -1
- package/package.json +6 -6
package/esm/controller.js
CHANGED
|
@@ -2,9 +2,27 @@ import isEqual from 'lodash/isEqual';
|
|
|
2
2
|
import isEmpty from 'lodash/isEmpty';
|
|
3
3
|
import cloneDeep from 'lodash/cloneDeep';
|
|
4
4
|
import { getFeedbackForCorrectness } from '@pie-lib/feedback';
|
|
5
|
-
import {
|
|
5
|
+
import { lockChoices, getShuffledChoices, partialScoring } from '@pie-lib/controller-utils';
|
|
6
6
|
import debug from 'debug';
|
|
7
7
|
|
|
8
|
+
function _extends() {
|
|
9
|
+
_extends = Object.assign || function (target) {
|
|
10
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
11
|
+
var source = arguments[i];
|
|
12
|
+
|
|
13
|
+
for (var key in source) {
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
15
|
+
target[key] = source[key];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return target;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return _extends.apply(this, arguments);
|
|
24
|
+
}
|
|
25
|
+
|
|
8
26
|
function _objectWithoutPropertiesLoose(source, excluded) {
|
|
9
27
|
if (source == null) return {};
|
|
10
28
|
var target = {};
|
|
@@ -20,76 +38,6 @@ function _objectWithoutPropertiesLoose(source, excluded) {
|
|
|
20
38
|
return target;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
function _objectWithoutProperties(source, excluded) {
|
|
24
|
-
if (source == null) return {};
|
|
25
|
-
var target = _objectWithoutPropertiesLoose(source, excluded);
|
|
26
|
-
var key, i;
|
|
27
|
-
|
|
28
|
-
if (Object.getOwnPropertySymbols) {
|
|
29
|
-
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
|
30
|
-
|
|
31
|
-
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
|
32
|
-
key = sourceSymbolKeys[i];
|
|
33
|
-
if (excluded.indexOf(key) >= 0) continue;
|
|
34
|
-
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
|
35
|
-
target[key] = source[key];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return target;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
43
|
-
try {
|
|
44
|
-
var info = gen[key](arg);
|
|
45
|
-
var value = info.value;
|
|
46
|
-
} catch (error) {
|
|
47
|
-
reject(error);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (info.done) {
|
|
52
|
-
resolve(value);
|
|
53
|
-
} else {
|
|
54
|
-
Promise.resolve(value).then(_next, _throw);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function _asyncToGenerator(fn) {
|
|
59
|
-
return function () {
|
|
60
|
-
var self = this,
|
|
61
|
-
args = arguments;
|
|
62
|
-
return new Promise(function (resolve, reject) {
|
|
63
|
-
var gen = fn.apply(self, args);
|
|
64
|
-
|
|
65
|
-
function _next(value) {
|
|
66
|
-
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function _throw(err) {
|
|
70
|
-
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
_next(undefined);
|
|
74
|
-
});
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function _defineProperty(obj, key, value) {
|
|
79
|
-
if (key in obj) {
|
|
80
|
-
Object.defineProperty(obj, key, {
|
|
81
|
-
value: value,
|
|
82
|
-
enumerable: true,
|
|
83
|
-
configurable: true,
|
|
84
|
-
writable: true
|
|
85
|
-
});
|
|
86
|
-
} else {
|
|
87
|
-
obj[key] = value;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return obj;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
41
|
var defaults = {
|
|
94
42
|
choiceMode: 'radio',
|
|
95
43
|
feedbackEnabled: false,
|
|
@@ -109,29 +57,24 @@ var defaults = {
|
|
|
109
57
|
toolbarEditorPosition: 'bottom'
|
|
110
58
|
};
|
|
111
59
|
|
|
112
|
-
|
|
60
|
+
const _excluded = ["extraCSSRules", "feedbackEnabled", "promptEnabled", "prompt", "lockChoiceOrder"];
|
|
61
|
+
const log = debug('@pie-element:match:controller');
|
|
113
62
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
var getResponseCorrectness = function getResponseCorrectness(model, answers) {
|
|
120
|
-
var env = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
121
|
-
var isPartialScoring = partialScoring.enabled(model, env);
|
|
122
|
-
var rows = model.rows;
|
|
123
|
-
var checkboxMode = model.choiceMode === 'checkbox';
|
|
63
|
+
const getResponseCorrectness = (model, answers, env = {}) => {
|
|
64
|
+
const isPartialScoring = partialScoring.enabled(model, env);
|
|
65
|
+
const rows = model.rows;
|
|
66
|
+
const checkboxMode = model.choiceMode === 'checkbox';
|
|
124
67
|
|
|
125
68
|
if (!answers || Object.keys(answers).length === 0) {
|
|
126
69
|
return 'unanswered';
|
|
127
70
|
}
|
|
128
71
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
72
|
+
const totalCorrectAnswers = checkboxMode ? getTotalCorrectAnswers(model) : getTotalCorrect(model);
|
|
73
|
+
let correctAnswers;
|
|
74
|
+
let incorrectAnswers = 0;
|
|
132
75
|
|
|
133
76
|
if (checkboxMode) {
|
|
134
|
-
|
|
77
|
+
const checkboxes = getCheckboxes(rows, answers);
|
|
135
78
|
correctAnswers = checkboxes.correctAnswers;
|
|
136
79
|
incorrectAnswers = checkboxes.incorrectAnswers;
|
|
137
80
|
} else {
|
|
@@ -149,19 +92,17 @@ var getResponseCorrectness = function getResponseCorrectness(model, answers) {
|
|
|
149
92
|
return 'incorrect';
|
|
150
93
|
};
|
|
151
94
|
|
|
152
|
-
|
|
153
|
-
var answers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
154
|
-
|
|
95
|
+
const getCorrectness = (question, env, answers = {}) => {
|
|
155
96
|
if (env.mode === 'evaluate') {
|
|
156
97
|
return getResponseCorrectness(question, answers, env);
|
|
157
98
|
}
|
|
158
99
|
};
|
|
159
100
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
101
|
+
const getCheckboxes = (rows, answers) => {
|
|
102
|
+
let correctAnswers = 0;
|
|
103
|
+
let incorrectAnswers = 0;
|
|
163
104
|
rows.forEach(row => {
|
|
164
|
-
|
|
105
|
+
const answer = answers[row.id];
|
|
165
106
|
|
|
166
107
|
if (answer) {
|
|
167
108
|
row.values.forEach((v, i) => {
|
|
@@ -179,8 +120,8 @@ var getCheckboxes = (rows, answers) => {
|
|
|
179
120
|
};
|
|
180
121
|
};
|
|
181
122
|
|
|
182
|
-
|
|
183
|
-
|
|
123
|
+
const getCorrectRadios = (rows, answers) => {
|
|
124
|
+
let correctAnswers = 0;
|
|
184
125
|
rows.forEach(row => {
|
|
185
126
|
if (isEqual(row.values, answers[row.id])) {
|
|
186
127
|
correctAnswers += 1;
|
|
@@ -189,14 +130,14 @@ var getCorrectRadios = (rows, answers) => {
|
|
|
189
130
|
return correctAnswers;
|
|
190
131
|
};
|
|
191
132
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
133
|
+
const getTotalCorrect = question => {
|
|
134
|
+
const checkboxMode = question.choiceMode === 'checkbox';
|
|
135
|
+
const matchingTable = checkboxMode ? question.layout - 1 : 1;
|
|
195
136
|
return (question.rows.length || 0) * matchingTable;
|
|
196
137
|
};
|
|
197
138
|
|
|
198
|
-
|
|
199
|
-
|
|
139
|
+
const getTotalCorrectAnswers = question => {
|
|
140
|
+
let noOfTotalCorrectAnswers = 0;
|
|
200
141
|
question.rows.forEach(row => {
|
|
201
142
|
row.values.forEach(value => {
|
|
202
143
|
if (value) {
|
|
@@ -207,41 +148,38 @@ var getTotalCorrectAnswers = question => {
|
|
|
207
148
|
return noOfTotalCorrectAnswers;
|
|
208
149
|
};
|
|
209
150
|
|
|
210
|
-
|
|
211
|
-
|
|
151
|
+
const getPartialScore = (question, answers) => {
|
|
152
|
+
const checkboxMode = question.choiceMode === 'checkbox';
|
|
212
153
|
|
|
213
154
|
if (checkboxMode) {
|
|
214
|
-
|
|
155
|
+
const {
|
|
215
156
|
correctAnswers,
|
|
216
157
|
incorrectAnswers
|
|
217
158
|
} = getCheckboxes(question.rows, answers);
|
|
218
|
-
|
|
219
|
-
|
|
159
|
+
const totalCorrect = getTotalCorrectAnswers(question);
|
|
160
|
+
const total = totalCorrect === 0 ? 1 : totalCorrect;
|
|
220
161
|
|
|
221
162
|
if (correctAnswers + incorrectAnswers > totalCorrect) {
|
|
222
|
-
|
|
223
|
-
|
|
163
|
+
const extraAnswers = correctAnswers + incorrectAnswers - totalCorrect;
|
|
164
|
+
const score = parseFloat(((correctAnswers - extraAnswers) / total).toFixed(2));
|
|
224
165
|
return score < 0 ? 0 : score;
|
|
225
166
|
} else {
|
|
226
167
|
return parseFloat((correctAnswers / total).toFixed(2));
|
|
227
168
|
}
|
|
228
169
|
} else {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return parseFloat((_correctAnswers / _totalCorrect).toFixed(2));
|
|
170
|
+
const correctAnswers = getCorrectRadios(question.rows, answers);
|
|
171
|
+
const totalCorrect = getTotalCorrect(question) === 0 ? 1 : getTotalCorrect(question);
|
|
172
|
+
return parseFloat((correctAnswers / totalCorrect).toFixed(2));
|
|
234
173
|
}
|
|
235
174
|
};
|
|
236
175
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
var isPartialScoring = partialScoring.enabled(question, env);
|
|
176
|
+
const getOutComeScore = (question, env, answers = {}) => {
|
|
177
|
+
const correctness = getCorrectness(question, env, answers);
|
|
178
|
+
const isPartialScoring = partialScoring.enabled(question, env);
|
|
241
179
|
return correctness === 'correct' ? 1 : correctness === 'partial' && isPartialScoring ? getPartialScore(question, answers) : 0;
|
|
242
180
|
};
|
|
243
181
|
|
|
244
|
-
|
|
182
|
+
const outcome = (question, session, env) => {
|
|
245
183
|
return new Promise(resolve => {
|
|
246
184
|
if (env.mode !== 'evaluate') {
|
|
247
185
|
resolve({
|
|
@@ -256,20 +194,19 @@ var outcome = (question, session, env) => {
|
|
|
256
194
|
});
|
|
257
195
|
}
|
|
258
196
|
|
|
259
|
-
|
|
197
|
+
const out = {
|
|
260
198
|
score: getOutComeScore(question, env, session.answers)
|
|
261
199
|
};
|
|
262
200
|
resolve(out);
|
|
263
201
|
}
|
|
264
202
|
});
|
|
265
203
|
};
|
|
266
|
-
function createDefaultModel() {
|
|
267
|
-
var model = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
204
|
+
function createDefaultModel(model = {}) {
|
|
268
205
|
return new Promise(resolve => {
|
|
269
|
-
resolve(
|
|
206
|
+
resolve(_extends({}, defaults, model));
|
|
270
207
|
});
|
|
271
208
|
}
|
|
272
|
-
|
|
209
|
+
const normalize = question => _extends({}, defaults, question);
|
|
273
210
|
/**
|
|
274
211
|
*
|
|
275
212
|
* @param {*} question
|
|
@@ -279,90 +216,84 @@ var normalize = question => _objectSpread(_objectSpread({}, defaults), question)
|
|
|
279
216
|
*/
|
|
280
217
|
|
|
281
218
|
function model(question, session, env, updateSession) {
|
|
282
|
-
return new Promise(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
var correctness, score;
|
|
286
|
-
|
|
287
|
-
if ((!session || isEmpty(session)) && env.mode === 'evaluate') {
|
|
288
|
-
correctness = 'unanswered';
|
|
289
|
-
score = '0%';
|
|
290
|
-
} else {
|
|
291
|
-
correctness = getCorrectness(normalizedQuestion, env, session && session.answers);
|
|
292
|
-
score = "".concat(getOutComeScore(normalizedQuestion, env, session && session.answers) * 100, "%");
|
|
293
|
-
}
|
|
219
|
+
return new Promise(async resolve => {
|
|
220
|
+
const normalizedQuestion = cloneDeep(normalize(question));
|
|
221
|
+
let correctness, score;
|
|
294
222
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
223
|
+
if ((!session || isEmpty(session)) && env.mode === 'evaluate') {
|
|
224
|
+
correctness = 'unanswered';
|
|
225
|
+
score = '0%';
|
|
226
|
+
} else {
|
|
227
|
+
correctness = getCorrectness(normalizedQuestion, env, session && session.answers);
|
|
228
|
+
score = `${getOutComeScore(normalizedQuestion, env, session && session.answers) * 100}%`;
|
|
229
|
+
}
|
|
301
230
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
231
|
+
const correctResponse = {};
|
|
232
|
+
const correctInfo = {
|
|
233
|
+
score,
|
|
234
|
+
correctness
|
|
235
|
+
};
|
|
236
|
+
const lockChoiceOrder = lockChoices(normalizedQuestion, session, env);
|
|
305
237
|
|
|
306
|
-
|
|
307
|
-
|
|
238
|
+
if (!lockChoiceOrder) {
|
|
239
|
+
normalizedQuestion.rows = await getShuffledChoices(normalizedQuestion.rows, session, updateSession, 'id');
|
|
240
|
+
}
|
|
308
241
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
242
|
+
normalizedQuestion.rows.forEach(row => {
|
|
243
|
+
correctResponse[row.id] = row.values;
|
|
244
|
+
|
|
245
|
+
if (env.mode !== 'evaluate') {
|
|
246
|
+
delete row.values;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const fb = env.mode === 'evaluate' && normalizedQuestion.feedbackEnabled ? getFeedbackForCorrectness(correctInfo.correctness, normalizedQuestion.feedback) : Promise.resolve(undefined);
|
|
250
|
+
fb.then(feedback => {
|
|
251
|
+
const {
|
|
252
|
+
extraCSSRules,
|
|
253
|
+
feedbackEnabled,
|
|
254
|
+
promptEnabled,
|
|
255
|
+
prompt,
|
|
256
|
+
lockChoiceOrder
|
|
257
|
+
} = normalizedQuestion,
|
|
258
|
+
essentials = _objectWithoutPropertiesLoose(normalizedQuestion, _excluded);
|
|
259
|
+
|
|
260
|
+
const out = _extends({}, essentials, {
|
|
261
|
+
extraCSSRules,
|
|
262
|
+
allowFeedback: feedbackEnabled,
|
|
263
|
+
prompt: promptEnabled ? prompt : null,
|
|
264
|
+
shuffled: !lockChoiceOrder,
|
|
265
|
+
feedback,
|
|
266
|
+
disabled: env.mode !== 'gather',
|
|
267
|
+
view: env.mode === 'view'
|
|
312
268
|
});
|
|
313
|
-
var fb = env.mode === 'evaluate' && normalizedQuestion.feedbackEnabled ? getFeedbackForCorrectness(correctInfo.correctness, normalizedQuestion.feedback) : Promise.resolve(undefined);
|
|
314
|
-
fb.then(feedback => {
|
|
315
|
-
var {
|
|
316
|
-
extraCSSRules,
|
|
317
|
-
feedbackEnabled,
|
|
318
|
-
promptEnabled,
|
|
319
|
-
prompt,
|
|
320
|
-
lockChoiceOrder
|
|
321
|
-
} = normalizedQuestion,
|
|
322
|
-
essentials = _objectWithoutProperties(normalizedQuestion, _excluded);
|
|
323
|
-
|
|
324
|
-
var out = _objectSpread(_objectSpread({}, essentials), {}, {
|
|
325
|
-
extraCSSRules,
|
|
326
|
-
allowFeedback: feedbackEnabled,
|
|
327
|
-
prompt: promptEnabled ? prompt : null,
|
|
328
|
-
shuffled: !lockChoiceOrder,
|
|
329
|
-
feedback,
|
|
330
|
-
disabled: env.mode !== 'gather',
|
|
331
|
-
view: env.mode === 'view'
|
|
332
|
-
});
|
|
333
269
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
270
|
+
if (env.role === 'instructor' && (env.mode === 'view' || env.mode === 'evaluate')) {
|
|
271
|
+
out.teacherInstructions = normalizedQuestion.teacherInstructionsEnabled ? normalizedQuestion.teacherInstructions : null;
|
|
272
|
+
out.rationale = normalizedQuestion.rationaleEnabled ? normalizedQuestion.rationale : null;
|
|
273
|
+
} else {
|
|
274
|
+
out.rationale = null;
|
|
275
|
+
out.teacherInstructions = null;
|
|
276
|
+
}
|
|
341
277
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
278
|
+
if (env.mode === 'evaluate') {
|
|
279
|
+
Object.assign(out, {
|
|
280
|
+
correctResponse,
|
|
281
|
+
correctness: correctInfo
|
|
282
|
+
});
|
|
283
|
+
}
|
|
348
284
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
});
|
|
285
|
+
log('out: ', out);
|
|
286
|
+
resolve(out);
|
|
352
287
|
});
|
|
353
|
-
|
|
354
|
-
return function (_x) {
|
|
355
|
-
return _ref.apply(this, arguments);
|
|
356
|
-
};
|
|
357
|
-
}());
|
|
288
|
+
});
|
|
358
289
|
}
|
|
359
|
-
|
|
290
|
+
const createCorrectResponseSession = (question, env) => {
|
|
360
291
|
return new Promise(resolve => {
|
|
361
292
|
if (env.mode !== 'evaluate' && env.role === 'instructor') {
|
|
362
|
-
|
|
293
|
+
const {
|
|
363
294
|
rows
|
|
364
295
|
} = question;
|
|
365
|
-
|
|
296
|
+
const answers = {};
|
|
366
297
|
rows.forEach(r => {
|
|
367
298
|
answers[r.id] = r.values;
|
|
368
299
|
});
|
|
@@ -376,20 +307,18 @@ var createCorrectResponseSession = (question, env) => {
|
|
|
376
307
|
});
|
|
377
308
|
}; // remove all html tags
|
|
378
309
|
|
|
379
|
-
|
|
310
|
+
const getInnerText = html => (html || '').replaceAll(/<[^>]*>/g, ''); // remove all html tags except img, iframe and source tag for audio
|
|
380
311
|
|
|
381
312
|
|
|
382
|
-
|
|
313
|
+
const getContent = html => (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '');
|
|
383
314
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
387
|
-
var {
|
|
315
|
+
const validate = (model = {}, config = {}) => {
|
|
316
|
+
const {
|
|
388
317
|
rows,
|
|
389
318
|
choiceMode,
|
|
390
319
|
headers
|
|
391
320
|
} = model;
|
|
392
|
-
|
|
321
|
+
const {
|
|
393
322
|
minQuestions,
|
|
394
323
|
maxQuestions,
|
|
395
324
|
maxLengthQuestionsHeading,
|
|
@@ -397,25 +326,25 @@ var validate = function validate() {
|
|
|
397
326
|
maxLengthAnswers,
|
|
398
327
|
maxLengthFirstColumnHeading
|
|
399
328
|
} = config;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
329
|
+
const rowsErrors = {};
|
|
330
|
+
const columnsErrors = {};
|
|
331
|
+
const errors = {};
|
|
403
332
|
['teacherInstructions', 'prompt', 'rationale'].forEach(field => {
|
|
404
333
|
var _config$field;
|
|
405
334
|
|
|
406
|
-
if ((_config$field = config[field])
|
|
335
|
+
if ((_config$field = config[field]) != null && _config$field.required && !getContent(model[field])) {
|
|
407
336
|
errors[field] = 'This field is required.';
|
|
408
337
|
}
|
|
409
338
|
});
|
|
410
339
|
|
|
411
340
|
if (rows.length < minQuestions) {
|
|
412
|
-
errors.noOfRowsError =
|
|
341
|
+
errors.noOfRowsError = `There should be at least ${minQuestions} question rows.`;
|
|
413
342
|
} else if (rows.length > maxQuestions) {
|
|
414
|
-
errors.noOfRowsError =
|
|
343
|
+
errors.noOfRowsError = `No more than ${maxQuestions} question rows should be defined.`;
|
|
415
344
|
}
|
|
416
345
|
|
|
417
346
|
(rows || []).forEach((row, index) => {
|
|
418
|
-
|
|
347
|
+
const {
|
|
419
348
|
id,
|
|
420
349
|
values = [],
|
|
421
350
|
title
|
|
@@ -423,21 +352,21 @@ var validate = function validate() {
|
|
|
423
352
|
rowsErrors[id] = '';
|
|
424
353
|
|
|
425
354
|
if (maxLengthQuestionsHeading && getInnerText(title).length > maxLengthQuestionsHeading) {
|
|
426
|
-
rowsErrors[id] +=
|
|
355
|
+
rowsErrors[id] += `Content length should be maximum ${maxLengthQuestionsHeading} characters. `;
|
|
427
356
|
}
|
|
428
357
|
|
|
429
358
|
if (!getContent(title)) {
|
|
430
359
|
rowsErrors[id] += 'Content should not be empty. ';
|
|
431
360
|
} else {
|
|
432
361
|
// check for identical content with the previous answers
|
|
433
|
-
|
|
362
|
+
const identicalAnswer = rows.slice(0, index).some(r => getContent(r.title) === getContent(title));
|
|
434
363
|
|
|
435
364
|
if (identicalAnswer) {
|
|
436
365
|
rowsErrors[id] += 'Content should be unique. ';
|
|
437
366
|
}
|
|
438
367
|
}
|
|
439
368
|
|
|
440
|
-
|
|
369
|
+
const hasCorrectResponse = values.some(value => !!value);
|
|
441
370
|
|
|
442
371
|
if (!hasCorrectResponse) {
|
|
443
372
|
rowsErrors[id] += 'No correct response defined.';
|
|
@@ -445,48 +374,48 @@ var validate = function validate() {
|
|
|
445
374
|
});
|
|
446
375
|
|
|
447
376
|
if (maxAnswers && headers.length - 1 > maxAnswers) {
|
|
448
|
-
errors.columnsLengthError =
|
|
377
|
+
errors.columnsLengthError = `There should be maximum ${maxAnswers} answers.`;
|
|
449
378
|
}
|
|
450
379
|
|
|
451
380
|
if (maxLengthFirstColumnHeading && headers[0].length > maxLengthFirstColumnHeading) {
|
|
452
|
-
columnsErrors[0] =
|
|
381
|
+
columnsErrors[0] = `Content length should be maximum ${maxLengthFirstColumnHeading} characters.`;
|
|
453
382
|
}
|
|
454
383
|
|
|
455
|
-
|
|
384
|
+
const headersContent = (headers || []).map(heading => getContent(heading));
|
|
456
385
|
headersContent.shift(); // remove first column since it does not require validation
|
|
457
386
|
|
|
458
387
|
headersContent.forEach((heading, index) => {
|
|
459
|
-
|
|
388
|
+
const headerIndex = index + 1; // we need to add 1 because we removed first header from validation
|
|
460
389
|
|
|
461
390
|
columnsErrors[headerIndex] = '';
|
|
462
391
|
|
|
463
392
|
if (maxLengthAnswers && getInnerText(heading).length > maxLengthAnswers) {
|
|
464
|
-
columnsErrors[headerIndex] +=
|
|
393
|
+
columnsErrors[headerIndex] += `Content length should be maximum ${maxLengthAnswers} characters. `;
|
|
465
394
|
}
|
|
466
395
|
|
|
467
396
|
if (!heading) {
|
|
468
397
|
columnsErrors[headerIndex] += 'Content should not be empty.';
|
|
469
398
|
} else {
|
|
470
399
|
// check for identical content with the previous headers
|
|
471
|
-
|
|
400
|
+
const identicalAnswer = headersContent.slice(0, index).some(head => head === heading);
|
|
472
401
|
|
|
473
402
|
if (identicalAnswer) {
|
|
474
403
|
columnsErrors[index + 1] += 'Content should be unique.';
|
|
475
404
|
}
|
|
476
405
|
}
|
|
477
406
|
});
|
|
478
|
-
|
|
407
|
+
const hasRowErrors = Object.values(rowsErrors).some(error => (error || '').length);
|
|
479
408
|
|
|
480
409
|
if (hasRowErrors) {
|
|
481
410
|
errors.rowsErrors = rowsErrors;
|
|
482
|
-
|
|
411
|
+
const noCorrectAnswer = Object.values(rowsErrors).some(error => (error || '').includes('No correct response defined.'));
|
|
483
412
|
|
|
484
413
|
if (noCorrectAnswer) {
|
|
485
414
|
errors.correctResponseError = choiceMode === 'radio' ? 'There should be a correct response defined for every row.' : 'There should be at least one correct response defined for every row.';
|
|
486
415
|
}
|
|
487
416
|
}
|
|
488
417
|
|
|
489
|
-
|
|
418
|
+
const hasColumnErrors = Object.values(columnsErrors).some(error => (error || '').length);
|
|
490
419
|
|
|
491
420
|
if (hasColumnErrors) {
|
|
492
421
|
errors.columnsErrors = columnsErrors;
|