-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselect-relations.js
More file actions
185 lines (154 loc) · 6.7 KB
/
select-relations.js
File metadata and controls
185 lines (154 loc) · 6.7 KB
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
/*
--------------------------------------------------
@ Select Relations JS @
Version: 2.0.0
Author: Kamran Gasimov
Created: 09.04.2024
Updated: 08.10.2025
© All rights are reserved Deirvlon Technologies.
NEW FEATURES:
- OR logic support using | symbol
- Parenthesis grouping for complex conditions
- Backward compatible with existing & (AND) logic
SYNTAX EXAMPLES:
- AND: "select1:1,2&select2:3"
- OR: "select1:1,2|select2:3"
- Grouping: "(select1:1&select2:2)|(select3:3&select4:4)"
- Mixed: "select1:1&(select2:2|select3:3)"
--------------------------------------------------
*/
function SelectRelations() {
// Expose a global function to manually update relations
window.updateSelectRelations = function () {
initializeFiltering();
};
document.addEventListener('DOMContentLoaded', function () {
initializeFiltering();
});
function initializeFiltering() {
const selectRelations = document.querySelectorAll('.select-relations');
selectRelations.forEach(select => {
// COLD START
updateFiltering(select, false);
// Check if Select2 is initialized
const isSelect2Initialized = $(select).hasClass('select2') !== undefined;
// Add event listener for change event
select.addEventListener('change', function () {
updateFiltering(this);
});
// If Select2 is initialized, also listen to its change event
if (isSelect2Initialized) {
$(select).on('change.select2', function () {
updateFiltering(this);
});
}
});
// Reset parent selects' selected options if they are hidden due to filtering
resetParentSelects();
}
function updateFiltering(select, restart = true) {
const parentId = select.getAttribute('id');
document.querySelectorAll(`[data-sf-parent*="${parentId}"]`).forEach(childSelect => {
if (childSelect.tagName == "SELECT") {
// SELECT INPUT
childSelect.querySelectorAll('option').forEach(option => {
const relationData = option.getAttribute('data-pr');
if (relationData) {
const displayOption = evaluateRelation(relationData);
option.disabled = !displayOption;
option.hidden = !displayOption;
option.ariaHidden = !displayOption;
}
});
} else {
// NORMAL INPUTS
const relationData = childSelect.getAttribute('data-pr');
if (relationData) {
const displayOption = evaluateRelation(relationData);
childSelect.style.display = displayOption ? '' : 'none';
}
}
});
// Reset parent selects' selected options if they are hidden due to filtering
if (restart)
resetParentSelects();
}
/**
* Evaluates a relation string with support for AND (&), OR (|), and parenthesis grouping
* @param {string} relationData - The relation expression to evaluate
* @returns {boolean} - Whether the condition is met
*/
function evaluateRelation(relationData) {
// Remove extra whitespace
relationData = relationData.trim();
// Handle parenthesis grouping
while (relationData.includes('(')) {
relationData = relationData.replace(/\(([^()]+)\)/g, (match, group) => {
return evaluateSimpleExpression(group) ? '1' : '0';
});
}
// After resolving all parentheses, evaluate the final expression
return evaluateSimpleExpression(relationData);
}
/**
* Evaluates a simple expression without parentheses
* Handles OR (|) and AND (&) operators with proper precedence (AND before OR)
* @param {string} expression - Expression without parentheses
* @returns {boolean} - Result of evaluation
*/
function evaluateSimpleExpression(expression) {
// Handle boolean results from parenthesis evaluation
if (expression === '1') return true;
if (expression === '0') return false;
// Split by OR operator (lower precedence)
const orParts = expression.split('|');
// If any OR part is true, return true
return orParts.some(orPart => {
// Split by AND operator (higher precedence)
const andParts = orPart.split('&');
// All AND parts must be true
return andParts.every(andPart => {
andPart = andPart.trim();
// Handle already evaluated boolean values
if (andPart === '1') return true;
if (andPart === '0') return false;
// Evaluate the condition
return evaluateCondition(andPart);
});
});
}
/**
* Evaluates a single condition (selectId:values)
* @param {string} condition - Single condition string
* @returns {boolean} - Whether condition is met
*/
function evaluateCondition(condition) {
const [selectId, selectValues] = condition.split(':');
if (!selectId || !selectValues) return false;
const el_parent = document.getElementById(selectId.trim());
if (!el_parent) return false;
const ids = selectValues.split(',').map(v => v.trim());
if (el_parent.type === "checkbox") {
return ids.includes(el_parent.checked ? '1' : '0');
} else {
const selectedOption = el_parent.options ? el_parent.options[el_parent.selectedIndex] : null;
return ids.includes(el_parent.value) ||
(selectedOption && selectedOption.dataset.alt && ids.includes(selectedOption.dataset.alt));
}
}
function resetParentSelects() {
document.querySelectorAll(`[data-sf-parent]`).forEach(childSelect => {
if (childSelect.tagName == "SELECT") {
// SELECT INPUT
if (childSelect.selectedIndex == null || childSelect.selectedIndex == -1)
return;
let selectOption = childSelect.options[childSelect.selectedIndex];
if (selectOption.disabled) {
$(childSelect).val(null).trigger('change.select2');
}
}
});
}
}
// autorun
SelectRelations(); //Init