-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreateConceptsTable.js
284 lines (251 loc) · 9.86 KB
/
createConceptsTable.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
/**
* "createConceptsTables.js"
* is a module that creates the concepts table and the child row with concept details.
* It uses the DataTables library to create the tables and the dataFetcher module to fetch the data from the API.
* The module exports a function that creates the concepts table and returns a function
* that updates the concepts table with new data.
* The module also exports a function that creates the child row, when a concept row is clicked.
* The module uses the createCodesTable module to create the codes table in the modal.
*/
import { fetchAndFilterArtefactsCollection } from "./dataFetcher.js";
import {
getStorageData,
createSelectElement,
createDropdownLabel,
createFlexContainer,
addChangeListenerToSelect,
addOptionsToSelect,
extractLabelFromDsId,
} from "./utils.js";
import { sessionKeys } from "./sessionStorageKeys.js";
import { createCodesTable } from "./createCodesTable.js";
/**
* Initializes the concepts table with the provided concept data for rows.
* @param {Array} conceptDataForRows - The data to populate the table rows.
* @returns {Function} - An update function to update the parent data table.
*/
export async function initializeConceptsTable(conceptDataForRows) {
const placeholder = document.querySelector("#concepts-table > table");
const dfIdFullList = getStorageData(sessionKeys.dfIdsAll);
const artefactsCollection = await fetchAndFilterArtefactsCollection(dfIdFullList);
const tableHeaders = generateTableHeaderAndFooter(artefactsCollection, dfIdFullList);
const parentTable = new DataTable(placeholder, {
initComplete: function () {
this.api()
.columns()
.every(function (index) {
let column = this;
if (index === 1) {
const id = "generalConceptFilter";
const title = "General Concept Selector";
const select = createSelectElement(title, id).append(new Option("-"));
const label = createDropdownLabel(title, id);
const flexContainer = createFlexContainer().append(label, select);
$("#general-concepts-container").append(flexContainer);
addChangeListenerToSelect(select, column);
addOptionsToSelect(select, column);
}
if (index === 2 || index === 3) {
const title = $(column.footer()).text();
const input = $('<input>').attr('placeholder', title);
$(column.footer()).empty().append(input);
input.on('keyup', function() {
if (column.search() !== this.value) {
column.search(this.value).draw();
}
});
}
if (index >= 4) {
const select = createSelectElement("Data Filter", "").append(new Option(""));
$(column.footer()).empty().append(select);
addChangeListenerToSelect(select, column);
addOptionsToSelect(select, column);
}
});
},
columnDefs: [
{
targets: 0,
className: "dt-control",
orderable: false,
data: null,
},
{ targets: 1, visible: false },
],
data: conceptDataForRows,
columns: tableHeaders,
});
parentTable.on("click", "td.dt-control", async function (e) {
try {
const tr = e.target.closest("tr");
const row = parentTable.row(tr);
if (row.child.isShown()) {
row.child.hide();
} else {
row.child(generateChildRowView(row.data())).show();
attachClickHandlersToDataActionButtons(artefactsCollection);
}
} catch (error) {
console.error('An error occurred:', error);
}
});
function updateParentDataTable(tableRows) {
parentTable.clear();
parentTable.rows.add(tableRows);
parentTable.draw();
setColumnVisibility();
}
function setColumnVisibility() {
parentTable.columns().data().each(function (value, index) {
if (index < 3) return; // Skip the first 3 columns
// Check if all values in the column are empty or undefined
var isEmptyColumn = value.every(function (cellValue) {
return cellValue === "" || cellValue === undefined;
});
if (isEmptyColumn) {
parentTable.column(index).visible(false); // Hide the column
} else {
parentTable.column(index).visible(true); // Show the column
}
});
}
return updateParentDataTable;
}
/**
* Generates the header and footer for a table based on the artefacts collection and a list of dfIds.
* @param {Array} artefactsCollection - The collection of artefacts.
* @param {Array} dfIdFullList - The list of dfIds to generate the table columns.
* @returns {Array} An array of column objects for the table.
*/
function generateTableHeaderAndFooter(artefactsCollection, dfIdFullList) {
const dsIds = dfIdFullList.map((dfId) => {
return artefactsCollection.find((artefact) => artefact.dfId === dfId).dsId;
});
setHeader();
setFooter();
const columns = [null, "conceptRoles", "conceptId", "conceptName", ...dfIdFullList].map((col) => ({
data: col,
defaultContent: "",
}));
return columns;
function setHeader() {
const headers = [
{ title: "" },
{ title: "roles" },
{ title: "Variable ID" },
{ title: "Variable Name", className: "min-width-200" },
...dsIds.map((dsId) => ({
title: extractLabelFromDsId(dsId),
className: "rotate-180",
})),
];
const headerRows = headers
.map(
(header) =>
`<th class="${header.className || "concept-cell"}">${
header.title
}</th>`
)
.join("");
$("#concepts-table > table > thead").append(`<tr>${headerRows}</tr>`);
}
function setFooter() {
const footers = ["", "roles", "ID Search", "Name Search", ...dsIds.map(() => "")];
const footerRows = footers.map((footer) => `<th>${footer}</th>`).join("");
const footerElement = $("#concepts-table > table > tfoot");
footerElement.append(`<tr>${footerRows}</tr>`);
}
}
/**
* Extracts country codes associated with a given concept ID from the constraints collection.
* @param {string} conceptId - The concept ID for which country codes need to be extracted.
* @returns {Array} An array of unique country codes associated with the concept ID.
*/
function extractCountryCodes(conceptId) {
const constraintsCollection = getStorageData(sessionKeys.constraintCollection);
return Array.from(constraintsCollection.reduce((acc, constraints) => {
if (Array.isArray(constraints.countries[conceptId])) {
constraints.countries[conceptId].forEach(geo => acc.add(geo));
}
return acc;
}, new Set()));
}
function buildTableRow(label, value) {
return `<tr><td width="15%">${label}</td><td width="85%">${value}</td></tr>`;
}
/**
* Generates a child row view for a given row data object.
* @param {Object} rowData - The data object containing information about the row.
* @returns {string} - The HTML representation of the child row view.
*/
function generateChildRowView(rowData) {
const {conceptRoles, description, conceptId, conceptName} = rowData;
const constrainedCountryCodes = extractCountryCodes(conceptId);
const uniqueConceptsObject = getStorageData(sessionKeys.uniqueConcepts);
const representation = uniqueConceptsObject[conceptId].representation;
const conceptHasCodes = ["mixed", "codes"].includes(representation);
const conceptHasCodesMixed = representation === "mixed";
const rows = [
buildTableRow("ID", conceptId),
buildTableRow("Name", conceptName),
description !== "no description" && buildTableRow("Description", description),
conceptRoles && buildTableRow("General Concepts", conceptRoles),
conceptHasCodesMixed && buildTableRow("Data Representation", "This variable representation has changed over the time series, i.e. it has both codes and free text as values."),
!conceptHasCodes && buildTableRow("Data Representation", "This variable representation has a free text as values."),
conceptHasCodes && buildTableRow("Code List", createDataActionButtons(["codes"], conceptId)),
constrainedCountryCodes.length > 0 && buildTableRow("Data Constraints", createDataActionButtons(constrainedCountryCodes, conceptId)),
].filter(Boolean);
return `<table class="table table-bordered child-table">${rows.join('')}</table>`;
}
/**
* Creates an array of data action buttons based on the input array and concept ID.
* @param {Array} array - The array of elements to create buttons for.
* @param {string} conceptId - The concept ID to associate with the buttons.
* @returns A string of HTML buttons with data attributes for each element in the array.
*/
function createDataActionButtons(array, conceptId) {
return array
.map(
(el) =>
`<button class="open-modal-button" data-concept-id="${conceptId}" data-country="${el}">${el}</button>`
)
.join("");
}
/**
* Checks if the given element is fully visible within the viewport.
* @param {Element} el - The element to check visibility for.
* @returns {boolean} True if the element is fully visible, false otherwise.
*/
function isElementFullyVisible(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
function attachClickHandlersToDataActionButtons(artefactsCollection) {
const countryButtons = document.querySelectorAll(".open-modal-button");
/** Since the modal is initially hidden, this event listener ensures
* column widths are adjusted upon modal visibility to prevent layout issues.
*/
$('#codes-table-modal').on('shown.bs.modal', function (e) {
var checkVisibility = setInterval(function() {
var table = document.getElementById('codes-table');
if (isElementFullyVisible(table)) {
$('#codes-table').DataTable().columns.adjust();
clearInterval(checkVisibility); // stop checking once the table is fully visible
}
}, 100);
});
countryButtons.forEach((button) => {
button.addEventListener("click", async (e) => {
const country = e.target.dataset.country;
const conceptId = e.target.dataset.conceptId;
createCodesTable(artefactsCollection, conceptId, country);
$(".modal-title").text(`${conceptId} variable code list${country !== "codes" ? ` for "${country}"` : ""}`);
$("#codes-table-modal").modal("show");
});
});
}