Skip to content

Commit dea0759

Browse files
committed
controlled visibility based on foreign key(s) of dependent tables.
moved version over to help menu
1 parent a4daedf commit dea0759

File tree

3 files changed

+108
-63
lines changed

3 files changed

+108
-63
lines changed

lib/AppContext.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ export default class AppContext {
418418
const [class_name, tree_obj] = obj;
419419

420420
// if it shares a key with another class which is its parent, this DH must be a child
421-
const is_child = this.relations?.[class_name]?.parent;
421+
const is_child = this.getParents(class_name);
422422

423423
// Doesn't build if tab id already exists.
424424
const dhId = `data-harmonizer-grid-${index}`;
@@ -497,6 +497,14 @@ export default class AppContext {
497497
return data_harmonizers;
498498
}
499499

500+
getParents(template_name) {
501+
return this.relations?.[template_name]?.parent;
502+
}
503+
504+
getChildren(template_name) {
505+
return this.relations?.[template_name]?.child;
506+
}
507+
500508
// Determine if parent relationships link class_name to given ancestor
501509
isAncestor(class_names, ancestor_name) {
502510
let subject_name = class_names.shift();
@@ -508,12 +516,32 @@ export default class AppContext {
508516
if (this.relations[subject_name].parent[ancestor_name])
509517
return true;
510518
// Ancestor wasn't one of parents, so try search on all ancestors.
511-
//console.log(this.relations[subject_name].parent, Object.values(this.relations[subject_name].parent))
512519
class_names.push(Object.values(this.relations[subject_name].parent));
513520
}
514521
return this.isAncestor(class_names, ancestor_name)
515522
}
516523

524+
/* Retrieves ordered list of tables that ultimately have given template as
525+
* a foreign key. Whether it is to enact cascading visibility, update or
526+
* deletion events, this provides the order in which to trigger changes.
527+
* Issue is that intermediate tables need to be checked in order due to
528+
* dependencies by foreign keys.
529+
* Relying on javascript implicit ordering of dictionary added elements.
530+
*/
531+
getDependents(class_names) {
532+
// Initialization case
533+
if (typeof class_names == "string") {
534+
class_names = {...this.getChildren(class_names)};
535+
}
536+
//
537+
let children = this.getChildren(class_names);
538+
if (children) {
539+
class_names = {...children};
540+
return getDependents(class_names);
541+
}
542+
return class_names;
543+
}
544+
517545
/*
518546
Return initialized, rendered dataHarmonizer instances rendered in order.
519547
If a template name is provided, only include that one in its schema, and any underlings.

lib/DataHarmonizer.js

+77-59
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import '@selectize/selectize';
22
import Handsontable from 'handsontable';
33
import SheetClip from 'sheetclip';
44
import $ from 'jquery';
5+
import {range as arrayRange} from 'lodash';
56
import 'jquery-ui-bundle';
67
import 'jquery-ui/dist/themes/base/jquery-ui.css';
78

@@ -751,11 +752,25 @@ class DataHarmonizer {
751752
}
752753
},
753754
afterSelection: (row, column, row2, column2) => {
755+
// A change in selected row
756+
if (self.current_selection[0] != row) {
757+
// Possibly focused key has changed child table's filter.
758+
// Issue: need to cascade this down immediately to any
759+
// subordinate table, otherwise if doing on-demand from a table's
760+
// perspective, then if such a table depends on an intermediate that
761+
// hasn't been refreshed, we'll get a wrong display.
762+
763+
Object.entries(this.context.getDependents(this.template_name))
764+
.forEach(([child_name, child]) => {
765+
// Dependent tables determine what parent foreign key values they
766+
// need to filter view by
767+
console.log("showing", child_name)
768+
this.showRowsByKeyVals(child_name);
769+
});
770+
};
771+
754772
self.current_selection = [row, column, row2, column2];
755-
// Two possible actions:
756-
// - For each subordinate table send a message to refilter
757773
// - See if sidebar info is required for top column click.
758-
759774
if (this.helpSidebar) {
760775
if (column > -1) {
761776
const field = self.slots[column];
@@ -834,24 +849,48 @@ class DataHarmonizer {
834849
if (startRowIndex === false)
835850
startRowIndex = this.hot.countRows();
836851

837-
console.log("menu",this.template_name, this.context.relations?.[this.template_name]?.parent)
838-
852+
let parents = this.context.getParents(this.template_name);
839853
// If this has no foreign key parent table(s) then go ahead and add x rows.
840-
if (! this.context.relations?.[this.template_name]?.parent) {
854+
if (!parents) {
841855
// Insert the new rows below the last existing row
842856
this.hot.alter(row_where, startRowIndex, numRows);
843857
return;
844858
}
845859

846-
// Here we deal with adding rows that need foreign keys.
847-
// Locate each foreign key and fetch its focused value, and copy into new
848-
// records below.
849-
// If missing key value(s), prompt user to focus appropriate tab(s) row
850-
// and try again.
860+
let [required_selections, errors] = this.getForeignKeyValues(parents);
861+
862+
if (errors) {
863+
// Prompt user to select appropriate parent table row(s) first.
864+
$('#empty-parent-key-modal-info').html(errors);
865+
$('#empty-parent-key-modal').modal('show');
866+
return;
867+
}
868+
869+
this.hot.alter(row_where, startRowIndex, numRows);
851870

871+
// Populate new rows with selected foreign key value(s)
872+
this.hot.batch(() => {
873+
for (let row = startRowIndex; row < startRowIndex + numRows; row++) {
874+
Object.entries(required_selections).forEach(([slot_name, value]) => {
875+
const col = this.getColumnIndexByFieldName(slot_name);
876+
this.hot.setDataAtCell(row, col, value);
877+
});
878+
};
879+
});
880+
881+
}
882+
883+
884+
885+
// Here we deal with adding rows that need foreign keys.
886+
// Locate each foreign key and fetch its focused value, and copy into new
887+
// records below.
888+
// If missing key value(s), prompt user to focus appropriate tab(s) row
889+
// and try again.
890+
getForeignKeyValues(parents) {
852891
let required_selections = {};
853-
let selection_error = '';
854-
Object.entries(this.context.relations?.[this.template_name]?.parent)
892+
let errors = '';
893+
Object.entries(parents)
855894
.forEach(([parent_name, parent]) => {
856895
Object.entries(parent)
857896
.forEach(([slot_name, foreign_slot_name]) => {
@@ -871,33 +910,14 @@ class DataHarmonizer {
871910
}
872911
if (!selected) {
873912
//required_selections[slot_name] = {source: parent_name};
874-
selection_error += `<li> <b>${parent_name}</b> (${foreign_slot_name})</li>`;
913+
errors += `<li> <b>${parent_name}</b> (${foreign_slot_name})</li>`;
875914
}
876915
else {
877916
required_selections[slot_name] = slot_value;
878917
}
879918
});
880919
});
881-
882-
if (selection_error) {
883-
// Prompt user to select appropriate parent table row(s) first.
884-
$('#empty-parent-key-modal-info').html(selection_error);
885-
$('#empty-parent-key-modal').modal('show');
886-
return;
887-
}
888-
889-
this.hot.alter(row_where, startRowIndex, numRows);
890-
891-
// Populate new rows with selected value(s)
892-
this.hot.batch(() => {
893-
for (let row = startRowIndex; row < startRowIndex + numRows; row++) {
894-
Object.entries(required_selections).forEach(([slot_name, value]) => {
895-
const col = this.getColumnIndexByFieldName(slot_name);
896-
this.hot.setDataAtCell(row, col, value);
897-
});
898-
};
899-
});
900-
920+
return [required_selections, errors]
901921
}
902922

903923
getColumnIndexByFieldName(slot_name) {
@@ -910,6 +930,7 @@ class DataHarmonizer {
910930
return -1;
911931
}
912932

933+
913934
/*
914935
// Function to find the nearest index after the last non-empty value
915936
findNearestIndexAfterLastNonEmpty(column) {
@@ -1066,33 +1087,30 @@ class DataHarmonizer {
10661087
filtersPlugin.filter();
10671088
}
10681089

1069-
hideMatchingRows(columnIndex, valueToMatch = null) {
1070-
function resetHiddenRows(hotInstance) {
1071-
const hiddenRowsPlugin = hotInstance.getPlugin('hiddenRows');
1072-
hiddenRowsPlugin.showRows(hiddenRowsPlugin.getHiddenRows()); // Reset any previously hidden rows
1073-
}
1074-
1075-
function getAllRowIndices(hotInstance) {
1076-
const totalRows = hotInstance.countRows();
1077-
return Array.from({ length: totalRows }, (_, i) => i); // Return array of row indices [0, 1, 2, ..., totalRows - 1]
1078-
}
1079-
1080-
const hotInstance = this.hot;
1090+
/* For given template name, show only rows that conform to that class's
1091+
* foreign key-value constraints;
1092+
*/
1093+
showRowsByKeyVals(template_name) {
1094+
const hotInstance = this.context.dhs[template_name].hot;
10811095
const hiddenRowsPlugin = hotInstance.getPlugin('hiddenRows');
1082-
resetHiddenRows(hotInstance); // Reset any previously hidden rows
10831096

1084-
let rowsToHide;
1085-
1086-
if (valueToMatch === null || isEmptyUnitVal(columnIndex)) {
1087-
// Hide all rows if no valueToMatch is provided
1088-
rowsToHide = getAllRowIndices(hotInstance);
1089-
} else {
1090-
// Hide rows that do not match the value at the given columnIndex
1091-
rowsToHide = getAllRowIndices(hotInstance).filter((row) => {
1092-
const cellValue = hotInstance.getDataAtCell(row, columnIndex);
1093-
return cellValue !== valueToMatch;
1094-
});
1095-
}
1097+
// Ensure all rows are visible
1098+
hiddenRowsPlugin.showRows(hiddenRowsPlugin.getHiddenRows());
1099+
1100+
//Fetch key-values to match
1101+
let parents = this.context.getParents(template_name);
1102+
let [required_selections, errors] = this.getForeignKeyValues(parents);
1103+
// arrayRange() makes [0, ... n]
1104+
let rowsToHide = arrayRange(0,hotInstance.countRows())
1105+
.filter((row) => {
1106+
for (let [slot_name, value] of Object.entries(required_selections)) {
1107+
const col = this.context.dhs[template_name].getColumnIndexByFieldName(slot_name);
1108+
const cellValue = hotInstance.getDataAtCell(row, col);
1109+
console.log(template_name,row,slot_name, col,cellValue,value)
1110+
if (cellValue != value)
1111+
return true;
1112+
}
1113+
});
10961114

10971115
hiddenRowsPlugin.hideRows(rowsToHide); // Hide the calculated rows
10981116
hotInstance.render(); // Render the table to apply changes

lib/toolbar.html

+1-2
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,6 @@
144144
data-target="#fill-modal"
145145
>Fill column...</span
146146
>
147-
148-
<span class="dropdown-item disabled" id="version-dropdown-item"></span>
149147
</div>
150148
</div>
151149
<button
@@ -211,6 +209,7 @@
211209
data-i18n="help_contact"
212210
>Contact Us</span
213211
>
212+
<span class="dropdown-item disabled" id="version-dropdown-item"></span>
214213
</div>
215214
</div>
216215

0 commit comments

Comments
 (0)