516 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			516 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| Joomla = window.Joomla || {};
 | |
| (Joomla => {
 | |
| 
 | |
|   /**
 | |
|    * Method that resets the filter inputs and submits the relative form
 | |
|    *
 | |
|    * @param {HTMLElement}  element  The element that initiates the call
 | |
|    * @returns {void}
 | |
|    * @since   4.0.0
 | |
|    */
 | |
|   Joomla.resetFilters = element => {
 | |
|     const {
 | |
|       form
 | |
|     } = element;
 | |
|     if (!form) {
 | |
|       throw new Error('Element must be inside a form!');
 | |
|     }
 | |
|     const elementsArray = [].slice.call(form.elements);
 | |
|     if (elementsArray.length) {
 | |
|       const newElementsArray = [];
 | |
|       elementsArray.forEach(elem => {
 | |
|         // Skip the token, the task, the boxchecked and the calling element
 | |
|         if (elem.getAttribute('name') === 'task' || elem.getAttribute('name') === 'boxchecked' || elem.value === '1' && /^[0-9A-F]{32}$/i.test(elem.name) || elem === element) {
 | |
|           return;
 | |
|         }
 | |
|         newElementsArray.push(elem);
 | |
|       });
 | |
| 
 | |
|       // Reset all filters
 | |
|       newElementsArray.forEach(elem => {
 | |
|         elem.value = '';
 | |
|       });
 | |
|       form.submit();
 | |
|     }
 | |
|   };
 | |
|   class Searchtools {
 | |
|     constructor(elem, options) {
 | |
|       const defaults = {
 | |
|         // Form options
 | |
|         formSelector: '.js-stools-form',
 | |
|         // Search
 | |
|         searchFieldSelector: '.js-stools-field-search',
 | |
|         clearBtnSelector: '.js-stools-btn-clear',
 | |
|         // Global container
 | |
|         mainContainerSelector: '.js-stools',
 | |
|         // Filter fields
 | |
|         searchBtnSelector: '.js-stools-btn-search',
 | |
|         filterBtnSelector: '.js-stools-btn-filter',
 | |
|         filterContainerSelector: '.js-stools-container-filters',
 | |
|         filtersHidden: true,
 | |
|         // List fields
 | |
|         listBtnSelector: '.js-stools-btn-list',
 | |
|         listContainerSelector: '.js-stools-container-list',
 | |
|         listHidden: true,
 | |
|         // Ordering specific
 | |
|         orderColumnSelector: '.js-stools-column-order',
 | |
|         orderBtnSelector: '.js-stools-btn-order',
 | |
|         orderFieldSelector: '.js-stools-field-order',
 | |
|         orderFieldName: 'list[fullordering]',
 | |
|         limitFieldSelector: '.js-stools-field-limit',
 | |
|         defaultLimit: 20,
 | |
|         activeOrder: null,
 | |
|         activeDirection: 'ASC',
 | |
|         // Extra
 | |
|         clearListOptions: false,
 | |
|         listSelectAutoSubmit: 'js-select-submit-on-change',
 | |
|         listSelectAutoReset: 'js-select-reset-on-change'
 | |
|       };
 | |
|       this.element = elem;
 | |
|       this.options = Joomla.extend(defaults, options);
 | |
| 
 | |
|       // Initialise selectors
 | |
|       this.theForm = document.querySelector(this.options.formSelector);
 | |
| 
 | |
|       // Filters
 | |
|       this.filterButton = document.querySelector(`${this.options.formSelector} ${this.options.filterBtnSelector}`);
 | |
|       this.filterContainer = document.querySelector(`${this.options.formSelector} ${this.options.filterContainerSelector}`) ? document.querySelector(`${this.options.formSelector} ${this.options.filterContainerSelector}`) : '';
 | |
|       this.filtersHidden = this.options.filtersHidden;
 | |
| 
 | |
|       // List fields
 | |
|       this.listButton = document.querySelector(this.options.listBtnSelector);
 | |
|       this.listContainer = document.querySelector(`${this.options.formSelector} ${this.options.listContainerSelector}`);
 | |
|       this.listHidden = this.options.listHidden;
 | |
| 
 | |
|       // Main container
 | |
|       this.mainContainer = document.querySelector(this.options.mainContainerSelector);
 | |
| 
 | |
|       // Search
 | |
|       this.searchButton = document.querySelector(`${this.options.formSelector} ${this.options.searchBtnSelector}`);
 | |
|       this.searchField = document.querySelector(`${this.options.formSelector} ${this.options.searchFieldSelector}`);
 | |
|       this.searchString = null;
 | |
|       this.clearButton = document.querySelector(this.options.clearBtnSelector);
 | |
| 
 | |
|       // Ordering
 | |
|       this.orderCols = document.querySelectorAll(`${this.options.formSelector} ${this.options.orderColumnSelector}`);
 | |
|       this.orderField = document.querySelector(`${this.options.formSelector} ${this.options.orderFieldSelector}`);
 | |
| 
 | |
|       // Limit
 | |
|       this.limitField = document.querySelector(`${this.options.formSelector} ${this.options.limitFieldSelector}`);
 | |
| 
 | |
|       // Init trackers
 | |
|       this.activeColumn = null;
 | |
|       this.activeDirection = this.options.activeDirection;
 | |
|       this.activeOrder = this.options.activeOrder;
 | |
|       this.activeLimit = null;
 | |
| 
 | |
|       // Extra options
 | |
|       this.clearListOptions = this.options.clearListOptions;
 | |
|       const self = this;
 | |
| 
 | |
|       // Get values
 | |
|       this.searchString = this.searchField ? this.searchField.value : '';
 | |
| 
 | |
|       // Do some binding
 | |
|       this.showFilters = this.showFilters.bind(this);
 | |
|       this.hideFilters = this.hideFilters.bind(this);
 | |
|       this.showList = this.showList.bind(this);
 | |
|       this.hideList = this.hideList.bind(this);
 | |
|       this.toggleFilters = this.toggleFilters.bind(this);
 | |
|       this.toggleList = this.toggleList.bind(this);
 | |
|       this.checkFilter = this.checkFilter.bind(this);
 | |
|       this.clear = this.clear.bind(this);
 | |
|       this.createOrderField = this.createOrderField.bind(this);
 | |
|       this.checkActiveStatus = this.checkActiveStatus.bind(this);
 | |
|       this.activeFilter = this.activeFilter.bind(this);
 | |
|       this.deactiveFilter = this.deactiveFilter.bind(this);
 | |
|       this.getFilterFields = this.getFilterFields.bind(this);
 | |
|       this.getListFields = this.getListFields.bind(this);
 | |
|       this.hideContainer = this.hideContainer.bind(this);
 | |
|       this.showContainer = this.showContainer.bind(this);
 | |
|       this.toggleContainer = this.toggleContainer.bind(this);
 | |
|       this.toggleDirection = this.toggleDirection.bind(this);
 | |
|       this.updateFieldValue = this.updateFieldValue.bind(this);
 | |
|       this.findOption = this.findOption.bind(this);
 | |
|       if (this.filterContainer && this.filterContainer.classList.contains('js-stools-container-filters-visible')) {
 | |
|         this.showFilters();
 | |
|         this.showList();
 | |
|       } else {
 | |
|         this.hideFilters();
 | |
|         this.hideList();
 | |
|       }
 | |
|       if (this.filterButton) {
 | |
|         this.filterButton.addEventListener('click', e => {
 | |
|           self.toggleFilters();
 | |
|           e.stopPropagation();
 | |
|           e.preventDefault();
 | |
|         });
 | |
|       }
 | |
|       if (this.listButton) {
 | |
|         this.listButton.addEventListener('click', e => {
 | |
|           self.toggleList();
 | |
|           e.stopPropagation();
 | |
|           e.preventDefault();
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       // Do we need to add to mark filter as enabled?
 | |
|       this.getFilterFields().forEach(i => {
 | |
|         const needsFormSubmit = !i.classList.contains(this.options.listSelectAutoSubmit) && i.closest(`joomla-field-fancy-select.${this.options.listSelectAutoSubmit}`);
 | |
|         const needsFormReset = !i.classList.contains(this.options.listSelectAutoReset) && i.closest(`joomla-field-fancy-select.${this.options.listSelectAutoReset}`);
 | |
|         self.checkFilter(i);
 | |
|         i.addEventListener('change', () => {
 | |
|           self.checkFilter(i);
 | |
|           if (i.classList.contains(this.options.listSelectAutoSubmit) || needsFormSubmit) {
 | |
|             i.form.submit();
 | |
|           }
 | |
|           if (i.classList.contains(this.options.listSelectAutoReset) || needsFormReset) {
 | |
|             this.clear(i);
 | |
|           }
 | |
|         });
 | |
|       });
 | |
|       if (this.clearButton) {
 | |
|         this.clearButton.addEventListener('click', self.clear);
 | |
|       }
 | |
| 
 | |
|       // Check/create ordering field
 | |
|       this.createOrderField();
 | |
|       this.orderCols.forEach(item => {
 | |
|         item.addEventListener('click', ({
 | |
|           target
 | |
|         }) => {
 | |
|           const element = target.tagName.toLowerCase() === 'span' ? target.parentNode : target;
 | |
| 
 | |
|           // Order to set
 | |
|           const newOrderCol = element.getAttribute('data-order');
 | |
|           const newDirection = element.getAttribute('data-direction');
 | |
|           const newOrdering = `${newOrderCol} ${newDirection}`;
 | |
| 
 | |
|           // The data-order attribute is required
 | |
|           if (newOrderCol.length) {
 | |
|             self.activeColumn = newOrderCol;
 | |
|             if (newOrdering !== self.activeOrder) {
 | |
|               self.activeDirection = newDirection;
 | |
|               self.activeOrder = newOrdering;
 | |
| 
 | |
|               // Update the order field
 | |
|               self.updateFieldValue(self.orderField, newOrdering);
 | |
|             } else {
 | |
|               self.toggleDirection();
 | |
|             }
 | |
|             self.theForm.submit();
 | |
|           }
 | |
|         });
 | |
|       });
 | |
|       this.checkActiveStatus(this);
 | |
|     }
 | |
|     checkFilter(element) {
 | |
|       if (element.tagName.toLowerCase() === 'select') {
 | |
|         const option = element.querySelector('option:checked');
 | |
|         if (option) {
 | |
|           if (option.value !== '') {
 | |
|             this.activeFilter(element, this);
 | |
|           } else {
 | |
|             this.deactiveFilter(element, this);
 | |
|           }
 | |
|         }
 | |
|       } else if (element.value !== '') {
 | |
|         this.activeFilter(element, this);
 | |
|       } else {
 | |
|         this.deactiveFilter(element, this);
 | |
|       }
 | |
|     }
 | |
|     clear(exceptElement = null) {
 | |
|       const self = this;
 | |
|       if (self.searchField) {
 | |
|         self.searchField.value = '';
 | |
|       }
 | |
|       self.getFilterFields().forEach(i => {
 | |
|         if (exceptElement && i === exceptElement || !i.closest(this.options.filterContainerSelector)) {
 | |
|           return;
 | |
|         }
 | |
|         i.value = '';
 | |
|         self.checkFilter(i);
 | |
|         if (window.jQuery && window.jQuery.chosen) {
 | |
|           window.jQuery(i).trigger('chosen:updated');
 | |
|         }
 | |
|       });
 | |
|       if (self.clearListOptions) {
 | |
|         self.getListFields().forEach(i => {
 | |
|           i.value = '';
 | |
|           self.checkFilter(i);
 | |
|           if (window.jQuery && window.jQuery.chosen) {
 | |
|             window.jQuery(i).trigger('chosen:updated');
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         // Special case to limit box to the default config limit
 | |
|         document.querySelector('#list_limit').value = self.options.defaultLimit;
 | |
|         if (window.jQuery && window.jQuery.chosen) {
 | |
|           window.jQuery('#list_limit').trigger('chosen:updated');
 | |
|         }
 | |
|       }
 | |
|       self.theForm.submit();
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     updateFilterCount(count) {
 | |
|       if (this.clearButton) {
 | |
|         this.clearButton.disabled = count === 0 && !this.searchString.length;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     checkActiveStatus(cont) {
 | |
|       let activeFilterCount = 0;
 | |
|       this.getFilterFields().forEach(item => {
 | |
|         if (!item.closest(this.options.filterContainerSelector)) {
 | |
|           return;
 | |
|         }
 | |
|         if (item.classList.contains('active')) {
 | |
|           activeFilterCount += 1;
 | |
|           if (cont.filterButton) {
 | |
|             cont.filterButton.classList.remove('btn-secondary');
 | |
|             cont.filterButton.classList.add('btn-primary');
 | |
|           }
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       // If there are no active filters - remove the filtered caption area from the table
 | |
|       if (activeFilterCount === 0) {
 | |
|         const filteredByCaption = document.getElementById('filteredBy');
 | |
|         if (filteredByCaption) {
 | |
|           filteredByCaption.parentNode.removeChild(filteredByCaption);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Disable clear button when no filter is active and search is empty
 | |
|       if (this.clearButton) {
 | |
|         this.clearButton.disabled = activeFilterCount === 0 && !this.searchString.length;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     activeFilter(element) {
 | |
|       element.classList.add('active');
 | |
|       const chosenId = `#${element.getAttribute('id')}`;
 | |
|       const tmpEl = element.querySelector(chosenId);
 | |
|       if (tmpEl) {
 | |
|         tmpEl.classList.add('active');
 | |
|       }
 | |
| 
 | |
|       // Add all active filters to the table caption for screen-readers
 | |
|       const filteredByCaption = document.getElementById('filteredBy');
 | |
|       const isHidden = Object.prototype.hasOwnProperty.call(element.attributes, 'type') && element.attributes.type.value === 'hidden';
 | |
| 
 | |
|       // The caption won't exist if no items match the filters so check for the element first
 | |
|       if (filteredByCaption && !isHidden) {
 | |
|         let captionContent = '';
 | |
|         if (element.tagName.toLowerCase() === 'select') {
 | |
|           if (element.multiple === true) {
 | |
|             const selectedOptions = element.querySelectorAll('option:checked');
 | |
|             const selectedTextValues = [].slice.call(selectedOptions).map(el => el.text);
 | |
|             captionContent = `${element.labels[0].textContent} - ${selectedTextValues.join()}`;
 | |
|           } else {
 | |
|             captionContent = `${element.labels[0].textContent} - ${element.options[element.selectedIndex].text}`;
 | |
|           }
 | |
|         } else {
 | |
|           captionContent = `${element.labels[0].textContent} - ${element.value}`;
 | |
|         }
 | |
|         filteredByCaption.textContent += captionContent;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     deactiveFilter(element) {
 | |
|       element.classList.remove('active');
 | |
|       const chosenId = `#${element.getAttribute('id')}`;
 | |
|       const tmpEl = element.querySelector(chosenId);
 | |
|       if (tmpEl) {
 | |
|         tmpEl.classList.remove('active');
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line consistent-return
 | |
|     getFilterFields() {
 | |
|       if (this.mainContainer) {
 | |
|         return this.mainContainer.querySelectorAll('select,input');
 | |
|       }
 | |
|       if (this.filterContainer) {
 | |
|         return this.filterContainer.querySelectorAll('select,input');
 | |
|       }
 | |
|       return [];
 | |
|     }
 | |
|     getListFields() {
 | |
|       return this.listContainer.querySelectorAll('select');
 | |
|     }
 | |
| 
 | |
|     // Common container functions
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     hideContainer(container) {
 | |
|       if (container) {
 | |
|         container.classList.remove('js-stools-container-filters-visible');
 | |
|         document.body.classList.remove('filters-shown');
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     showContainer(container) {
 | |
|       container.classList.add('js-stools-container-filters-visible');
 | |
|       document.body.classList.add('filters-shown');
 | |
|     }
 | |
|     toggleContainer(container) {
 | |
|       if (container.classList.contains('js-stools-container-filters-visible')) {
 | |
|         this.hideContainer(container);
 | |
|       } else {
 | |
|         this.showContainer(container);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // List container management
 | |
|     hideList() {
 | |
|       this.hideContainer(this.filterContainer);
 | |
|     }
 | |
|     showList() {
 | |
|       this.showContainer(this.filterContainer);
 | |
|     }
 | |
|     toggleList() {
 | |
|       this.toggleContainer(this.filterContainer);
 | |
|     }
 | |
| 
 | |
|     // Filters container management
 | |
|     hideFilters() {
 | |
|       this.hideContainer(this.filterContainer);
 | |
|     }
 | |
|     showFilters() {
 | |
|       this.showContainer(this.filterContainer);
 | |
|     }
 | |
|     toggleFilters() {
 | |
|       this.toggleContainer(this.filterContainer);
 | |
|     }
 | |
|     toggleDirection() {
 | |
|       const self = this;
 | |
|       let newDirection = 'ASC';
 | |
|       if (self.activeDirection.toUpperCase() === 'ASC') {
 | |
|         newDirection = 'DESC';
 | |
|       }
 | |
|       self.activeDirection = newDirection;
 | |
|       self.activeOrder = `${self.activeColumn} ${newDirection}`;
 | |
|       self.updateFieldValue(self.orderField, self.activeOrder);
 | |
|     }
 | |
|     createOrderField() {
 | |
|       const self = this;
 | |
|       if (!this.orderField) {
 | |
|         this.orderField = document.createElement('input');
 | |
|         this.orderField.setAttribute('type', 'hidden');
 | |
|         this.orderField.setAttribute('id', 'js-stools-field-order');
 | |
|         this.orderField.setAttribute('class', 'js-stools-field-order');
 | |
|         this.orderField.setAttribute('name', self.options.orderFieldName);
 | |
|         this.orderField.setAttribute('value', `${self.activeOrder} ${this.activeDirection}`);
 | |
|         this.theForm.append(this.orderField);
 | |
|       }
 | |
| 
 | |
|       // Add missing columns to the order select
 | |
|       if (this.orderField.tagName.toLowerCase() === 'select') {
 | |
|         const allOptions = [].slice.call(this.orderField.options);
 | |
|         allOptions.forEach(option => {
 | |
|           let value = option.getAttribute('data-order');
 | |
|           const name = option.getAttribute('data-name');
 | |
|           const direction = option.getAttribute('data-direction');
 | |
|           if (value && value.length) {
 | |
|             value = `${value} ${direction}`;
 | |
|             let $option = self.findOption(self.orderField, value);
 | |
|             if (!$option.length) {
 | |
|               $option = document.createElement('option');
 | |
|               $option.text = name;
 | |
|               $option.value = value;
 | |
| 
 | |
|               // If it is the active option select it
 | |
|               if (option.classList.contains('active')) {
 | |
|                 $option.setAttribute('selected', 'selected');
 | |
|               }
 | |
| 
 | |
|               // Append the option and repopulate the chosen field
 | |
|               this.orderFieldName.innerHTML += Joomla.sanitizeHtml($option);
 | |
|             }
 | |
|           }
 | |
|         });
 | |
|         if (window.jQuery && window.jQuery.chosen) {
 | |
|           window.jQuery(this.orderField).trigger('chosen:updated');
 | |
|         }
 | |
|       }
 | |
|       this.activeOrder = this.orderField.value;
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this
 | |
|     updateFieldValue(field, newValue) {
 | |
|       const type = field.getAttribute('type');
 | |
|       if (type === 'hidden' || type === 'text') {
 | |
|         field.setAttribute('value', newValue);
 | |
|       } else if (field.tagName.toLowerCase() === 'select') {
 | |
|         const allOptions = [].slice.call(field.options);
 | |
|         let desiredOption;
 | |
| 
 | |
|         // Select the option result
 | |
|         allOptions.forEach(option => {
 | |
|           if (option.value === newValue) {
 | |
|             desiredOption = option;
 | |
|           }
 | |
|         });
 | |
|         if (desiredOption && desiredOption.length) {
 | |
|           desiredOption.setAttribute('selected', 'selected');
 | |
|         } else {
 | |
|           // If the option does not exist create it on the fly
 | |
|           const option = document.createElement('option');
 | |
|           option.text = newValue;
 | |
|           option.value = newValue;
 | |
|           option.setAttribute('selected', 'selected');
 | |
| 
 | |
|           // Append the option and repopulate the chosen field
 | |
|           field.appendChild(option);
 | |
|         }
 | |
|         field.value = newValue;
 | |
|         // Trigger the chosen update
 | |
|         if (window.jQuery && window.jQuery.chosen) {
 | |
|           field.trigger('chosen:updated');
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // eslint-disable-next-line class-methods-use-this,consistent-return
 | |
|     findOption(select, value) {
 | |
|       // eslint-disable-next-line no-plusplus
 | |
|       for (let i = 0, l = select.length; l > i; i++) {
 | |
|         if (select[i].value === value) {
 | |
|           return select[i];
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   const onBoot = () => {
 | |
|     if (Joomla.getOptions('searchtools')) {
 | |
|       const options = Joomla.getOptions('searchtools');
 | |
|       const element = document.querySelector(options.selector);
 | |
| 
 | |
|       // eslint-disable-next-line no-new
 | |
|       new Searchtools(element, options);
 | |
|     }
 | |
|     const sort = document.getElementById('sorted');
 | |
|     const order = document.getElementById('orderedBy');
 | |
|     if (sort && sort.hasAttribute('data-caption') && order) {
 | |
|       const orderedBy = sort.getAttribute('data-caption');
 | |
|       order.textContent += orderedBy;
 | |
|     }
 | |
|     if (sort && sort.hasAttribute('data-sort')) {
 | |
|       const ariasort = sort.getAttribute('data-sort');
 | |
|       sort.parentNode.setAttribute('aria-sort', ariasort);
 | |
|     }
 | |
| 
 | |
|     // Cleanup
 | |
|     document.removeEventListener('DOMContentLoaded', onBoot);
 | |
|   };
 | |
| 
 | |
|   // Execute on DOM Loaded Event
 | |
|   document.addEventListener('DOMContentLoaded', onBoot);
 | |
| })(Joomla);
 |