PageSourceSearch

https://www.best-of-bio.fr/js/filters2.js

js best-of-bio.fr collected 2026-10-07 00:10:54 UTC 16,073 bytes, 445 lines download raw bytes

1jQuery(document).ready(function ($) {
2
3  /**
4   ** Var collection
5   */
6
7  const $body = $('body')
8  const $filterElements = $('.filter-bar, .filter-bar li, .filter-bar form, .filter-bar button', '#products')
9  const buttons = '.filter-bar button'
10  const labels = '.filter-bar-container-sub label:not([for="search"])'
11  const filterBtn = '.btn-filter button'
12  const btnOpenFilter = '.open-filter'
13
14  /**
15   ** Anim collection
16   */
17  const anim = {
18    ripple: (el, event, scale = 40, input = false) => {
19      // Ripple effect on click
20      // Note : $el[0] = this
21
22      const $el = typeof el[0] === 'undefined' ? $(el) : el
23      const xPos = input ? 5 : event.pageX - $el.offset().left
24      const yPos = input ? 5 : event.pageY - $el.offset().top
25      const elWavesRipple = document.createElement('span')
26
27      elWavesRipple.className = 'waves-ripple'
28      elWavesRipple.style.left = xPos + 'px'
29      elWavesRipple.style.top = yPos + 'px'
30
31      $el[0].appendChild(elWavesRipple)
32
33      anime({
34        targets: $el.find('.waves-ripple')[0],
35        duration: 600,
36        scale: [0, scale],
37        opacity: [0.2, 0],
38        easing: 'easeOutSine',
39        complete: function () {
40          $el.find('.waves-ripple').remove()
41        },
42      })
43    },
44    list: (el) => {
45      if (el.next()[0] === undefined) return
46
47      anime({
48        targets: el.next()[0].querySelectorAll('label'),
49        duration: 150,
50        translateY: [5, 0],
51        opacity: [0, 1],
52        easing: 'easeOutSine',
53        delay: anime.stagger(50, { start: 150 }),
54      })
55    },
56    openMenu: () => {
57      let tl = anime.timeline(filter.options)
58      let selectors = window.innerWidth > 992 ? '.filter-bar-container > ul > li, .filter-bar-container > ul > fieldset > li, form, button' : '.filter-bar-container ul li, form, button'
59
60      tl.add({
61        targets: filter.selector,
62        translateX: [0, '-100%'],
63      }).add(
64        {
65          targets: filter.selector.querySelectorAll(selectors),
66          translateX: ['10px', 0],
67          opacity: [0, 1],
68          delay: anime.stagger(25),
69        },
70        '-=500'
71      )
72    },
73    closeMenu: () => {
74      let tl = anime.timeline(filter.options)
75
76      tl.add({
77        targets: filter.selector.querySelectorAll('li, form, button'),
78        translateX: [0, '10px'],
79        opacity: [1, 0],
80        delay: anime.stagger(25),
81      }).add(
82        {
83          targets: filter.selector,
84          translateX: ['-100%', 0],
85        },
86        '300'
87      )
88    },
89    resetSVG: () => {
90      anime({
91        targets: '.reset-filter svg',
92        rotate: [0, '360deg'],
93        duration: 800,
94        easing: 'easeOutExpo',
95      })
96    },
97  }
98
99  /**
100   ** Filter UI + URL
101   */
102  const filter = {
103    selector: document.querySelector('.filter-bar'),
104    isOpen: false,
105    isClose: !this.isOpen,
106    cssClass: {
107      openMobileMenu: 'js-filter-menu-open',
108      toggleSubmenu: 'js-filter-submenu-open',
109    },
110    options: {
111      //options globales AnimeJS
112      easing: 'easeOutExpo',
113      duration: 750,
114    },
115    url: {
116      //filter.url => GET/SET l'url
117      
118      searchParams: new URLSearchParams(window.location.search), //vanilla JS
119      objectURL: {}, //jQuery param => va contenir tous les slugs de chaque catégorie sous forme d'objet (j'ai pas fait de tableau à plusieurs dimension pour les sous(-sous)-cat parce que je suis inté :p)
120      string: '',
121      remove: (mainCategory, category) => {
122 
123        //Garde tous les paramètres en cours
124        let currentCategories = filter.url.searchParams.getAll(mainCategory)
125
126        //Vide les paramètres pour la catégorie sélectionnée (pas le choix que de faire ça, il n'existe pas de fn du style "deleteOne='ma-categorie'")
127        filter.url.searchParams.delete(mainCategory)
128
129        //Remets toutes les catégories cochées dans le tableau sauf celle qui vient d'être décochée
130        currentCategories.forEach((previousCategory) => (previousCategory !== category ? filter.url.searchParams.append(mainCategory, previousCategory) : null))
131      },
132      handle: (mainCategory, category, status) => {
133
134        // Supprime la recherche sur les filtres en dessous du current
135        let filters_list = [];
136        $( $('.filter-bar-container-sub .filter-name') ).each(function( index ) {
137          //if($( this ).attr('data-category') != undefined) {
138            if(filters_list.indexOf($( this ).attr('data-category')) == -1)
139              filters_list.push($( this ).attr('data-category'));
140          //}          
141        });
142        let current_index_filter = filters_list.indexOf(mainCategory);
143        for(var i = current_index_filter+1; i<filters_list.length; i++) {
144
145            if(mainCategory!= 'countries') {
146              filter.url.searchParams.delete(filters_list[i])
147              delete filter.url.objectURL[filters_list[i]]
148            }
149        }        
150        
151
152        status ? filter.url.searchParams.append(mainCategory, category) : filter.url.remove(mainCategory, category)        
153
154        //Gère l'objet objectURL
155        filter.url.objectURL[mainCategory] = filter.url.searchParams.getAll(mainCategory)
156        
157        // Supprime les types parents si sous type checké
158        if(mainCategory == "type-de-produits") {
159          var types_datas = filter.url.objectURL["type-de-produits"];
160          $(types_datas).each(function( index ) {
161            var sous_type = $('.filter-name[data-category="type-de-produits"][data-value="'+types_datas[index]+'"]');
162            var parent = sous_type.data('parent')   
163            if(parent) {
164              var allchilds = $('label[for="cd"] input[type="checkbox"]:checked').filter(function() {
165                return $(this).siblings('.filter-name[data-category="type-de-produits"]').data('parent') === parent;
166              });
167              if(status || allchilds.length>0) {
168                let indexOf = filter.url.objectURL['type-de-produits'].indexOf(parent.toString())
169                if(indexOf!=-1) filter.url.objectURL['type-de-produits'].splice(indexOf , 1); 
170              }
171            }      
172          });
173        }
174        if (!filter.url.objectURL[mainCategory].length) 
175          delete filter.url.objectURL[mainCategory]
176        filter.url.string = $.param(filter.url.objectURL)
177      },
178    },
179    toggleStatus: () => {
180      filter.isClose = !filter.isClose
181      filter.isOpen = !filter.isOpen
182    },
183    openMenu: () => {
184      $body.addClass(filter.cssClass.openMobileMenu)
185
186      anim.openMenu()
187
188      filter.toggleStatus()
189    },
190    closeMenu: () => {
191      $body.removeClass(filter.cssClass.openMobileMenu)
192
193      anim.closeMenu()
194
195      filter.toggleStatus()
196    },
197    toggleMenu: () => {
198      filter.isOpen ? filter.closeMenu() : filter.openMenu()
199    },
200    resetMenu: () => {
201      //reset *VISUEL* du menu, pas de data ici
202      $body.removeClass(filter.cssClass.openMobileMenu)
203
204      $filterElements.removeAttr('style')
205
206      filter.isOpen = false
207    },
208    toggleSubmenu: (el, event) => {
209      //plie/déplie les "panneaux" de filtres
210      let $button = $(el)
211
212      if (event) anim.ripple($button, event)
213
214      if (!$button.hasClass(filter.cssClass.toggleSubmenu)) anim.list($button)
215
216      //Slide haut/bas le sous-menu
217      $button.toggleClass(filter.cssClass.toggleSubmenu).next().slideToggle(400)
218    },
219    openAllSubmenus: () => {
220      //ouvre tous les sous-menu (comme son nom l'indique)
221      $(buttons).each(function () {
222        let $button = $(this)
223
224        //Reset CSS style de tous les labels & affiche les via le CSS => opacity :1
225        if (!$button.hasClass(filter.cssClass.toggleSubmenu)) $button.next().find('label').removeAttr('style').css({ opacity: 1 })
226
227        $button.addClass(filter.cssClass.toggleSubmenu)
228
229        $button.next().show()
230      })
231    },
232    closeAllSubmenus: () => {
233      //ouvre tous les sous-menu (comme son nom l'indique)
234      $(buttons).each(function () {
235        let $button = $(this)
236
237        //Reset CSS style de tous les labels & affiche les via le CSS => opacity :1
238        if (!$button.hasClass(filter.cssClass.toggleSubmenu)) $button.next().find('label').removeAttr('style').css({ opacity: 0 })
239
240        $button.removeClass(filter.cssClass.toggleSubmenu)
241
242        $button.next().hide()
243      })
244    },
245    submenuAutoToggle: (el) => {
246      //Ferme et supprime les sous-menu/sous-filtres ainsi que les slugs correspondants
247      let $label = $(el)
248      let input = $label.find('input[type="checkbox"]')[0] ? $label.find('input[type="checkbox"]')[0] : $label.find('input[type="radio"]')[0]
249      let $subMenu = $label.nextAll('ul')
250
251      filter.toggleSubmenu(el)
252
253      //Uncheck les input des sous-menus
254      if (!input.checked) {
255        $subMenu.find('label:not(.label-all)').each(function () {
256          let $subMenuInput = $(this).find('input')[0]
257          if ($subMenuInput.checked) filter.handle(this) //Gère (supprime dans ce cas-ci) les slugs
258        })
259      }
260    },
261    handle: (el, event, ripple = true) => {
262      //Toggle les inputs & choppe les slug de la catégorie cliquées et de la catégorie parente pour en faire une URL via filter.url.handle()
263      let $label = $(el)
264      let $parentUl = $label.parents('ul')
265      let $subMenu = $label.nextAll('ul')
266      //let mainCategory = $parentUl.prev('button').attr('data-value')
267      let mainCategory = $label.find('.filter-name').attr('data-category')
268      let category = $label.find('.filter-name').attr('data-value')
269      let input = $label.find('input[type="checkbox"]')[0] ? $label.find('input[type="checkbox"]')[0] : $label.find('input[type="radio"]')[0]
270      let is_radio = $(input).attr('type') == 'radio'?true:false;
271      let is_sstype = $(input).hasClass('sstype');
272      // if(is_sstype) mainCategory = 'sous-types';
273
274      //Anim le click si l'interface event est passée et qu'on est bien dans une checkbox
275      if (event && ripple && input) anim.ripple($label, event, 7, true)
276
277      //toggle le check de l'input en JS... obligé car sinon l'event "click" est trigger 2 fois car mon <input> est dans mon <label>
278      if (input) input.checked = input.checked === true && input.disabled === false ? false : true
279      
280      //Ferme les sous-menu si il existe
281      if ($subMenu.length) filter.submenuAutoToggle(el)
282      if(is_radio) filter.url.searchParams.delete(mainCategory)
283
284      //Modifie l'url en conséquence
285      if (input) filter.url.handle(mainCategory, category, input.checked)
286      
287
288      filter.handleCatInputsToggle(el, mainCategory)
289    },
290    handleCatInputsToggle: (el, mainCategory) => {
291
292      let $label = $(el)
293      
294      if ($label.find('input')[0].type === 'radio') return
295      if ($label.parents('.radio-container').length) return
296
297      let $parentUl = $label.parents('ul')
298      if($parentUl.find('> li > .label-all input').length){
299        var $inputAll = $parentUl.find('> li > .label-all input')
300        var $catInputs = $parentUl.find('> li > label:not(.label-all) input')
301        var countOfCheckedInputs = $parentUl.find('> li > label:not(.label-all) input:checked').length
302      }else{
303        var $inputAll = $parentUl.find('.label-all input')
304        var $catInputs = $parentUl.find('label:not(.label-all) input')
305        var countOfCheckedInputs = $parentUl.find('label:not(.label-all) input:checked').length
306      }
307      
308      let isInputAll = $(el).hasClass('label-all')
309      let input = $label.find('input[type="checkbox"]')[0]
310      
311      //Si c'est l'input "TOUT" && qu'il n'est PAS checké, check l'input "TOUT"
312      if (isInputAll && input.checked === false){
313        $inputAll.prop('checked', true)
314        filter.url.searchParams.delete(mainCategory)
315        filter.url.objectURL[mainCategory] = {}
316      } 
317
318      //Si c'est l'input "TOUT" && qu'il est checké, décheck tous les inputs
319      if (isInputAll && input.checked === true) {
320          console.log('all')
321          if(mainCategory == "type-de-produits"){
322            filter.url.searchParams.delete(mainCategory)
323            filter.url.objectURL[mainCategory] = {}
324            $catInputs.prop('checked', false)
325            filter.handleSubCatInputsToggle(el)
326            filter.url.handle(mainCategory, null, false)
327          }
328          else if($catInputs.length >1 && $inputAll.length >1) {
329            $catInputs.each((catInput)=>{
330              if(catInput>0) $($catInputs.get(catInput)).prop('checked', false)
331            })
332            filter.url.searchParams.delete(mainCategory)
333            filter.url.objectURL[mainCategory] = {}
334            filter.url.handle(mainCategory, null, false)
335          //}
336        }
337        else {
338          $catInputs.prop('checked', false)
339          filter.handleSubCatInputsToggle(el)
340          filter.url.searchParams.delete(mainCategory)
341          filter.url.objectURL[mainCategory] = {}
342          filter.url.handle(mainCategory, null, false)
343        }
344      }
345
346      //Si c'est PAS l'input "TOUT" && qu'il est checké, décheck l'input "TOUT"
347      if (!isInputAll && (input && input.checked === true)){
348        if(mainCategory=="type-de-produits"){
349          if($label.find('.filter-name').data('parent')){
350            let indexOf = filter.url.objectURL[mainCategory].indexOf($label.find('.filter-name').data('parent').toString())
351            if(indexOf!=-1) filter.url.objectURL[mainCategory].splice(indexOf , 1);
352          }
353        }
354        if($inputAll.length>1){
355          if($($inputAll.get(1)).prop('checked') == true){
356            $($inputAll.get(1)).prop('checked', false)
357          }
358        }else{
359          if($inputAll.prop('checked') == true){
360            $inputAll.prop('checked', false)
361          }
362        }
363      }
364
365      //Si c'est PAS l'input "TOUT" && que les autres inputs ne sont PAS checké, check l'input "TOUT"
366      if (!isInputAll && !countOfCheckedInputs) {
367        if(mainCategory=="type-de-produits"){
368          $inputAll.prop('checked', true);
369          filter.url.objectURL[mainCategory] = []
370          filter.url.searchParams.delete(mainCategory)
371          filter.url.objectURL[mainCategory].push($inputAll.next().next().attr('data-value'));
372        }else if($inputAll.length>1){
373          $($inputAll[1]).prop('checked', true);
374        }else{
375          $inputAll.prop('checked', true);
376        }
377      }
378    }
379    ,
380    handleSubCatInputsToggle: (el) => {
381      let $openedSubSubMenus = $(el).parents('ul').find(`.${filter.cssClass.toggleSubmenu}`)
382
383      $openedSubSubMenus.each(function () {
384        filter.toggleSubmenu(this)
385      })
386    }
387    ,
388    resetFilters: () => {
389      location.reload();
390      anim.resetSVG()
391
392      $(labels).each(function () {
393        let input = $(this).find('input')[0]
394        let isInputAll = $(this).hasClass('label-all')
395
396        input.checked = isInputAll ? true : false
397
398        filter.url.objectURL = {}
399        filter.url.searchParams = new URLSearchParams()
400      })
401    },
402  }
403
404//   window.innerWidth > 992 ? filter.resetMenu() : filter.openAllSubmenus()
405
406  /*window.addEventListener(
407    'resize',
408    _.debounce(() => (window.innerWidth > 992 ? filter.resetMenu() : filter.openAllSubmenus()), 600)
409  )*/
410
411  /**
412   ** Open/close filter menu
413   */
414  $(document).on('click', buttons, function (event) {
415    $(this).hasClass('close') ? filter.closeMenu() : ($(this).hasClass('reset-filter') ? filter.resetFilters() : ($(this).hasClass('reset-filter-map') ? window.location.href.split("?")[0] :filter.toggleSubmenu(this, event)))
416  })
417
418  /**
419   ** Handle label UI behaviour + pass to "handleParamsURL"
420   */
421  $(document).on('click', labels, function (event) {
422    event.stopPropagation()
423    event.preventDefault()
424
425    filter.handle(this, event)
426  })
427
428  /**
429   ** Ripple anim on button click
430   */
431  $(document).on('click', filterBtn, function (event) {
432    anim.ripple(this, event, 8)
433  })
434
435  /**
436   ** open filter menu on button click
437   */
438  $(document).on(
439    'click',
440    btnOpenFilter,
441    _.throttle(() => filter.openMenu(), 500, { leading: true })
442  )
443
444  /*if (dev)*/ return (window.coccifilter = { filter })
445})

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.