1/** 2 * Filter26 3 * @version 1.1.1 4 * @description Filter26 is een filter plugin die het mogelijk maakt om items te filteren op basis van data attributen. 5 * @author Buro26 6 */ 7 8// Zet een reference naar jQuery als $ geen function is of undefined. 9if (typeof $ !== 'function') { 10 $ = (typeof jQuery === 'function') ? jQuery : undefined; 11} 12 13class Filter26 { 14 constructor ({ 15 filterableItems = '.filter-item', 16 animationType = 'fade', 17 animationDuration = 200, 18 filterBtn = '.filterBtn', 19 itemCounter = '.item-counter', 20 resetFiltersBtn = '.reset-filter', 21 filterSelect, 22 filterRange, 23 useUrlParams 24 }) { 25 this.filterableItems = filterableItems 26 this.animationType = animationType 27 this.animationDuration = animationDuration 28 this.filterBtn = filterBtn 29 this.itemCounter = itemCounter 30 this.resetFiltersBtn = resetFiltersBtn 31 this.filterSelect = filterSelect 32 this.filterRange = filterRange 33 this.useUrlParams = useUrlParams 34 35 this.filters = {} 36 37 this.startFiltering(); 38 } 39 40 /** 41 * Start het filteren. 42 */ 43 startFiltering() { 44 $(document).ready(() => { 45 if (!this.filterableItems) this.setError('Filter26: geen filterableItems ingesteld') 46 if (!this.filterBtn) this.setError('Filter26: geen filterBtn ingesteld') 47 if (!this.itemCounter) this.setError('Filter26: geen itemCounter ingesteld') 48 if (!this.filterSelect) this.setError('Filter26: geen filterSelect ingesteld') 49 if (!this.filterRange) this.setError('Filter26: geen filterRange ingesteld') 50 51 this.triggerEvent('filter26:onFilterStart') 52 this.restoreFiltersFromUrl() 53 54 if (this.itemCounter) { 55 this.updateCounter() 56 } 57 }) 58 59 // Filteren met buttons 60 if (this.filterBtn) { 61 $(this.filterBtn).on('click', (e) => { 62 const filter = $(e.target) 63 let filterButtonId = filter.attr('id'); 64 if (this.useUrlParams) this.storeFilterInUrl(filterButtonId, filter.data(filterButtonId)) 65 this.updateFilterObject(filterButtonId, filter.data(filterButtonId)) 66 if (!filterButtonId) this.setError('Filter26: Geen id op de filterbutton gevonden!'); 67 this.setActiveFilter(filter) 68 this.doFilter(this.filters) 69 }) 70 } 71 72 // Filteren met selects 73 if (this.filterSelect) { 74 $(this.filterSelect).on('change', (e) => { 75 const filterSelect = $(e.target); 76 if (filterSelect.attr('id') !== 'sort') { 77 if (this.useUrlParams) this.storeFilterInUrl(filterSelect.attr('id'), filterSelect.val()) 78 this.updateFilterObject(filterSelect.attr('id'), filterSelect.val()) 79 this.doFilter(this.filters) 80 } else if (filterSelect.attr('id') === 'sort') { 81 let sort = filterSelect.find(":selected").val() 82 let sortBy = sort.split('-')[0] 83 let sortOrder = sort.split('-')[1] 84 this.sortItems(sortBy, sortOrder) 85 } 86 }) 87 } 88 89 // Filteren met ranges 90 if (this.filterRange) { 91 $(this.filterRange).on('change', (e) => { 92 const filterRange = $(e.target); 93 if (filterRange.attr('id') !== 'sort') { 94 if (this.useUrlParams) this.storeFilterInUrl(filterRange.attr('id'), filterRange.val()) 95 this.updateFilterObject(filterRange.attr('id'), filterRange.val()) 96 this.doFilter(this.filters) 97 } else if (filterRange.attr('id') === 'sort') { 98 let sort = filterRange.find(":selected").val() 99 let sortBy = sort.split('-')[0] 100 let sortOrder = sort.split('-')[1] 101 this.sortItems(sortBy, sortOrder) 102 } 103 }) 104 } 105 106 // Reset filters 107 if (this.resetFiltersBtn) { 108 $(this.resetFiltersBtn).on('click', () => { 109 this.filters = {} 110 $(this.filterableItems).show(this.animationDuration) 111 $(this.filterBtn).removeClass('active') 112 $(this.filterSelect).val('') 113 this.updateCounter() 114 this.removeUrlParams() 115 }) 116 } 117 118 $(window).on('popstate', (e) => { 119 this.restoreFiltersFromUrl() 120 }) 121 } 122 123 /** 124 * Verwijder de URL parameters. 125 */ 126 removeUrlParams() { 127 window.location.search = ''; 128 window.location.href.replace(window.location.search, ''); 129 } 130 131 /** 132 * Zet een error. 133 * @param {String} message - De error message. 134 */ 135 setError(message) { 136 console.log('Filter26: ' + message) 137 } 138 139 /** 140 * Sorteer de items. 141 * @param {String} sortBy - De naam van de filter. 142 * @param {String} sortOrder - De waarde van de filter. 143 */ 144 sortItems(sortBy, sortOrder) { 145 this.triggerEvent('filter26:onSortingStart', { sortBy, sortOrder }) 146 147 let visibleItems = $(this.filterableItems + ':visible'); 148 visibleItems.fadeOut(100); 149 150 visibleItems.sort(function (a, b) { 151 let valueA = $(a).data(sortBy); 152 let valueB = $(b).data(sortBy); 153 154 // Sorteer de items op basis van hun data attribuut. 155 if (sortOrder === 'asc') { 156 return valueA.localeCompare(valueB); 157 } else { 158 return valueB.localeCompare(valueA); 159 } 160 }).appendTo($(this.filterableItems).parent()).fadeIn(100); 161 if (this.useUrlParams) this.storeFilterInUrl('sort', sortBy + '-' + sortOrder) 162 } 163 164 /** 165 * Update de filter object. 166 * @param {String} filterName - De naam van de filter. 167 * @param {String} filterValue - De waarde van de filter. 168 */ 169 updateFilterObject(filterName, filterValue) { 170 if (filterValue !== '') { 171 // Controleer eerst of de filter al bestaat, zo ja, update de waarde 172 if (this.filters[filterName]) { 173 this.filters[filterName].value = filterValue; 174 } else { 175 this.filters[filterName] = { key: filterName, value: filterValue }; 176 } 177 } else { 178 delete this.filters[filterName] 179 let animationType = this.animationType 180 let animationDuration = this.animationDuration 181 182 if (Object.keys(this.filters).length === 0) { 183 if (animationType === 'show') $(this.filterableItems).show(animationDuration) 184 else $(this.filterableItems).fadeIn(animationDuration) 185 } 186 } 187 } 188 189 /** 190 * Haalt de filters op uit de URL. 191 */ 192 restoreFiltersFromUrl() {
193 const urlParams = new URLSearchParams(window.location.search); 194 195 urlParams.forEach((urlValue, urlKey) => { 196 const filter = $(`#${urlKey}`) 197 if (filter.length && urlKey !== 'sort') { 198 this.updateFilterObject(urlKey, urlValue) 199 let filterButtonId = filter.attr('id'); 200 $(this.filterBtn).removeClass('active') 201 $(`#${urlKey}[data-${filterButtonId}="${urlValue}"]`).addClass('active') // Zet de button op de juiste filterwaarde 202 $('#' + urlKey).val(urlValue); // Zet de select elementen op de juiste filterwaarde 203 } 204 }) 205 206 this.doFilter(this.filters) 207 208 // Als het filteren klaar is en er is een sorteer parameter meegegeven, sorteer dan de items. 209 $(this.filterableItems).promise().done(() => { 210 if (urlParams.has('sort')) { 211 let sortBy = urlParams.get('sort').split('-')[0] 212 let sortOrder = urlParams.get('sort').split('-')[1] 213 this.sortItems(sortBy, sortOrder) 214 215 $('#sort').val(urlParams.get('sort')) 216 } 217 }) 218 } 219 220 /** 221 * Slaat de filter op in de URL. 222 * @param {String} filterName - De naam van de filter. 223 * @param {String} filterValue - De waarde van de filter. 224 */ 225 storeFilterInUrl(filterName, filterValue) { 226 const urlParams = new URLSearchParams(window.location.search) 227 if (filterName && filterValue) { 228 urlParams.set(filterName, filterValue) 229 } else if (!filterValue) { 230 urlParams.delete(filterName) 231 } 232 history.pushState(null, '', `${window.location.pathname}?${urlParams.toString()}`) 233 } 234 235 /** 236 * Zet de actieve filter button. 237 * @param {Object} filter - De filter button. 238 */ 239 setActiveFilter(filter) { 240 $(this.filterBtn).removeClass('active') 241 filter.addClass('active') 242 } 243 244 /** 245 * Update de teller van de items. 246 */ 247 updateCounter() { 248 const numberOfVisibleItems = $(this.filterableItems + ':visible').length; 249 const itemCounter = $(this.itemCounter); 250 251 itemCounter.animate({ itemCounter: numberOfVisibleItems }, { 252 duration: this.animationDuration, 253 easing: 'swing', 254 step: function (currentNumber) { 255 itemCounter.text(Math.ceil(currentNumber)) 256 } 257 }); 258 259 this.triggerEvent('filter26:onFilterEnd', { filters: this.filters, numberOfVisibleItems }) 260 } 261 262 /** 263 * Trigger een event. 264 * @param {String} eventName - De naam van het event. 265 * @param {Object} data - De data die meegegeven moet worden. 266 */ 267 triggerEvent(eventName, data) { 268 $(this.filterableItems).trigger(eventName, { data }); 269 } 270 271 /** 272 * Filtert de items op basis van de meegegeven filters. 273 * @param {Object} filters - The filters to apply. 274 */ 275 doFilter(filters) { 276 $(this.filterableItems).hide(); 277 this.triggerEvent('filter26:onFilterProgress', filters); 278 279 let animationType = this.animationType; 280 let animationDuration = this.animationDuration; 281 282 $(this.filterableItems).each((index, element) => { 283 const item = $(element); 284 let visible = true; 285 286 for (let [filterGroep, filterKeuze] of Object.entries(filters)) { 287 if (this.filterRange && (filterGroep.endsWith("-from") || filterGroep.endsWith("-to"))) { 288 const filterKey = filterGroep.split('-')[0]; 289 290 // Converteer de datum naar een Date object. 291 const parseDate = (dateString) => { 292 const [day, month, year] = dateString.split('-').map(Number); 293 return new Date(year, month - 1, day); 294 }; 295 296 const cardDate = parseDate(item.data(filterKey)); 297 const filterDate = parseDate(filterKeuze.value); 298 299 if ((filterGroep.endsWith("-from") && cardDate < filterDate) || 300 (filterGroep.endsWith("-to") && cardDate > filterDate)) { 301 visible = false;
302 } 303 } else { 304 const itemValue = item.data(filterGroep); 305 if (!itemValue || !itemValue.toString().includes(filterKeuze.value)) { 306 visible = false; 307 } 308 } 309 } 310 311 if (visible) { 312 if (animationType === 'show') item.show(animationDuration); 313 else item.fadeIn(animationDuration); 314 } 315 }); 316 317 $(this.filterableItems).promise().done(() => { 318 this.updateCounter(); 319 }); 320 } 321 322}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.