advanced-filter-system 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/AFS.esm.js +184 -4
- package/dist/AFS.esm.js.map +1 -1
- package/dist/AFS.js +184 -4
- package/dist/AFS.js.map +1 -1
- package/dist/AFS.min.js +1 -1
- package/dist/AFS.min.js.map +1 -1
- package/package.json +1 -1
package/dist/AFS.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview Advanced Filter System for DOM elements
|
|
9
|
-
* @version 1.0.
|
|
9
|
+
* @version 1.0.6
|
|
10
10
|
*
|
|
11
11
|
* A flexible and customizable filtering system that supports:
|
|
12
12
|
* - Multiple filtering modes (OR/AND)
|
|
@@ -71,12 +71,23 @@
|
|
|
71
71
|
filterMode: "OR",
|
|
72
72
|
searchKeys: ["title"],
|
|
73
73
|
debounceTime: 300,
|
|
74
|
+
debug: false,
|
|
75
|
+
logLevel: 'info',
|
|
74
76
|
...options
|
|
75
77
|
};
|
|
76
78
|
|
|
79
|
+
// Define logging levels hierarchy
|
|
80
|
+
this.logLevels = {
|
|
81
|
+
error: 0,
|
|
82
|
+
warn: 1,
|
|
83
|
+
info: 2,
|
|
84
|
+
debug: 3
|
|
85
|
+
};
|
|
86
|
+
|
|
77
87
|
// Initialize elements
|
|
78
88
|
this.container = document.querySelector(this.options.containerSelector);
|
|
79
89
|
this.items = document.querySelectorAll(this.options.itemSelector);
|
|
90
|
+
this.sortOrders = {};
|
|
80
91
|
this.filterButtons = document.querySelectorAll(this.options.filterButtonSelector);
|
|
81
92
|
this.searchInput = document.querySelector(this.options.searchInputSelector);
|
|
82
93
|
this.counter = document.querySelector(this.options.counterSelector);
|
|
@@ -88,18 +99,78 @@
|
|
|
88
99
|
this.filterGroups = new Map();
|
|
89
100
|
this.groupMode = "OR"; // Default group mode
|
|
90
101
|
|
|
102
|
+
this.log('debug', 'Initializing AFS with options:', this.options);
|
|
91
103
|
this.init();
|
|
92
104
|
}
|
|
93
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Internal logging method
|
|
108
|
+
* Handles debug message output based on current log level
|
|
109
|
+
*
|
|
110
|
+
* @private
|
|
111
|
+
* @param {string} level - Log level ('error', 'warn', 'info', 'debug')
|
|
112
|
+
* @param {...any} args - Arguments to log
|
|
113
|
+
*/
|
|
114
|
+
log(level) {
|
|
115
|
+
if (!this.options.debug) return;
|
|
116
|
+
const currentLevelValue = this.logLevels[this.options.logLevel];
|
|
117
|
+
const messageLevel = this.logLevels[level];
|
|
118
|
+
if (messageLevel <= currentLevelValue) {
|
|
119
|
+
const timestamp = new Date().toISOString();
|
|
120
|
+
const prefix = `[AFS ${level.toUpperCase()}] ${timestamp}`;
|
|
121
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
122
|
+
args[_key2 - 1] = arguments[_key2];
|
|
123
|
+
}
|
|
124
|
+
switch (level) {
|
|
125
|
+
case 'error':
|
|
126
|
+
console.error(prefix, ...args);
|
|
127
|
+
break;
|
|
128
|
+
case 'warn':
|
|
129
|
+
console.warn(prefix, ...args);
|
|
130
|
+
break;
|
|
131
|
+
case 'info':
|
|
132
|
+
console.info(prefix, ...args);
|
|
133
|
+
break;
|
|
134
|
+
case 'debug':
|
|
135
|
+
console.debug(prefix, ...args);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Configure debug mode settings
|
|
143
|
+
*
|
|
144
|
+
* @public
|
|
145
|
+
* @param {boolean} enabled - Enable or disable debug mode
|
|
146
|
+
* @param {string} [level='info'] - Log level ('error', 'warn', 'info', 'debug')
|
|
147
|
+
*/
|
|
148
|
+
setDebugMode(enabled) {
|
|
149
|
+
let level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'info';
|
|
150
|
+
this.options.debug = enabled;
|
|
151
|
+
if (this.logLevels.hasOwnProperty(level)) {
|
|
152
|
+
this.options.logLevel = level;
|
|
153
|
+
}
|
|
154
|
+
this.log('info', `Debug mode ${enabled ? 'enabled' : 'disabled'} with level: ${level}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
94
157
|
/**
|
|
95
158
|
* Initialize the filter system
|
|
159
|
+
* Sets up styles, events, and initial state
|
|
160
|
+
*
|
|
96
161
|
* @private
|
|
97
162
|
*/
|
|
98
163
|
init() {
|
|
164
|
+
this.log('debug', 'Starting initialization');
|
|
165
|
+
if (!this.container) {
|
|
166
|
+
this.log('error', `Container not found: ${this.options.containerSelector}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
99
169
|
this.addStyles();
|
|
100
170
|
this.bindEvents();
|
|
101
171
|
this.loadFromURL();
|
|
102
172
|
this.updateCounter();
|
|
173
|
+
this.log('info', 'Initialization complete');
|
|
103
174
|
}
|
|
104
175
|
|
|
105
176
|
/**
|
|
@@ -131,6 +202,7 @@
|
|
|
131
202
|
const styleSheet = document.createElement("style");
|
|
132
203
|
styleSheet.textContent = styles;
|
|
133
204
|
document.head.appendChild(styleSheet);
|
|
205
|
+
this.log('debug', 'Styles added to document');
|
|
134
206
|
}
|
|
135
207
|
|
|
136
208
|
/**
|
|
@@ -138,6 +210,7 @@
|
|
|
138
210
|
* @private
|
|
139
211
|
*/
|
|
140
212
|
bindEvents() {
|
|
213
|
+
this.log('debug', 'Binding events');
|
|
141
214
|
this.filterButtons.forEach(button => {
|
|
142
215
|
button.addEventListener("click", () => this.handleFilterClick(button));
|
|
143
216
|
});
|
|
@@ -147,6 +220,7 @@
|
|
|
147
220
|
}, this.options.debounceTime));
|
|
148
221
|
}
|
|
149
222
|
window.addEventListener("popstate", () => this.loadFromURL());
|
|
223
|
+
this.log('debug', 'Events bound successfully');
|
|
150
224
|
}
|
|
151
225
|
|
|
152
226
|
/**
|
|
@@ -156,6 +230,7 @@
|
|
|
156
230
|
*/
|
|
157
231
|
handleFilterClick(button) {
|
|
158
232
|
const filterValue = button.dataset.filter;
|
|
233
|
+
this.log('debug', 'Filter button clicked:', filterValue);
|
|
159
234
|
if (filterValue === "*") {
|
|
160
235
|
this.resetFilters();
|
|
161
236
|
} else {
|
|
@@ -170,6 +245,7 @@
|
|
|
170
245
|
* @private
|
|
171
246
|
*/
|
|
172
247
|
resetFilters() {
|
|
248
|
+
this.log('debug', 'Resetting filters');
|
|
173
249
|
this.filterButtons.forEach(btn => btn.classList.remove(this.options.activeClass));
|
|
174
250
|
this.currentFilters.clear();
|
|
175
251
|
this.currentFilters.add("*");
|
|
@@ -193,6 +269,7 @@
|
|
|
193
269
|
* @param {HTMLElement} button - Filter button element
|
|
194
270
|
*/
|
|
195
271
|
toggleFilter(filterValue, button) {
|
|
272
|
+
this.log('debug', `Toggling filter: ${filterValue}`);
|
|
196
273
|
this.currentFilters.delete("*");
|
|
197
274
|
this.filterButtons[0].classList.remove(this.options.activeClass);
|
|
198
275
|
if (button.classList.contains(this.options.activeClass)) {
|
|
@@ -211,6 +288,62 @@
|
|
|
211
288
|
}
|
|
212
289
|
}
|
|
213
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Sort items based on auto-detecting ASC or DESC for each key
|
|
293
|
+
* @public
|
|
294
|
+
* @param {string} key - The data attribute key to sort by (e.g., 'title', 'price', 'date')
|
|
295
|
+
*/
|
|
296
|
+
sortWithOrder(key) {
|
|
297
|
+
const items = [...this.items];
|
|
298
|
+
|
|
299
|
+
// Check if the current sort order is ASC, default to ASC if undefined
|
|
300
|
+
const currentOrder = this.sortOrders[key] || "asc";
|
|
301
|
+
const newOrder = currentOrder === "asc" ? "desc" : "asc"; // Toggle order
|
|
302
|
+
|
|
303
|
+
// Sort items based on the new order
|
|
304
|
+
items.sort((a, b) => {
|
|
305
|
+
let valueA = a.dataset[key];
|
|
306
|
+
let valueB = b.dataset[key];
|
|
307
|
+
|
|
308
|
+
// Check if values are numeric and convert them to numbers if they are
|
|
309
|
+
const isNumeric = !isNaN(valueA) && !isNaN(valueB);
|
|
310
|
+
if (isNumeric) {
|
|
311
|
+
valueA = parseFloat(valueA);
|
|
312
|
+
valueB = parseFloat(valueB);
|
|
313
|
+
}
|
|
314
|
+
if (newOrder === "asc") {
|
|
315
|
+
return isNumeric ? valueA - valueB : valueA.localeCompare(valueB);
|
|
316
|
+
} else {
|
|
317
|
+
return isNumeric ? valueB - valueA : valueB.localeCompare(valueA);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Update the current sort order for the key
|
|
322
|
+
this.sortOrders[key] = newOrder;
|
|
323
|
+
|
|
324
|
+
// Reorder items in the DOM
|
|
325
|
+
items.forEach(item => this.container.appendChild(item));
|
|
326
|
+
this.log('debug', `Sorting items by ${key} in ${newOrder} order`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Shuffle items randomly and display in the container
|
|
331
|
+
* @public
|
|
332
|
+
*/
|
|
333
|
+
shuffle() {
|
|
334
|
+
const itemsArray = [...this.items];
|
|
335
|
+
|
|
336
|
+
// Shuffle the itemsArray using Fisher-Yates algorithm
|
|
337
|
+
for (let i = itemsArray.length - 1; i > 0; i--) {
|
|
338
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
339
|
+
[itemsArray[i], itemsArray[j]] = [itemsArray[j], itemsArray[i]];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Re-append the shuffled items to the container
|
|
343
|
+
itemsArray.forEach(item => this.container.appendChild(item));
|
|
344
|
+
this.log('debug', 'Shuffling items');
|
|
345
|
+
}
|
|
346
|
+
|
|
214
347
|
/**
|
|
215
348
|
* Apply current filters to items
|
|
216
349
|
* @public
|
|
@@ -222,6 +355,7 @@
|
|
|
222
355
|
filter() {
|
|
223
356
|
// Store the original filter logic
|
|
224
357
|
const standardFilter = () => {
|
|
358
|
+
this.log('debug', 'Applying filters');
|
|
225
359
|
this.visibleItems.clear();
|
|
226
360
|
this.items.forEach(item => {
|
|
227
361
|
if (this.currentFilters.has("*")) {
|
|
@@ -264,6 +398,7 @@
|
|
|
264
398
|
setTimeout(() => {
|
|
265
399
|
this.updateCounter();
|
|
266
400
|
}, this.options.animationDuration);
|
|
401
|
+
this.log('info', `Filter applied. Visible items: ${this.visibleItems.size}`);
|
|
267
402
|
}
|
|
268
403
|
|
|
269
404
|
/**
|
|
@@ -279,12 +414,12 @@
|
|
|
279
414
|
try {
|
|
280
415
|
// Validate inputs
|
|
281
416
|
if (!groupId || !Array.isArray(filters)) {
|
|
282
|
-
|
|
417
|
+
this.log('error', 'Invalid group ID or filters');
|
|
283
418
|
return false;
|
|
284
419
|
}
|
|
285
420
|
const validOperator = operator.toUpperCase();
|
|
286
421
|
if (!["AND", "OR"].includes(validOperator)) {
|
|
287
|
-
|
|
422
|
+
this.log('error', 'Invalid operator:', operator);
|
|
288
423
|
operator = "OR";
|
|
289
424
|
}
|
|
290
425
|
|
|
@@ -302,7 +437,7 @@
|
|
|
302
437
|
}
|
|
303
438
|
return true;
|
|
304
439
|
} catch (error) {
|
|
305
|
-
|
|
440
|
+
this.log('error', 'Error adding filter group:', error);
|
|
306
441
|
return false;
|
|
307
442
|
}
|
|
308
443
|
}
|
|
@@ -433,17 +568,21 @@
|
|
|
433
568
|
* @param {string} query - Search query
|
|
434
569
|
*/
|
|
435
570
|
search(query) {
|
|
571
|
+
this.log('debug', 'Performing search with query:', query);
|
|
436
572
|
this.currentSearch = query.toLowerCase().trim();
|
|
573
|
+
let matches = 0;
|
|
437
574
|
this.items.forEach(item => {
|
|
438
575
|
const searchText = this.options.searchKeys.map(key => item.dataset[key] || "").join(" ").toLowerCase();
|
|
439
576
|
const matchesSearch = this.currentSearch === "" || searchText.includes(this.currentSearch);
|
|
440
577
|
if (matchesSearch) {
|
|
441
578
|
this.showItem(item);
|
|
579
|
+
matches++;
|
|
442
580
|
} else {
|
|
443
581
|
this.hideItem(item);
|
|
444
582
|
}
|
|
445
583
|
});
|
|
446
584
|
this.updateURL();
|
|
585
|
+
this.log('info', `Search complete. Found ${matches} matches`);
|
|
447
586
|
setTimeout(() => {
|
|
448
587
|
this.updateCounter();
|
|
449
588
|
}, this.options.animationDuration);
|
|
@@ -466,6 +605,7 @@
|
|
|
466
605
|
return 0;
|
|
467
606
|
});
|
|
468
607
|
items.forEach(item => this.container.appendChild(item));
|
|
608
|
+
this.log('debug', 'Sorting items by multiple criteria:', criteria);
|
|
469
609
|
}
|
|
470
610
|
|
|
471
611
|
/**
|
|
@@ -488,6 +628,7 @@
|
|
|
488
628
|
setTimeout(() => {
|
|
489
629
|
this.updateCounter();
|
|
490
630
|
}, this.options.animationDuration);
|
|
631
|
+
this.log('debug', `Filtering items by range: ${key} between ${min} and ${max}`);
|
|
491
632
|
}
|
|
492
633
|
|
|
493
634
|
/**
|
|
@@ -510,6 +651,8 @@
|
|
|
510
651
|
* @private
|
|
511
652
|
*/
|
|
512
653
|
updateURL() {
|
|
654
|
+
this.log('debug', 'Updating URL');
|
|
655
|
+
|
|
513
656
|
// If only "*" filter is active or no filters are active, clear the URL
|
|
514
657
|
if (this.currentFilters.size === 0 || this.currentFilters.size === 1 && this.currentFilters.has("*")) {
|
|
515
658
|
window.history.pushState({}, "", window.location.pathname);
|
|
@@ -548,6 +691,7 @@
|
|
|
548
691
|
}
|
|
549
692
|
const newURL = `${window.location.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
|
550
693
|
window.history.pushState({}, "", newURL);
|
|
694
|
+
this.log('debug', 'URL updated:', newURL);
|
|
551
695
|
}
|
|
552
696
|
|
|
553
697
|
/**
|
|
@@ -555,6 +699,7 @@
|
|
|
555
699
|
* @private
|
|
556
700
|
*/
|
|
557
701
|
loadFromURL() {
|
|
702
|
+
this.log('debug', 'Loading state from URL');
|
|
558
703
|
const params = new URLSearchParams(window.location.search);
|
|
559
704
|
|
|
560
705
|
// Load groups if they exist
|
|
@@ -610,6 +755,7 @@
|
|
|
610
755
|
if (search) {
|
|
611
756
|
this.search(search);
|
|
612
757
|
}
|
|
758
|
+
this.log('info', 'State loaded from URL');
|
|
613
759
|
}
|
|
614
760
|
|
|
615
761
|
/**
|
|
@@ -623,6 +769,7 @@
|
|
|
623
769
|
if (this.counter) {
|
|
624
770
|
this.counter.textContent = `Showing ${visible} of ${total}`;
|
|
625
771
|
}
|
|
772
|
+
this.log('debug', `Counter updated: ${visible}/${total}`);
|
|
626
773
|
return {
|
|
627
774
|
total,
|
|
628
775
|
visible
|
|
@@ -788,6 +935,7 @@
|
|
|
788
935
|
mode: this.options.filterMode
|
|
789
936
|
};
|
|
790
937
|
localStorage.setItem(`afs_preset_${presetName}`, JSON.stringify(preset));
|
|
938
|
+
this.log('info', `Preset saved: ${presetName}`);
|
|
791
939
|
}
|
|
792
940
|
|
|
793
941
|
/**
|
|
@@ -871,6 +1019,38 @@
|
|
|
871
1019
|
getActiveFiltersByType(type) {
|
|
872
1020
|
return [...this.currentFilters].filter(filter => filter.startsWith(`${type}:`)).map(filter => filter.split(":")[1]);
|
|
873
1021
|
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Clear all filters, url and search
|
|
1025
|
+
*
|
|
1026
|
+
* @public
|
|
1027
|
+
*/
|
|
1028
|
+
clearAllFilters() {
|
|
1029
|
+
this.currentFilters.clear();
|
|
1030
|
+
this.currentFilters.add("*");
|
|
1031
|
+
this.filter();
|
|
1032
|
+
this.updateURL();
|
|
1033
|
+
|
|
1034
|
+
// Uncheck all checkboxes if any with activeClass
|
|
1035
|
+
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
|
1036
|
+
checkboxes.forEach(checkbox => {
|
|
1037
|
+
if (checkbox.classList.contains(this.options.activeClass)) {
|
|
1038
|
+
checkbox.checked = false;
|
|
1039
|
+
checkbox.classList.remove(this.options.activeClass);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// Clear active on buttons
|
|
1044
|
+
this.filterButtons.forEach(btn => {
|
|
1045
|
+
btn.classList.remove(this.options.activeClass);
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
// Clear search input
|
|
1049
|
+
if (this.searchInput) {
|
|
1050
|
+
this.searchInput.value = "";
|
|
1051
|
+
}
|
|
1052
|
+
this.log('info', 'All filters cleared');
|
|
1053
|
+
}
|
|
874
1054
|
}
|
|
875
1055
|
|
|
876
1056
|
exports.AFS = AFS;
|