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/README.md
CHANGED
|
@@ -224,6 +224,22 @@ filter.on('filter', (data) => {
|
|
|
224
224
|
filter.enableKeyboardNavigation();
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
### 13. Sort asc/desc
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
filter.sortWithComparator('price', (a, b) => parseFloat(a) - parseFloat(b), 'asc');
|
|
231
|
+
|
|
232
|
+
// or
|
|
233
|
+
|
|
234
|
+
filter.sortWithOrder('price');
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 13. Shuffle
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
filter.shuffle();
|
|
241
|
+
```
|
|
242
|
+
|
|
227
243
|
## Browser Compatibility
|
|
228
244
|
|
|
229
245
|
- ✅ Chrome
|
|
@@ -249,7 +265,9 @@ const filter = new AFS({
|
|
|
249
265
|
animationDuration: 300,
|
|
250
266
|
filterMode: 'OR',
|
|
251
267
|
searchKeys: ['title'],
|
|
252
|
-
debounceTime: 300
|
|
268
|
+
debounceTime: 300,
|
|
269
|
+
debug: false, // Enable console logs
|
|
270
|
+
logLevel: 'info', // 'info', 'warn', 'error', 'debug'
|
|
253
271
|
});
|
|
254
272
|
```
|
|
255
273
|
|
|
@@ -275,6 +293,8 @@ const filter = new AFS({
|
|
|
275
293
|
- `sortMultiple(criteria)`
|
|
276
294
|
- `sortWithComparator(key, comparator)`
|
|
277
295
|
- `addRangeFilter(key, min, max)`
|
|
296
|
+
- `shuffle()`
|
|
297
|
+
- `sortWithOrder(key)`
|
|
278
298
|
|
|
279
299
|
### State Management
|
|
280
300
|
|
package/dist/AFS.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Advanced Filter System for DOM elements
|
|
3
|
-
* @version 1.0.
|
|
3
|
+
* @version 1.0.6
|
|
4
4
|
*
|
|
5
5
|
* A flexible and customizable filtering system that supports:
|
|
6
6
|
* - Multiple filtering modes (OR/AND)
|
|
@@ -65,12 +65,23 @@ class AFS {
|
|
|
65
65
|
filterMode: "OR",
|
|
66
66
|
searchKeys: ["title"],
|
|
67
67
|
debounceTime: 300,
|
|
68
|
+
debug: false,
|
|
69
|
+
logLevel: 'info',
|
|
68
70
|
...options
|
|
69
71
|
};
|
|
70
72
|
|
|
73
|
+
// Define logging levels hierarchy
|
|
74
|
+
this.logLevels = {
|
|
75
|
+
error: 0,
|
|
76
|
+
warn: 1,
|
|
77
|
+
info: 2,
|
|
78
|
+
debug: 3
|
|
79
|
+
};
|
|
80
|
+
|
|
71
81
|
// Initialize elements
|
|
72
82
|
this.container = document.querySelector(this.options.containerSelector);
|
|
73
83
|
this.items = document.querySelectorAll(this.options.itemSelector);
|
|
84
|
+
this.sortOrders = {};
|
|
74
85
|
this.filterButtons = document.querySelectorAll(this.options.filterButtonSelector);
|
|
75
86
|
this.searchInput = document.querySelector(this.options.searchInputSelector);
|
|
76
87
|
this.counter = document.querySelector(this.options.counterSelector);
|
|
@@ -82,18 +93,78 @@ class AFS {
|
|
|
82
93
|
this.filterGroups = new Map();
|
|
83
94
|
this.groupMode = "OR"; // Default group mode
|
|
84
95
|
|
|
96
|
+
this.log('debug', 'Initializing AFS with options:', this.options);
|
|
85
97
|
this.init();
|
|
86
98
|
}
|
|
87
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Internal logging method
|
|
102
|
+
* Handles debug message output based on current log level
|
|
103
|
+
*
|
|
104
|
+
* @private
|
|
105
|
+
* @param {string} level - Log level ('error', 'warn', 'info', 'debug')
|
|
106
|
+
* @param {...any} args - Arguments to log
|
|
107
|
+
*/
|
|
108
|
+
log(level) {
|
|
109
|
+
if (!this.options.debug) return;
|
|
110
|
+
const currentLevelValue = this.logLevels[this.options.logLevel];
|
|
111
|
+
const messageLevel = this.logLevels[level];
|
|
112
|
+
if (messageLevel <= currentLevelValue) {
|
|
113
|
+
const timestamp = new Date().toISOString();
|
|
114
|
+
const prefix = `[AFS ${level.toUpperCase()}] ${timestamp}`;
|
|
115
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
116
|
+
args[_key2 - 1] = arguments[_key2];
|
|
117
|
+
}
|
|
118
|
+
switch (level) {
|
|
119
|
+
case 'error':
|
|
120
|
+
console.error(prefix, ...args);
|
|
121
|
+
break;
|
|
122
|
+
case 'warn':
|
|
123
|
+
console.warn(prefix, ...args);
|
|
124
|
+
break;
|
|
125
|
+
case 'info':
|
|
126
|
+
console.info(prefix, ...args);
|
|
127
|
+
break;
|
|
128
|
+
case 'debug':
|
|
129
|
+
console.debug(prefix, ...args);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Configure debug mode settings
|
|
137
|
+
*
|
|
138
|
+
* @public
|
|
139
|
+
* @param {boolean} enabled - Enable or disable debug mode
|
|
140
|
+
* @param {string} [level='info'] - Log level ('error', 'warn', 'info', 'debug')
|
|
141
|
+
*/
|
|
142
|
+
setDebugMode(enabled) {
|
|
143
|
+
let level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'info';
|
|
144
|
+
this.options.debug = enabled;
|
|
145
|
+
if (this.logLevels.hasOwnProperty(level)) {
|
|
146
|
+
this.options.logLevel = level;
|
|
147
|
+
}
|
|
148
|
+
this.log('info', `Debug mode ${enabled ? 'enabled' : 'disabled'} with level: ${level}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
88
151
|
/**
|
|
89
152
|
* Initialize the filter system
|
|
153
|
+
* Sets up styles, events, and initial state
|
|
154
|
+
*
|
|
90
155
|
* @private
|
|
91
156
|
*/
|
|
92
157
|
init() {
|
|
158
|
+
this.log('debug', 'Starting initialization');
|
|
159
|
+
if (!this.container) {
|
|
160
|
+
this.log('error', `Container not found: ${this.options.containerSelector}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
93
163
|
this.addStyles();
|
|
94
164
|
this.bindEvents();
|
|
95
165
|
this.loadFromURL();
|
|
96
166
|
this.updateCounter();
|
|
167
|
+
this.log('info', 'Initialization complete');
|
|
97
168
|
}
|
|
98
169
|
|
|
99
170
|
/**
|
|
@@ -125,6 +196,7 @@ class AFS {
|
|
|
125
196
|
const styleSheet = document.createElement("style");
|
|
126
197
|
styleSheet.textContent = styles;
|
|
127
198
|
document.head.appendChild(styleSheet);
|
|
199
|
+
this.log('debug', 'Styles added to document');
|
|
128
200
|
}
|
|
129
201
|
|
|
130
202
|
/**
|
|
@@ -132,6 +204,7 @@ class AFS {
|
|
|
132
204
|
* @private
|
|
133
205
|
*/
|
|
134
206
|
bindEvents() {
|
|
207
|
+
this.log('debug', 'Binding events');
|
|
135
208
|
this.filterButtons.forEach(button => {
|
|
136
209
|
button.addEventListener("click", () => this.handleFilterClick(button));
|
|
137
210
|
});
|
|
@@ -141,6 +214,7 @@ class AFS {
|
|
|
141
214
|
}, this.options.debounceTime));
|
|
142
215
|
}
|
|
143
216
|
window.addEventListener("popstate", () => this.loadFromURL());
|
|
217
|
+
this.log('debug', 'Events bound successfully');
|
|
144
218
|
}
|
|
145
219
|
|
|
146
220
|
/**
|
|
@@ -150,6 +224,7 @@ class AFS {
|
|
|
150
224
|
*/
|
|
151
225
|
handleFilterClick(button) {
|
|
152
226
|
const filterValue = button.dataset.filter;
|
|
227
|
+
this.log('debug', 'Filter button clicked:', filterValue);
|
|
153
228
|
if (filterValue === "*") {
|
|
154
229
|
this.resetFilters();
|
|
155
230
|
} else {
|
|
@@ -164,6 +239,7 @@ class AFS {
|
|
|
164
239
|
* @private
|
|
165
240
|
*/
|
|
166
241
|
resetFilters() {
|
|
242
|
+
this.log('debug', 'Resetting filters');
|
|
167
243
|
this.filterButtons.forEach(btn => btn.classList.remove(this.options.activeClass));
|
|
168
244
|
this.currentFilters.clear();
|
|
169
245
|
this.currentFilters.add("*");
|
|
@@ -187,6 +263,7 @@ class AFS {
|
|
|
187
263
|
* @param {HTMLElement} button - Filter button element
|
|
188
264
|
*/
|
|
189
265
|
toggleFilter(filterValue, button) {
|
|
266
|
+
this.log('debug', `Toggling filter: ${filterValue}`);
|
|
190
267
|
this.currentFilters.delete("*");
|
|
191
268
|
this.filterButtons[0].classList.remove(this.options.activeClass);
|
|
192
269
|
if (button.classList.contains(this.options.activeClass)) {
|
|
@@ -205,6 +282,62 @@ class AFS {
|
|
|
205
282
|
}
|
|
206
283
|
}
|
|
207
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Sort items based on auto-detecting ASC or DESC for each key
|
|
287
|
+
* @public
|
|
288
|
+
* @param {string} key - The data attribute key to sort by (e.g., 'title', 'price', 'date')
|
|
289
|
+
*/
|
|
290
|
+
sortWithOrder(key) {
|
|
291
|
+
const items = [...this.items];
|
|
292
|
+
|
|
293
|
+
// Check if the current sort order is ASC, default to ASC if undefined
|
|
294
|
+
const currentOrder = this.sortOrders[key] || "asc";
|
|
295
|
+
const newOrder = currentOrder === "asc" ? "desc" : "asc"; // Toggle order
|
|
296
|
+
|
|
297
|
+
// Sort items based on the new order
|
|
298
|
+
items.sort((a, b) => {
|
|
299
|
+
let valueA = a.dataset[key];
|
|
300
|
+
let valueB = b.dataset[key];
|
|
301
|
+
|
|
302
|
+
// Check if values are numeric and convert them to numbers if they are
|
|
303
|
+
const isNumeric = !isNaN(valueA) && !isNaN(valueB);
|
|
304
|
+
if (isNumeric) {
|
|
305
|
+
valueA = parseFloat(valueA);
|
|
306
|
+
valueB = parseFloat(valueB);
|
|
307
|
+
}
|
|
308
|
+
if (newOrder === "asc") {
|
|
309
|
+
return isNumeric ? valueA - valueB : valueA.localeCompare(valueB);
|
|
310
|
+
} else {
|
|
311
|
+
return isNumeric ? valueB - valueA : valueB.localeCompare(valueA);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Update the current sort order for the key
|
|
316
|
+
this.sortOrders[key] = newOrder;
|
|
317
|
+
|
|
318
|
+
// Reorder items in the DOM
|
|
319
|
+
items.forEach(item => this.container.appendChild(item));
|
|
320
|
+
this.log('debug', `Sorting items by ${key} in ${newOrder} order`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Shuffle items randomly and display in the container
|
|
325
|
+
* @public
|
|
326
|
+
*/
|
|
327
|
+
shuffle() {
|
|
328
|
+
const itemsArray = [...this.items];
|
|
329
|
+
|
|
330
|
+
// Shuffle the itemsArray using Fisher-Yates algorithm
|
|
331
|
+
for (let i = itemsArray.length - 1; i > 0; i--) {
|
|
332
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
333
|
+
[itemsArray[i], itemsArray[j]] = [itemsArray[j], itemsArray[i]];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Re-append the shuffled items to the container
|
|
337
|
+
itemsArray.forEach(item => this.container.appendChild(item));
|
|
338
|
+
this.log('debug', 'Shuffling items');
|
|
339
|
+
}
|
|
340
|
+
|
|
208
341
|
/**
|
|
209
342
|
* Apply current filters to items
|
|
210
343
|
* @public
|
|
@@ -216,6 +349,7 @@ class AFS {
|
|
|
216
349
|
filter() {
|
|
217
350
|
// Store the original filter logic
|
|
218
351
|
const standardFilter = () => {
|
|
352
|
+
this.log('debug', 'Applying filters');
|
|
219
353
|
this.visibleItems.clear();
|
|
220
354
|
this.items.forEach(item => {
|
|
221
355
|
if (this.currentFilters.has("*")) {
|
|
@@ -258,6 +392,7 @@ class AFS {
|
|
|
258
392
|
setTimeout(() => {
|
|
259
393
|
this.updateCounter();
|
|
260
394
|
}, this.options.animationDuration);
|
|
395
|
+
this.log('info', `Filter applied. Visible items: ${this.visibleItems.size}`);
|
|
261
396
|
}
|
|
262
397
|
|
|
263
398
|
/**
|
|
@@ -273,12 +408,12 @@ class AFS {
|
|
|
273
408
|
try {
|
|
274
409
|
// Validate inputs
|
|
275
410
|
if (!groupId || !Array.isArray(filters)) {
|
|
276
|
-
|
|
411
|
+
this.log('error', 'Invalid group ID or filters');
|
|
277
412
|
return false;
|
|
278
413
|
}
|
|
279
414
|
const validOperator = operator.toUpperCase();
|
|
280
415
|
if (!["AND", "OR"].includes(validOperator)) {
|
|
281
|
-
|
|
416
|
+
this.log('error', 'Invalid operator:', operator);
|
|
282
417
|
operator = "OR";
|
|
283
418
|
}
|
|
284
419
|
|
|
@@ -296,7 +431,7 @@ class AFS {
|
|
|
296
431
|
}
|
|
297
432
|
return true;
|
|
298
433
|
} catch (error) {
|
|
299
|
-
|
|
434
|
+
this.log('error', 'Error adding filter group:', error);
|
|
300
435
|
return false;
|
|
301
436
|
}
|
|
302
437
|
}
|
|
@@ -427,17 +562,21 @@ class AFS {
|
|
|
427
562
|
* @param {string} query - Search query
|
|
428
563
|
*/
|
|
429
564
|
search(query) {
|
|
565
|
+
this.log('debug', 'Performing search with query:', query);
|
|
430
566
|
this.currentSearch = query.toLowerCase().trim();
|
|
567
|
+
let matches = 0;
|
|
431
568
|
this.items.forEach(item => {
|
|
432
569
|
const searchText = this.options.searchKeys.map(key => item.dataset[key] || "").join(" ").toLowerCase();
|
|
433
570
|
const matchesSearch = this.currentSearch === "" || searchText.includes(this.currentSearch);
|
|
434
571
|
if (matchesSearch) {
|
|
435
572
|
this.showItem(item);
|
|
573
|
+
matches++;
|
|
436
574
|
} else {
|
|
437
575
|
this.hideItem(item);
|
|
438
576
|
}
|
|
439
577
|
});
|
|
440
578
|
this.updateURL();
|
|
579
|
+
this.log('info', `Search complete. Found ${matches} matches`);
|
|
441
580
|
setTimeout(() => {
|
|
442
581
|
this.updateCounter();
|
|
443
582
|
}, this.options.animationDuration);
|
|
@@ -460,6 +599,7 @@ class AFS {
|
|
|
460
599
|
return 0;
|
|
461
600
|
});
|
|
462
601
|
items.forEach(item => this.container.appendChild(item));
|
|
602
|
+
this.log('debug', 'Sorting items by multiple criteria:', criteria);
|
|
463
603
|
}
|
|
464
604
|
|
|
465
605
|
/**
|
|
@@ -482,6 +622,7 @@ class AFS {
|
|
|
482
622
|
setTimeout(() => {
|
|
483
623
|
this.updateCounter();
|
|
484
624
|
}, this.options.animationDuration);
|
|
625
|
+
this.log('debug', `Filtering items by range: ${key} between ${min} and ${max}`);
|
|
485
626
|
}
|
|
486
627
|
|
|
487
628
|
/**
|
|
@@ -504,6 +645,8 @@ class AFS {
|
|
|
504
645
|
* @private
|
|
505
646
|
*/
|
|
506
647
|
updateURL() {
|
|
648
|
+
this.log('debug', 'Updating URL');
|
|
649
|
+
|
|
507
650
|
// If only "*" filter is active or no filters are active, clear the URL
|
|
508
651
|
if (this.currentFilters.size === 0 || this.currentFilters.size === 1 && this.currentFilters.has("*")) {
|
|
509
652
|
window.history.pushState({}, "", window.location.pathname);
|
|
@@ -542,6 +685,7 @@ class AFS {
|
|
|
542
685
|
}
|
|
543
686
|
const newURL = `${window.location.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
|
544
687
|
window.history.pushState({}, "", newURL);
|
|
688
|
+
this.log('debug', 'URL updated:', newURL);
|
|
545
689
|
}
|
|
546
690
|
|
|
547
691
|
/**
|
|
@@ -549,6 +693,7 @@ class AFS {
|
|
|
549
693
|
* @private
|
|
550
694
|
*/
|
|
551
695
|
loadFromURL() {
|
|
696
|
+
this.log('debug', 'Loading state from URL');
|
|
552
697
|
const params = new URLSearchParams(window.location.search);
|
|
553
698
|
|
|
554
699
|
// Load groups if they exist
|
|
@@ -604,6 +749,7 @@ class AFS {
|
|
|
604
749
|
if (search) {
|
|
605
750
|
this.search(search);
|
|
606
751
|
}
|
|
752
|
+
this.log('info', 'State loaded from URL');
|
|
607
753
|
}
|
|
608
754
|
|
|
609
755
|
/**
|
|
@@ -617,6 +763,7 @@ class AFS {
|
|
|
617
763
|
if (this.counter) {
|
|
618
764
|
this.counter.textContent = `Showing ${visible} of ${total}`;
|
|
619
765
|
}
|
|
766
|
+
this.log('debug', `Counter updated: ${visible}/${total}`);
|
|
620
767
|
return {
|
|
621
768
|
total,
|
|
622
769
|
visible
|
|
@@ -782,6 +929,7 @@ class AFS {
|
|
|
782
929
|
mode: this.options.filterMode
|
|
783
930
|
};
|
|
784
931
|
localStorage.setItem(`afs_preset_${presetName}`, JSON.stringify(preset));
|
|
932
|
+
this.log('info', `Preset saved: ${presetName}`);
|
|
785
933
|
}
|
|
786
934
|
|
|
787
935
|
/**
|
|
@@ -865,6 +1013,38 @@ class AFS {
|
|
|
865
1013
|
getActiveFiltersByType(type) {
|
|
866
1014
|
return [...this.currentFilters].filter(filter => filter.startsWith(`${type}:`)).map(filter => filter.split(":")[1]);
|
|
867
1015
|
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Clear all filters, url and search
|
|
1019
|
+
*
|
|
1020
|
+
* @public
|
|
1021
|
+
*/
|
|
1022
|
+
clearAllFilters() {
|
|
1023
|
+
this.currentFilters.clear();
|
|
1024
|
+
this.currentFilters.add("*");
|
|
1025
|
+
this.filter();
|
|
1026
|
+
this.updateURL();
|
|
1027
|
+
|
|
1028
|
+
// Uncheck all checkboxes if any with activeClass
|
|
1029
|
+
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
|
1030
|
+
checkboxes.forEach(checkbox => {
|
|
1031
|
+
if (checkbox.classList.contains(this.options.activeClass)) {
|
|
1032
|
+
checkbox.checked = false;
|
|
1033
|
+
checkbox.classList.remove(this.options.activeClass);
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// Clear active on buttons
|
|
1038
|
+
this.filterButtons.forEach(btn => {
|
|
1039
|
+
btn.classList.remove(this.options.activeClass);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
// Clear search input
|
|
1043
|
+
if (this.searchInput) {
|
|
1044
|
+
this.searchInput.value = "";
|
|
1045
|
+
}
|
|
1046
|
+
this.log('info', 'All filters cleared');
|
|
1047
|
+
}
|
|
868
1048
|
}
|
|
869
1049
|
|
|
870
1050
|
export { AFS };
|