-
Notifications
You must be signed in to change notification settings - Fork 11
/
buildGrid.js
198 lines (173 loc) · 7.94 KB
/
buildGrid.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import { translate } from "./common.js";
export const grid_replace_regex = /\|grid(\!|\?)*\|([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)\|/g;
//NOTE: This function is not used in the current implementation
export function firstFun(event) {
event.preventDefault();
}
//NOTE: This function is not used in the current implementation
export function toggle_grid(event) {
event.preventDefault();
let element = event.target;
let id_regex = /(^.*?)(_sm)?(_\d+$)/;
let tmp = element.id.match(id_regex);
// tmp MUST match!!!
if (!tmp) {
console.error("ERROR in [grid] toggle_grid!!! Bad element id!\n", element);
return;
}
let otherid = tmp[2] ? tmp[1] + tmp[3] : tmp[1] + "_sm" + tmp[3];
let otherElement = document.getElementById(otherid);
otherElement.checked = element.checked;
element.form.value[otherElement.name] = element.form.value[element.name];
const isElementSmall = element.dataset.isSmallGridCell === "1";
const isOtherElementSmall = otherElement.dataset.isSmallGridCell === "1";
if (isElementSmall) {
delete element.form.value[element.name];
}
if (isOtherElementSmall) {
delete element.form.value[otherElement.name];
}
}
function grid_replace_piped_variables(txt){
txt = txt.replace(/\{\$([ue]:)?([^}]+)}/g, (all, type, varid) => {
return `<span data-gridreplacetype=${type == "e:" ? "eval" : "_val"} data-gridreplace=${encodeURIComponent(varid)}></span>`
});
txt = txt.replace(' <span', ' <span')
return txt
}
function grid_text_displayif(original_text){
let question_text = original_text
let dif_regex = /%displayif=([^%]+)%([^%]+)%/g
if (dif_regex.test(question_text)) {
question_text = question_text.replace(dif_regex,(match,p1,p2)=>{
return `<span displayif="${encodeURIComponent(p1)}" class="grid-displayif"> ${p2}</span>`
})
}
return question_text;
}
// Builds the HTML Table for a grid question (radio-selectable multi-option fields).
function buildHtmlTable(grid_obj){
// is there a hard/soft edit?
let gridPrompt = "hardedit='false' softedit='false'";
if (grid_obj.prompt) {
if (grid_obj.prompt === '!') {
gridPrompt = "hardedit='true' softedit='false'";
}
else if (grid_obj.prompt === '?') {
gridPrompt = "hardedit='false' softedit='true'";
}
}
// replace displayif and piped variables...
let shared_text = grid_text_displayif(grid_obj.shared_text)
shared_text = grid_replace_piped_variables(shared_text)
// Begin form and set up accessibility description.
// Ask the main question, then begin the table structure (this semantic HTML helps screen readers).
let grid_html = `
<form ${grid_obj.args} class="container question" data-grid="true" ${gridPrompt} aria-describedby="formDescription" role="form">
<div id="formDescription" class="sr-only" aria-live="polite">Please interact with the table below to answer the questions.</div>
<div>${grid_text_displayif(shared_text)}</div>
<table class="quest-grid table-layout table">`;
// Build the table header row with the question text and response headers. Start with a placeholder for the row header.
grid_html += '<thead class="hr" role="rowgroup"><tr><th class="nr hr"></th>';
grid_obj.responses.forEach((resp) => {
const header_text = resp.text;
grid_html += `<th class="hr" scope="col" data-header="${header_text}">${header_text}</th>`;
});
grid_html += '</tr></thead><tbody role="rowgroup">';
// now lets handle each question...
grid_obj.questions.forEach((question) => {
// check for row-level display if. Then check for displayif inside row text
const displayif = question.displayif ? `data-displayif="${encodeURIComponent(question.displayif)}"` : '';
const piped_question_text = grid_text_displayif(question.question_text);
const question_text = grid_replace_piped_variables(piped_question_text);
// Start the row for the question, then add the row header (question text)
grid_html +=
`<tr role="row" data-question-id="${question.id}" data-gridrow="true" aria-labelledby="qtext${question.id}" ${displayif}>
<th scope="row" id="qtext${question.id}" class="nr">${question_text}</th>`;
// All selectable responses for a given question share the same 'name' attribute to link them as a group
// The label is used as a click target for the radio/checkbox input
grid_obj.responses.forEach((resp, resp_index) => {
grid_html += `
<td class="response" data-question-id="${question.id}" data-header="${resp.text}" role="gridcell">
<input type="${resp.type}" name="${question.id}" id="${question.id}_${resp_index}" value="${resp.value}" data-gridcell="true" data-grid="true">
<label for="${question.id}_${resp_index}" id="label${question.id}_${resp_index}" class="custom-label">${resp.text}</label>
</td>`;
});
// Close the row for the question
grid_html += `</tr>`;
});
grid_html += `</tbody></table>
<div class="container">
<div class="row">
<div class="col-md-3 col-sm-12">
<button type="submit" class="previous w-100" aria-label="Back to the previous section" data-click-type="previous">${translate("backButton")}</button>
</div>
<div class="col-md-6 col-sm-12">
<button type="submit" class="reset w-100" aria-label="Reset answer for this question" data-click-type="reset">${translate("resetAnswerButton")}</button>
</div>
<div class="col-md-3 col-sm-12">
<button type="submit" class="next w-100" aria-label="Go to the next section" data-click-type="next">${translate("nextButton")}</button>
</div>
</div>
</div>
</form>`;
return grid_html;
}
// note the text should contain the entirity of ONE grid!
// the regex for a grid is /\|grid\|([^|]*?)\|([^|]*?)\|([^|]*?)\|
// you can use the /g and then pass it to the function one at a time...
export function parseGrid(text) {
let grid_obj = {};
// look for key elements of the text
// |grid|id=xxx|shared_text|questions|response|
let grid_regex = /\|grid(\!|\?)*\|([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)\|/;
let grid_match = text.match(grid_regex);
if (grid_match) {
grid_obj = {
original: grid_match[0],
prompt: grid_match[1],
args: grid_match[2],
shared_text: grid_match[3],
question_text: grid_match[4],
shared_response: grid_match[5],
questions: [],
responses: [],
};
//need to account for displayif
// first check grid-displayif
let args_regex = /displayif=[\'\"]?((?:[^\'\"].+[^\'\"](?:[^\'\"])))[\"\']?$/mg
grid_obj.args = grid_obj.args.replace(args_regex,(match,group1)=>{
return `displayif=${encodeURIComponent(group1)}`
})
console.log(grid_obj.args)
//let question_regex = /\[([A-Z][A-Z0-9_]*)\](.*?);\s*(?=[\[\]])/g;
let question_regex = /\[([A-Z][A-Z0-9_]*)(,displayif=[^\]]+)?\](.*?)[;\]]/g;
let question_matches = grid_obj.question_text.matchAll(question_regex);
for (const match of question_matches) {
let displayIf = '';
if (match[2]) {
displayIf = match[2].replace(",displayif=", "");
}
let question_text = match[3];
// Issue 403: Dont evaluate the markdown expressions at render time.
// create a span with the markdown. When it's time to display
// the value, then evaluate the markdown.
question_text = grid_replace_piped_variables(question_text)
let question_obj = { id: match[1], question_text: question_text, displayif: encodeURIComponent(displayIf) };
grid_obj.questions.push(question_obj);
}
let rb_cb_regex = /([\[\(])(\w+):([^\]\)]+)[\]\)]/g;
let response_matches = grid_obj.shared_response.matchAll(rb_cb_regex);
if (response_matches) {
for (const match of response_matches) {
grid_obj.responses.push({
is_radio: match[1] == "(",
type: match[1] == "(" ? "radio" : "checkbox",
value: match[2],
text: match[3],
});
}
}
}
return buildHtmlTable(grid_obj);
}