views_faceted_filters_js-0.0.18/libraries/client-side-faceted-filters/dist/js/csff.js

libraries/client-side-faceted-filters/dist/js/csff.js
/**
 * Client-side faceted filters.
 *
 * @param {Element} targetElement
 *   The target Element.
 * @param {array} settings
 *   The settings array.
 * @return {Csff}
 */
function Csff(targetElement, settings) {
    /**
     * @var {object}
     */
    const self = this;

    /**
     * Defines the default settings and can be used as template for providing
     * custom settings.
     *
     * All unprovided custom settings will fall back to their defaults.
     *
     * @var {object}
     */
    const defaultSettings = {
        // General settings:
        injection_selector: "#facets", // Facets container
        injection_method: "beforeend", // Facets container injection method

        // Facet filters:
        facets_show: true, // Enable faceted filters
        facets: [
            // EXAMPLE:
            // {
            //   id: 'color',
            //   title: 'Color',
            //   description: 'Filter by color:',
            //   count_show: true,
            //   values_sort: 'value',
            //   values_sort_direction: 'asc',
            //   multivalue_separator: '|',
            //   weight: 0,
            // },
            // {
            //   id: 'size',
            //   title: 'Size',
            //   description: 'Filter by size:',
            //   count_show: true,
            //   values_sort: 'count',
            //   values_sort_direction: 'desc',
            //   multivalue_separator: '|',
            //   weight: 1,
            // }
        ],

        // Fulltext search:
        fulltext_search_show: false, // Enable full text search
        fulltext_search_title: "Search",
        fulltext_search_description: "",
        fulltext_search_placeholder: "",
        fulltext_search_delay: 500,

        // Reset button:
        reset_button_show: true, // Show reset button
        reset_button_title: "Reset",
        reset_button_description: "",
        reset_button_label: "Reset", // Reset button label

        // Empty text:
        results_empty_text: "No matching results.",

        // HTML templates:
        template_facets_container: '<form class="csff-facets" action="#"><!-- Prevent implicit submission of the form -->\
      <button type="submit" disabled style="display: none" aria-hidden="true"></button></form>',
        template_facet: '<section class="csff-facet view-faceted-filters-js-facet block block--js" id="csff-facet-${id}">\
        <h2 class="csff-facet__title block__title">${title}</h2>\
        <div class="csff-facet__content block__content">\
            <div class="csff-facet__description block__description">${description}</div>\
            <div class="csff-facet__values">\
              ${content}\
            </div>\
        </div>\
      </section>',
        template_facet_value: '<div class="csff-facet__value form-item form-type-checkbox"><input type="checkbox" name="${facetId}" id="${id}" value="${value}" class="csff-facet__value-input form-checkbox" /><label for="${id}" class="option csff-facet__value-label"><span class="csff-facet__value-label-text">${value}</span><span class="csff-facet__value-count">${count}</span></label></div>',
        template_facet_reset: '<div class="csff-facets__reset">\
          <button class="button button--reset">${label}</button>\
        </div>',
        template_search: '<div class="csff-facet__value form-item form-type-search"><input type="search" name="csff-search" class="csff-facet__value-input form-search" placeholder="${placeholder}" /></div>',
        template_results_empty: '<div class="csff-results-empty hidden--results-empty"><div class="csff-results-empty__content">${content}</div></div>',

        // The following options are typically not changed, but we even allow that
        // for resolving possible conflicts:
        _csff_controls_facets_container_selector: ".csff-facets",
        _csff_item_selector: ".csff-item",
        _csff_subitem_selector: ".csff-subitem",
        _csff_filter_input_selector: ".csff-facet__value-input.form-checkbox",
        _csff_search_input_selector: ".csff-facet__value-input.form-search",
        _csff_reset_input_selector: ".csff-facets__reset .button--reset",
        _csff_results_empty_selector: ".csff-results-empty",
        _csff_facet_class_prefix: "csff-facet-",
        _csff_facet_hidden_item_class: "hidden--filter",
        _csff_facet_data_attribute_prefix: "csff-facet-",
        _csff_facet_data_attribute_value_suffix: "--value",
        _csff_search_class: "csff-search-input",
        _csff_search_data_attribute_prefix: "csff-search--value",
        _csff_search_hidden_item_class: "hidden--search",
        _csff_results_empty_hidden_class: "hidden--results-empty",
    };
    settings = {...defaultSettings, ...settings };

    /**
     * @var array
     */
    const facetIndex = {
        // EXAMPLE:
        // color: { // The facet id
        //   values: { // The items with values for the facet
        //     'Red': { // Indexed by value for quick retrieval.
        //       value: 'Red',
        //       items: [] // If this is filled, this is a regular item facet.
        //       subitems: [] // If this is filled, this is a subitem facet.
        //     }
        //   }
        // }
    };

    /**
     * Initialize Csff. Called once on construct, you typically
     * don't want to call this.
     *
     * @return {void}
     */
    this.init = function() {
        // Initialize controls:
        self._initControls();
    };

    /**
     * Execute filtering based on the currently selected facets & search inputs.
     *
     * @param {Element} triggeringElement
     * @return {void}
     */
    this.filter = function(triggeringElement) {
        // Filter by search value
        const searchInputElement = self._domGetSearchInputElement()
        if (searchInputElement) {
            // The search input element is present:
            self._filterBySearch(searchInputElement.value);
        }

        const activeFacetFilters = {};
        self._domGetFacetFilterInputElements().forEach((item, index) => {
            if (item.checked) {
                activeFacetFilters[item.name] = activeFacetFilters[item.name] || [];
                activeFacetFilters[item.name].push(item.value);
            }
        });
        self._domGetItemElements().forEach((item, index) => {
            if (Object.entries(activeFacetFilters).length > 0) {
                // There are active filters, initially hide all:
                item.classList.add(settings._csff_facet_hidden_item_class);
            } else {
                // There are no active filters, unhide all:
                item.classList.remove(settings._csff_facet_hidden_item_class);
            }
        });

        // Apply the filter logic:
        // We intentionally initialize these arrays
        // with null to be able to determine if the result is empty or was never
        // used. This is important for combining results.
        let filterResultItems = null;
        let filterResultSubitems = null;
        for (const [facetId, values] of Object.entries(activeFacetFilters)) {
            const facetFilterResultsItemsSet = new Set();
            const facetFilterResultsSubitemsSet = new Set();

            // Get the results from the single facet. Values of a single facet
            // are always combined by OR (UNION)
            const facetFilterResults = self._facetIndexFilterFacet(facetId, values);
            if (Object.entries(facetFilterResults).length > 0) {
                facetFilterResults.forEach((facetFilterResult) => {
                    if (!facetFilterResult.items.length > 0 && !facetFilterResult.subitems.length > 0) {
                        // This facet has no items at all. Nothing to do!
                    } else if (facetFilterResult.items.length > 0 && facetFilterResult.subitems.length > 0) {
                        // This facet has items and subitems, we don't support that combination yet!
                        // TODO: Implement this whenever it's needed :)
                        console.warn('Facet with id: "' + facetId + '" has items and subitems. This is not supported yet.')
                    } else if (facetFilterResult.items.length > 0) {
                        // This is a regular item facet!
                        // Add regular result items:
                        facetFilterResult.items.forEach((facetFilterResultItem) =>
                            facetFilterResultsItemsSet.add(facetFilterResultItem)
                        );
                    } else if (facetFilterResult.subitems.length > 0) {
                        // This is a subitem facet!
                        // Handle subitem result items (THIS IS SPECIAL!):
                        // Subitems have to be combined by AND (INTERSECT) on the same subitem.
                        // This is required to only return items with an existing COMBINATION
                        // of the selected properties on a subitem.
                        facetFilterResult.subitems.forEach((facetFilterResultSubitem) => {
                            facetFilterResultsSubitemsSet.add(self._getRepresentativeSubitem(facetFilterResultSubitem))
                        })
                    } else {
                        // Should never happen.
                        console.warn('That was unexpected... ;)')
                    }
                })

                // Now (if present) combine them with other facets.
                // This is always done by AND (INTERSECT):
                if (facetFilterResultsItemsSet.size > 0) {
                    if (filterResultItems === null || filterResultItems.length === 0) {
                        // No other results yet. We can't intersect with empty.
                        filterResultItems = [...facetFilterResultsItemsSet];
                    } else {
                        // Intersect with other facet results
                        filterResultItems = filterResultItems.filter((filterResult) => [...facetFilterResultsItemsSet].includes(filterResult));
                    }
                }


                // Now (if present) combine them with other subitem facets.
                // This is always done by AND (INTERSECT):
                if (facetFilterResultsSubitemsSet.size > 0) {
                    if (filterResultSubitems === null || filterResultSubitems.length === 0) {
                        // No other results yet. We can't intersect with empty.
                        filterResultSubitems = [...facetFilterResultsSubitemsSet];
                    } else {
                        // Intersect with other facet results
                        filterResultSubitems = filterResultSubitems.filter((filterResult) => [...facetFilterResultsSubitemsSet].includes(filterResult));
                    }
                }
            }
        }

        // Determine if subitems have been set at all.
        // If filterResultSubitems == [] this means, that
        // subitems were filtered, but no matching results found
        // That means, that in contrast to filterResultSubitems === null
        // the complete result must be empty.
        // If filterResultSubitems contains items, their representative items
        // have to be combined by AND (UNION):
        if (filterResultSubitems !== null) {
            let subitemRepresentatives = [];
            if (filterResultSubitems.length > 0) {
                subitemRepresentatives = self._getRepresentativeItems(filterResultSubitems)
            }
            // Now combine them with other facets.
            // This is always done by AND (INTERSECT):
            if (filterResultItems === null) {
                // No other results yet. We can't intersect with empty.
                filterResultItems = [...subitemRepresentatives];
            } else {
                // Intersect with other facet results
                filterResultItems = filterResultItems.filter((filterResult) => [...subitemRepresentatives].includes(filterResult));
            }
        }

        if (filterResultItems !== null && filterResultItems.length > 0) {
            // Show the ones from the resultset:
            filterResultItems.forEach((item) =>
                item.classList.remove(settings._csff_facet_hidden_item_class)
            )
        }

        // Show results empty text if there are no visible results:
        let hasVisibleItems = false;
        self._domGetItemElements().forEach((item, index) => {
            if (item.offsetWidth > 0 || item.offsetHeight > 0) {
                hasVisibleItems = true;
            }
        });
        if (hasVisibleItems) {
            self._domGetEmptyResultsElement().classList.add(settings._csff_results_empty_hidden_class)
        } else {
            self
                ._domGetEmptyResultsElement()
                .classList.remove(settings._csff_results_empty_hidden_class)
        }
    };

    /**
     * Reset all filters and fulltext search. Show all results unfiltered.
     *
     * @return {void}
     */
    this.reset = function() {
        // Reset search input:
        const searchInputElement = self._domGetSearchInputElement()
        if (searchInputElement) {
            // The search input element is present:
            self._domGetSearchInputElement().value = "";
        }

        // Reset facet filters:
        self._domGetFacetFilterInputElements().forEach((input, index) => {
            input.checked = false;
        });

        self.filter(null);
    };

    /**
     * Applies the full text filter on all items.
     *
     * @param {string} filterValue
     * @return {void}
     */
    this._filterBySearch = function(filterValue) {
        filterValue = filterValue.trim();
        if (filterValue.length == 0) {
            self._domGetItemElements().forEach((item, index) => {
                item.classList.remove(settings._csff_search_hidden_item_class);
            });
            return;
        }
        // Split words by space (multi word or search):
        const words = filterValue.split(" ");
        self._domGetItemElements().forEach((item, index) => {
            item.classList.remove(settings._csff_search_hidden_item_class);
            words.forEach((word) => {
                if (!item.textContent.toLowerCase().includes(word.toLowerCase())) {
                    item.classList.add(settings._csff_search_hidden_item_class);
                }
            });
        });
    };


    /**
     * Helper function to get the representative (parent) DOM Elements
     * for the given elements.
     *
     * This is required to find the elements to hide based on the given
     * (e.g. child) element which contains the facet properties.
     * If this is not (child of) an item, returns null.
     *
     * @see this._getRepresentativeItem
     *
     * @param {array} elements
     *   The DOM Elements to retrieve the representative {Element} items for.
     * @return {array}
     *   The representative {Element} items.
     */
    this._getRepresentativeItems = function(elements) {
        let representativeItemsSet = new Set();
        elements.forEach((element) => {
            representativeItemsSet.add(self._getRepresentativeItem(element))
        })
        return [...representativeItemsSet];
    }

    /**
     * Helper function to get the representative (parent) DOM Element
     * for the given element.
     *
     * This is required to find the element to hide based on the given
     * (e.g. child) element which contains the facet properties.
     * If this is not (child of) an item, returns null.
     *
     * @see this._getRepresentativeItems
     *
     * @param {Element} element
     *   The DOM Element to retrieve the representative {Element} item for.
     * @return {Element|null}
     *   The representative {Element} items.
     */
    this._getRepresentativeItem = function(element) {
        return element.closest(settings._csff_item_selector);
    };

    /**
     * Returns the representative subitem if the given subitem (child) element
     * If element is not (child of) a subitem, returns null.
     *
     *
     * @param {Element} element
     * @return {Element|null}
     */
    this._getRepresentativeSubitem = function(element) {
        return element.closest(settings._csff_subitem_selector);
    };

    /**
     * Internal helper function to Initialize all faceted filter control UI
     * elements in the DOM.
     *
     * @return {void}
     */
    this._initControls = function _initControls() {
        // Insert wrapping container:
        const injectionTarget = self._domGetInjectionTargetElement();
        if (!injectionTarget) {
            throw new Error("Injection target could not be found.");
        }
        injectionTarget.insertAdjacentHTML(
            settings.injection_method,
            settings.template_facets_container
        );

        // Set the elemFacetsContainer variable to store the container:
        const elemFacetsContainer = self._domGetFacetsContainerElement();
        if (!elemFacetsContainer) {
            throw new Error("Facets container could not be found.");
        }

        let hasActveFacets = false;
        if (settings.facets_show) {
            hasActveFacets = self._initControlsFacets();
        }

        if (settings.fulltext_search_show) {
            self._initControlsSearch();
        }

        const showReset = settings.reset_button_show && (settings.fulltext_search_show || hasActveFacets)
        if (showReset) {
            self._initControlsReset();
        }

        // Insert hidden empty results container:
        if (settings.results_empty_text) {
            targetElement.insertAdjacentHTML(
                "beforeend",
                self._theme_results_empty(settings.results_empty_text)
            );
        }
    };

    /**
     * Internal helper function to Initialize the facet controls UI.
     *
     * @return boolean
     *   Returns true if facets have been initialized, else false if there are none.
     */
    this._initControlsFacets = function() {
        let hasActveFacets = false;

        // Collect the properties to build the facets:
        self._collectProperties();

        const { facets } = settings;
        let facetsHtml = "";
        // Sort facets by weight:
        facets.sort((a, b) => a.weight - b.weight);
        settings.facets.forEach((facet) => {
            if (facet.id) {
                const facetId = facet.id;

                // Check if there are values for this facet id:
                if (facetIndex.hasOwnProperty(facetId)) {
                    let facetValuesHTML = '';

                    // Sort values:
                    let facetValueSorted = Object.values(facetIndex[facetId]['values'])
                    switch (facet.values_sort) {
                        case 'value':
                            // Sort by value (natural)
                            facetValueSorted = facetValueSorted.sort((a, b) => {
                                return a.value.localeCompare(b.value, undefined, {
                                    numeric: true,
                                    sensitivity: 'base'
                                })
                            })
                            break;
                        case 'count':
                            // Sort by count
                            facetValueSorted = facetValueSorted.sort((a, b) => a.items.length - b.items.length);
                            break;

                        default:
                            // No sorting.
                            break;
                    }
                    if (facet.values_sort_direction == 'desc') {
                        facetValueSorted.reverse()
                    }
                    // Loop values and add them as checkboxes:
                    facetValueSorted.forEach((properties) => {
                        const value = properties.value;
                        const itemCount = (properties.subitems.length > 0 ? this._getRepresentativeItems(properties.subitems).length : properties.items.length)
                        const facetValueElementHTML = self._theme_facet_value(
                            facetId,
                            value,
                            itemCount
                        )
                        facetValuesHTML += facetValueElementHTML;
                    })

                    // Values for this facet are present:
                    // Create a container for this facet:
                    facetsHtml += self._theme_facet(
                        facetId,
                        facet.title,
                        facet.description,
                        facetValuesHTML
                    );
                }
            }
        });
        if (facetsHtml.length > 0) {
            self
                ._domGetFacetsContainerElement()
                .insertAdjacentHTML("beforeend", facetsHtml)
            hasActveFacets = true;
        }

        // As all HTML was dynamically added, we have to bind the change event afterwards:
        self._domGetFacetFilterInputElements().forEach((input) => {
            input.addEventListener("change", function(e) {
                self.filter(e.target);
            });
        });

        return hasActveFacets;
    };

    /**
     * Helper function to get all facet values for the given facetId.
     *
     * @param {string} facetId
     * @return {Object}
     */
    this._facetIndexGetFacetValues = function(facetId) {
        if (facetIndex.hasOwnProperty(facetId)) {
            return facetIndex[facetId].values;
        }
        return {};
    };

    /**
     * Helper function to return all facet results for the given filterValues.
     * Multiple filterValues are combined by OR (UNION) for the same facet.
     * So all results are returned matching at least one filterValues item.
     *
     * @param {string} facetId
     * @param {array} filterValues
     * @return {array}
     */
    this._facetIndexFilterFacet = function(facetId, filterValues = []) {
        const values = self._facetIndexGetFacetValues(facetId);
        const results = [];
        filterValues.forEach((filterValue, index) => {
            if (values.hasOwnProperty(filterValue)) {
                results.push(values[filterValue]);
            }
        });
        return results;
    };

    /**
     * Helper function to collect all facet related properties (filterable values)
     * from the items.
     *
     * Adds all collected properties on this.facetIndex.
     *
     * @return {void}
     */
    this._collectProperties = function() {
        const items = self._domGetItemElements();
        items.forEach((item) => {
            settings.facets.forEach((facet) => {
                if (facet.id) {
                    const facetId = facet.id;
                    // Find defintion of facets data attribute:
                    const dataAttributeValueName = `data-${settings._csff_facet_data_attribute_prefix}${facetId}${settings._csff_facet_data_attribute_value_suffix}`;
                    let elem = false;
                    if (item.hasAttribute(dataAttributeValueName)) {
                        // The element defines the data attribute itself:
                        elem = item;
                    } else {
                        // Get child element with data attribute:
                        elem = item.querySelector(`[${dataAttributeValueName}]`);
                    }

                    if (elem) {
                        // Check if this is (or is part of) a subitem.
                        let subitem = elem.closest(settings._csff_subitem_selector);
                        if (subitem) {
                            // If this is (in) a subitem, we can expect more than one
                            // data attribute within the item. In this case, add them all
                            subitemElems = item.querySelectorAll(`[${dataAttributeValueName}]`);
                            subitemElems.forEach((subitemElem) => {
                                const value = subitemElem.getAttribute(dataAttributeValueName);
                                // Split value by separator as separate values:
                                const values = value.split(facet.multivalue_separator)
                                values.forEach((splitValue) => self._addToIndex(facetId, splitValue, false, subitemElem))
                            })
                        } else {
                            // This is a regular item, no subitem:
                            const value = elem.getAttribute(dataAttributeValueName);
                            // Split value by separator as separate values:
                            const values = value.split(facet.multivalue_separator)
                            values.forEach((splitValue) => self._addToIndex(facetId, splitValue, elem, false))
                        }

                    }
                } else {
                    console.warn(
                        `Missing "id" for facet definition: ${JSON.stringify(facet)}`
                    );
                }
            });
        });
    };

    /**
     * Helper function to add facet properties to the index on this.facetIndex.
     *
     * @param {string} facetId
     * @param {string} value
     * @param {Element|boolean} element
     * @param {Element|boolean} subitemElem
     *
     * @return {void}
     */
    this._addToIndex = function(facetId, value, element = false, subitemElem = false) {
        // Trim value:
        value = value.trim();
        if (value.length === 0) {
            return;
        }
        if (!facetIndex.hasOwnProperty(facetId)) {
            // No property for the facetId exists yet:
            facetIndex[facetId] = {
                values: {},
            };
        }

        if (!facetIndex[facetId].values.hasOwnProperty(value)) {
            // No such value exists yet for the given facetId:
            facetIndex[facetId]['values'][value] = {
                'value': value,
                'items': [], // If this is filled, this is a regular item facet
                'subitems': [], // If this is filled, this is a subitem facet
            }
        }

        if (subitemElem) {
            // If this is a subitem, push the subitem element.
            // We determine the
            facetIndex[facetId]['values'][value]['subitems'].push(subitemElem);
        } else {
            // Otherwise push the item element:
            // Add the item element to the index:
            facetIndex[facetId]['values'][value]['items'].push(element);
        }
    }

    /**
     * Internal helper function to Initialize the full text search controls UI.
     *
     * @return {void}
     */
    this._initControlsSearch = function() {
        self
            ._domGetFacetsContainerElement()
            .insertAdjacentHTML(
                "afterbegin",
                self._theme_search(
                    settings.fulltext_search_title,
                    settings.fulltext_search_description,
                    settings.fulltext_search_placeholder
                )
            );

        let typeout;
        self._domGetSearchInputElement().addEventListener("input", (e) => {
            if (typeout) {
                clearTimeout(typeout);
            }
            typeout = setTimeout(() => {
                self.filter(e.target);
            }, settings.fulltext_search_delay);
        });
    };

    /**
     * Internal helper function to Initialize the reset controls UI.
     *
     * @return {void}
     */
    this._initControlsReset = function() {
        self
            ._domGetFacetsContainerElement()
            .insertAdjacentHTML(
                "beforeend",
                self._theme_facet_reset(
                    settings.reset_button_title,
                    settings.reset_button_description,
                    settings.reset_button_label
                )
            );
        self
            ._domGetResetButtonInputElement()
            .addEventListener("click", function(e) {
                self.reset();
                // Prevent page reload:
                e.preventDefault();
            });
    };

    /**
     * DOM query helper to retrieve the facets container element from Document.
     *
     * @return {Element}
     */
    this._domGetFacetsContainerElement = function() {
        return document.querySelector(
            settings._csff_controls_facets_container_selector
        );
    };

    /**
     * DOM query helper to retrieve the injection target element from Document.
     *
     * @return {Element}
     */
    this._domGetInjectionTargetElement = function() {
        return document.querySelector(settings.injection_selector);
    };

    /**
     * DOM query helper to retrieve all result item elements from the target
     * element.
     *
     * @return {Element}
     */
    this._domGetItemElements = function() {
        return targetElement.querySelectorAll(settings._csff_item_selector);
    };

    /**
     * DOM query helper to retrieve the injection target element from the
     * facets container element.
     *
     * @return {Element}
     */
    this._domGetSearchInputElement = function() {
        return this._domGetFacetsContainerElement().querySelector(
            settings._csff_search_input_selector
        );
    };

    /**
     * DOM query helper to retrieve the injection target element from the
     * facets container element.
     *
     * @return {Element}
     */
    this._domGetFacetFilterInputElements = function() {
        return this._domGetFacetsContainerElement().querySelectorAll(
            settings._csff_filter_input_selector
        );
    };

    /**
     * DOM query helper to retrieve the reset button element from the
     * facets container element.
     *
     * @return {Element}
     */
    this._domGetResetButtonInputElement = function() {
        return this._domGetFacetsContainerElement().querySelector(settings._csff_reset_input_selector);
    };

    /**
     * DOM query helper to retrieve the empty results container element from the
     * facets container element.
     *
     * @return {Element}
     */
    this._domGetEmptyResultsElement = function() {
        return targetElement.querySelector(settings._csff_results_empty_selector);
    };

    /**
     * Returns the "Facet container" template string, filled with given variables.
     *
     * @return {string}
     */
    this._theme_facets_container = function() {
        return self._interpolateTemplate(settings.template_facets_container, {});
    };

    /**
     * Returns the "Facet" template string, filled with given variables.
     *
     * @param {string} id
     * @param {string} title
     * @param {string} description
     * @param {string} content
     * @return {string}
     */
    this._theme_facet = function(id, title, description, content) {
        return self._interpolateTemplate(settings.template_facet, {
            id: id,
            title: title,
            description: description,
            content: content,
        });
    };

    /**
     * Returns the "Single facet value" template string, filled with given variables.
     *
     * @param {string} facetId
     * @param {string} value
     * @param {string} count
     * @return {string}
     */
    this._theme_facet_value = function(facetId, value, count) {
        const id = `${facetId}-${value}`.replace(/\W/g, "-").toLowerCase();
        return self._interpolateTemplate(settings.template_facet_value, {
            facetId: facetId,
            id: id,
            value: value,
            count: count,
        });
    };

    /**
     * Returns the "Reset" template string, filled with given variables.
     *
     * @param {string} title
     * @param {string} description
     * @param {string} label
     * @return {string}
     */
    this._theme_facet_reset = function(title = "", description = "", label = "") {
        const content = self._interpolateTemplate(settings.template_facet_reset, {
            label: label,
        });
        const id = "csff-reset";
        return self._theme_facet(id, title, description, content);
    };

    /**
     * Returns the "search" template string, filled with given variables.
     *
     * @param {string} title
     * @param {string} description
     * @param {string} placeholder
     * @return {string}
     */
    this._theme_search = function(title = "", description = "", placeholder = "") {
        const content = self._interpolateTemplate(settings.template_search, {
            placeholder: placeholder,
        });
        const id = "csff-search";
        return self._theme_facet(id, title, description, content);
    };

    /**
     * Returns the "Empty results" template string, filled with given variables.
     *
     * @param {string} content
     * @return {string}
     */
    this._theme_results_empty = function(content) {
        return self._interpolateTemplate(settings.template_results_empty, {
            content: content,
        });
    };

    /**
     * Helper function to replace variables in the JavaScript templating syntax
     * ${} by a key-values object.
     *
     * This does NOT execute / eval code, simply replaces strings.
     *
     * @param {string} templateString
     * @param {Object} replacements Key-Value object with replacement texts.
     * @return {string}
     */
    this._interpolateTemplate = function(templateString, replacements) {
        return Object.entries(replacements).reduce(
            (result, [key, value]) => result.replaceAll(`$\{${key}}`, `${value}`),
            templateString
        );
    };

    // Call init and return self.
    this.init();
    return this;
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc