-
Notifications
You must be signed in to change notification settings - Fork 0
/
script.js
403 lines (353 loc) · 13.2 KB
/
script.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
let answerClasses = [
"purpose REB-approved",
"purpose QI-QA",
"purpose admin-operations-support",
"purpose non-human-research",
"type qualitative",
"type quantitative",
"type mixed-methods",
"collection surveys",
"collection direct-data-entry",
"collection interview-focus-groups",
"collection import",
"source biospecimen",
"source lab-slides",
"source image-videos",
"source genomics",
"source literature-reviews",
"source other-data-sources"
];
let questionClasses = ["purpose", "type", "collection", "source"];
let softwareData = [
{
purpose: ["REB-approved", "QI-QA"],
type: ["qualitative", "quantitative"],
collection: ["surveys", "direct-data-entry", "interview-focus-groups", "import"],
source: ["genomics", "other-data-sources"],
Name: "RedCap",
Description: "RedCap Description"
},
{
purpose: ["admin-operations-support"],
type: ["quantitative"],
collection: ["surveys", "import"],
source: ["image-videos", "other-data-sources"],
Name: "Qualtrics",
Description: "Qualtrics Description"
}
// Add more data objects as needed
];
// Dynamically compute the number of questions based on the data object
function computeNumQuestions() {
let questionsDiv = document.querySelector('.questions');
let questions = questionsDiv.querySelectorAll('.question');
let numQuestions = questions.length;
return numQuestions;
}
let numQuestions = computeNumQuestions();
// Generate card-like structures dynamically using the data object
function generateCards() {
let cardsContainer = document.querySelector('.cards-container');
softwareData.forEach(function (software) {
let card = document.createElement('div');
card.classList.add('card');
count = 0;
for (let key in software) {
if (count >= numQuestions) {
break;
}
for (let i = 0; i < software[key].length; i++) {
card.classList.add(software[key][i]);
}
count++;
}
let nameElement = document.createElement('h4');
nameElement.textContent = software.Name;
let descriptionElement = document.createElement('p');
descriptionElement.textContent = software.Description;
card.appendChild(nameElement);
card.appendChild(descriptionElement);
cardsContainer.appendChild(card);
});
}
generateCards();
function makePropertiesList(inputArray) {
function makeReadable(inputString) {
const wordsArray = inputString.split('-');
const capitalizedWordsArray = wordsArray.map(word => word.charAt(0).toUpperCase() + word.slice(1));
return capitalizedWordsArray.join(' ');
}
const readableArray = inputArray.map(makeReadable);
return readableArray.join(', ');
}
let cards = document.querySelectorAll(".card");
let infoIfSelected = document.querySelector(".info-if-selected");
let selectedProvidersList = document.querySelector(".selected-data-storage-providers-list");
let step2SelectAllButton = document.querySelector(".step2-select-all");
let step2ClearAnswersButton = document.querySelector(".step2-clear-selections");
let clearAnswersButton = document.querySelector(".clear-answers");
let checkboxes = document.querySelectorAll('input[type="checkbox"]');
let radioButtons = document.querySelectorAll('input[type="radio"]');
cards.forEach(function (card) {
card.addEventListener("click", toggleCardSelection);
});
checkboxes.forEach(function (checkbox) {
checkbox.addEventListener("click", function () {
toggleCardSelectionBasedOnAnswers();
});
});
radioButtons.forEach(function (radio) {
radio.addEventListener("click", function () {
toggleCardSelectionBasedOnAnswers();
});
});
step2SelectAllButton.addEventListener("click", function () {
cards.forEach(function (card) {
if (card.classList.contains("filtered") || checkIfAllAnswersAreUnchecked()) {
card.classList.add("selected");
} else {
card.classList.remove("selected");
}
});
updateSelectedProvidersList();
checkStep3Visibility();
});
step2ClearAnswersButton.addEventListener("click", function () {
cards.forEach(function (card) {
card.classList.remove("selected");
});
updateSelectedProvidersList();
checkStep3Visibility();
});
clearAnswersButton.addEventListener("click", function () {
checkboxes.forEach(function (checkbox) {
checkbox.checked = false;
});
radioButtons.forEach(function (radio) {
radio.checked = false;
});
cards.forEach(function (card) {
card.classList.remove("filtered");
});
updateSelectedProvidersList();
checkStep3Visibility();
});
/**
* The function toggles the selection of cards based on the state of answers and updates the
* selected providers list and step 3 visibility.
*/
function toggleCardSelectionBasedOnAnswers() {
let answerBoolDict = createAnswerBoolDict(questionClasses);
cards.forEach(function (card) {
let cardAnswerDict = createAnswerDict(card, answerClasses);
compareDictsBasedOnFilter(cardAnswerDict, answerBoolDict, filterDict(answerBoolDict), card);
});
updateSelectedProvidersList();
checkStep3Visibility();
}
/**
* The function creates a dictionary where the keys are class names and the values are arrays of
* boolean values indicating whether the corresponding answers are checked or not.
* @param questionClasses - questionClasses is an array of strings representing the classes of the
* questions. Each string in the array should be in the format "questionClass answerClass", where
* "questionClass" is the class name of the question and "answerClass" is the class name of the
* answers associated with that question.
* @returns a dictionary object where the keys are the first elements of the strings in the
* `questionClasses` array, and the values are arrays of boolean values indicating whether the
* corresponding answers with the same name are checked or not.
*/
function createAnswerBoolDict(questionClasses) {
let answerBoolDict = {};
questionClasses.forEach(function (questionClass) {
let classListString = questionClass.split(" ");
answerBoolDict[classListString[0]] = [];
let answers = document.querySelectorAll('input[name="' + classListString[0] + '"');
answers.forEach(function (answer) {
answerBoolDict[classListString[0]].push(answer.checked);
});
});
return answerBoolDict;
}
/**
* The function creates a dictionary mapping answer classes to their corresponding values for a
* given card element.
* @param card - The `card` parameter is an HTML element representing a card.
* @param answerClasses - An array of strings representing the classes of answers.
* @returns a dictionary (answerDict) that contains the answer classes as keys and an array of
* boolean values as values.
*/
function createAnswerDict(card, answerClasses) {
let answerDict = {};
answerClasses.forEach(function (answerClass) {
let classListString = answerClass.split(" ");
let classValue = card.classList.contains(classListString[1]);
if (!(classListString[0] in answerDict)) {
answerDict[classListString[0]] = [];
}
answerDict[classListString[0]].push(classValue);
});
return answerDict;
}
/**
* The function compares two dictionaries based on a filter list and modifies a card element
* accordingly.
* @param dict1 - The first dictionary to compare.
* @param dict2 - dict2 is a dictionary object containing key-value pairs.
* @param filterList - The filterList parameter is an array that contains the keys of the
* properties in the dictionaries that you want to compare.
* @param card - The `card` parameter is a reference to an HTML element that represents a card or
* item in a list.
*/
function compareDictsBasedOnFilter(dict1, dict2, filterList, card) {
let cardRemoved = false;
for (let key in filterList) {
if (implies(dict2[filterList[key]], dict1[filterList[key]]) && !cardRemoved) {
card.classList.add("filtered");
} else {
card.classList.remove("filtered");
card.classList.remove("selected");
cardRemoved = true;
}
}
if (filterList.length == 0) {
card.classList.remove("filtered");
}
}
/**
* The function checks if all answers are unchecked.
* @returns a boolean value indicating whether all answers are unchecked.
*/
function checkIfAllAnswersAreUnchecked() {
let allUnchecked = true;
radioButtons.forEach(function (radio) {
if (radio.checked) {
allUnchecked = false;
}
});
checkboxes.forEach(function (checkbox) {
if (checkbox.checked) {
allUnchecked = false;
}
});
return allUnchecked;
}
/**
* The function toggles the "selected" class on a card element if it is not filtered or if all
* answers are unchecked, and then updates the visibility of step 3 and the selected providers
* list.
*/
function toggleCardSelection() {
if (this.classList.contains("filtered") || checkIfAllAnswersAreUnchecked()) {
this.classList.toggle("selected");
}
checkStep3Visibility();
updateSelectedProvidersList();
}
/**
* The function checks if any cards are selected and changes display property
* of step 3 to block from none and vice-versa.
*/
function checkStep3Visibility() {
let selectedCards = document.querySelectorAll(".card.selected");
infoIfSelected.style.display = selectedCards.length > 0 ? "block" : "none";
}
function updateSelectedProvidersList() {
selectedProvidersList.innerHTML = ""; // Clear the previous content
let selectedCards = document.querySelectorAll(".card.selected");
let tableHTML = `
<div class="table-container">
<table class="list">
<thead>
<tr>
<th></th>`; // Empty first header column
selectedCards.forEach(function (card) {
let name = card.querySelector("h4").textContent;
tableHTML += `<th>${name}</th>`; // Add software names as header columns
});
tableHTML += `
</tr>
</thead>
<tbody>`;
tableHTML += `
<tr>
<td><b>Description</b></td>`; // Add row for descriptions
selectedCards.forEach(function (card) {
let name = card.querySelector("h4").textContent;
softwareData.forEach(function (data) {
if (data.Name === name) {
let description = data.Description;
tableHTML += `<td>${description}</td>`; // Add software-specific description as table cell
}
});
});
questionClasses.forEach(function (questionClass) {
tableHTML += "<tr>";
let question = questionClass.charAt(0).toUpperCase() + questionClass.slice(1)
tableHTML += `<td><b>${question}</b></td>`; // First column represents the question
selectedCards.forEach(function (card) {
let name = card.querySelector("h4").textContent;
softwareData.forEach(function (data) {
if (data.Name === name) {
let answer = data[questionClass] ? data[questionClass] : "";
answer = makePropertiesList(answer)
tableHTML += `<td>${answer}</td>`; // Add software-specific answer as table cell
}
});
});
tableHTML += "</tr>";
});
tableHTML += `
</tbody>
</table>
</div>`;
selectedProvidersList.innerHTML = tableHTML;
}
/**
* The function filterDict filters a dictionary by checking if any of its values are not false.
* @param dict - The parameter `dict` is expected to be an object (dictionary) containing key-value
* pairs.
* @returns a list of keys from the input dictionary that have at least one value that is not
* false.
*/
function filterDict(dict) {
let filterList = [];
for (let key in dict) {
if (!checkAllFalse(dict[key])) {
filterList.push(key);
}
}
return filterList;
}
/**
* The function checks if all elements in a given list are false.
* @param list - An array of values to check.
* @returns a boolean value. It will return true if every element in the list is false, and false
* otherwise.
*/
function checkAllFalse(list) {
return list.every(i => i === false);
}
/**
* The function "implies" checks if every element in list1 implies the corresponding element in
* list2.
* @param list1 - An array of boolean values.
* @param list2 - The parameter "list2" is expected to be an array.
* @returns a boolean value. It returns true if all elements in list1 imply the corresponding
* elements in list2, and false otherwise.
*/
function implies(list1, list2) {
for (let i = 0; i < list1.length; i++) {
if (list1[i] && !list2[i]) {
return false;
}
}
return true;
};
/*
Requirements:
1. Can select filtered cards independently
2. If filter changes and a card is no longer filtered, it should be unselected
3. If any card selected, show step 3 with the info about card
4. If no cards selected, hide step 3
5. All the conditions are in an AND relationship
*/