PageSourceSearch

https://www.melett.com.pt/wp-content/themes/meletttheme/assets/scripts/components/MelettSearch.js?v=1776354865

js melett.com.pt collected 2026-10-06 06:38:45 UTC 10,160 bytes, 301 lines download raw bytes

1/**
2 * Melett Component: Search.
3 *
4 * @package Melett\Scripts\Components
5 */
6
7import gtmSearch from "analytics/search";
8import gtmViewItemList from "analytics/viewItemList";
9import gtmSelectItem from "analytics/selectItem";
10import presentSlug from "helpers/presentSlug";
11import formatStockCode from "helpers/formatStockCode";
12import {
13    ajaxURL,
14    getNonce,
15    getMessage,
16    getCurrentLanguage,
17    getMasterPricingLink,
18    getTNKSLink,
19} from "helpers/serverData";
20
21const $ = jQuery;
22/**
23 * DOM procedures for Melett Custom Search bars.
24 *
25 * @template template-parts/forms/search.php
26 * @author Contedia Ltd
27 */
28class MelettSearch {
29    /**
30     * The Container for the Melett Search bar.
31     *
32     * Set DOM elements and event listeners.
33     *
34     * @param {string} selector The selector for the search container
35     */
36    constructor(data) {
37        this.container                  = $(data.selector);
38
39        if(!this.container.length) {
40            return;
41        }
42
43        this.name                       = data.name;
44        this.title                      = data.title ?? presentSlug(data.name);
45        this.form                       = this.container.find('.melett-search-form');
46        this.searchBy                   = data.searchBy;
47        this.field                      = this.form.find('.search-field');
48        this.type                       = this.form.data('search-type');
49        this.resultsContainer           = this.container.find('.ajax-results-container');
50        this.ajaxURL                    = ajaxURL;
51        this.currentLang                = getCurrentLanguage();
52
53        /**
54         * Clear and hide Ajax Search results containers of items when search is unfocused.
55         */
56        $(document).on("click", function(event){
57            if(!$(event.target).closest(data.selector).length){
58                $('.melett-search-form-container .ajax-results-container').hide();
59                $('.melett-search-form-container .ajax-results-container').empty();
60                $('.melett-search-form-container .melett-search-form').removeClass('form-in-focus');
61            }
62        });
63
64        this.debouncedInput = this.debounce(this.input.bind(this), 500);
65
66        this.form.submit(this.submit.bind(this));
67        this.field.on('input', (e) => this.debouncedInput(e));
68        this.field.focusin(this.focusSearch);
69
70    }
71
72    debounce(func, wait) {
73        let timeout;
74        return function executedFunction(...args) {
75            const later = () => {
76                clearTimeout(timeout);
77                func(...args);
78            };
79            clearTimeout(timeout);
80            timeout = setTimeout(later, wait);
81        };
82    }
83
84
85    getValue() {
86        return this.field.val().trim();
87    }
88
89    submit(e) {
90        let searchVal = this.getValue();
91
92        gtmSearch({
93            searchBy: this.searchBy,
94            searchTerm: searchVal,
95            searchBar: this.name,
96        });
97        this.init(e, (subResponse) => {
98            let d = JSON.parse(subResponse);
99            let success = d.success;
100            let excluded = d.excluded ?? false;
101            let partsList = d.partsList;
102            let redirectURL = null;
103
104            this.resultsContainer.hide();
105            this.resultsContainer.css('max-height', '0');
106            this.resultsContainer.empty();
107
108            if (success) {
109                if(partsList.length === 1) {
110                    let part = partsList[0];
111                    gtmSelectItem({
112                        listId: this.name,
113                        listName: this.title,
114                        item: { id: part.code }
115                    });
116                    redirectURL = `${window.location.origin}/master-pricing/?partcode=${encodeURIComponent(`${part.code}`)}`
117                } else {
118                    if (excluded) {
119                        redirectURL = `${window.location.origin}/product-disabled/?partno=${encodeURIComponent(searchVal)}`
120                    } else {
121                        redirectURL = `${window.location.origin}/turbo-no-keyword-search/?partno=${encodeURIComponent(searchVal)}`
122                    }
123                }
124
125                window.location = redirectURL;
126            }
127        });
128    }
129
130    async input(e) {
131        await this.init(e, (subResponse) => {
132            let d = JSON.parse(subResponse);
133            let success = d.success;
134            let partsList = d.partsList;
135            this.#clearResponseItems();
136
137            if (success) {
138                let gtmArgs = {
139                    listId: this.name,
140                    listName: this.title,
141                    items: []
142                };
143                this.form.removeClass('loading');
144
145                if(partsList.length) {
146                    this.resultsContainer.show();
147                    this.resultsContainer.css('max-height', '550px');
148                    partsList.map((part, partIndex) => {
149                        let defaultDesc = String(part.name_en).trim();
150                        let translatedDesc = part.hasOwnProperty(`name_${this.currentLang}`) ? String(part[`name_${this.currentLang}`]).trim() : false;
151                        let desc = translatedDesc && translatedDesc.length ? translatedDesc : defaultDesc;
152                        let isObsolete = desc.toLowerCase().includes('obsolete');
153                        let isBlank = part.code.toLowerCase().includes('blank');
154
155                        if (!isObsolete && !isBlank) {
156                            this.#ajaxResponseItem(
157                                `${formatStockCode(part.code)} - ${desc}`,
158                                getMasterPricingLink(part.code),
159                                {
160                                    stockCode: part.code,
161                                    description: desc,
162                                    index: partIndex
163                                }
164                            );
165
166                            gtmArgs.items.push({
167                                id: part.code,
168                                description: desc,
169                                index: partIndex,
170                            });
171                        }
172
173                    })
174
175                    gtmViewItemList(gtmArgs);
176                } else {
177                    this.resultsContainer.show();
178                    this.resultsContainer.css('max-height', 'unset');
179                    this.#ajaxResponseItem(
180                        getMessage('search_click_here_for_further_results'),
181                        getTNKSLink(this.getValue()),
182                    );
183                }
184            }
185        })
186    }
187
188    /**
189     * Initiates the search submission.
190     *
191     * Declare common variables that are essential for any search submission,
192     * feed them to the relevant AJAX submission
193     * and call the delegated "success" function.
194     *
195     * @param e The event object (submission, input change, etc)
196     * @param successF The function to be called when the AJAX request is successful (no errors).
197     * @returns {Promise<void>}
198     */
199    async init(e, successF) {
200        e.preventDefault();
201        let searchVal = this.getValue();
202        let data = {};
203
204        if(searchVal.length < 3) {
205            return;
206        }
207
208        this.form.addClass('loading');
209
210        if(this.type === 'turbo-parts') {
211            data.action = 'melett_parts_list_search';
212            data.stock_code = searchVal;
213            data.nonce = getNonce('mtt_search_nonce');
214            this.searchAjaxSub(
215                data,
216                successF
217            )
218        }
219    }
220
221    /**
222     * Send the AJAX request with supplied data.
223     *
224     * This is the template for all Melett search requests.
225     *
226     * @param {object} subData Post data for the AJAX request.
227     * @param {dom element} ajaxCont The HTML container for the AJAX search result items.
228     * @param {function} successF The function to call when the request is successful (no errors).
229     * @returns {Promise<void>}
230     */
231    searchAjaxSub(subData, successF) {
232        $.ajax({
233            url: this.ajaxURL,
234            type: 'POST',
235            dataType: "text",
236            data: subData,
237            success: function(data, textStatus, jqXHR) {
238                successF(data);
239            },
240            error: function(jqXHR, textStatus, errorThrown){
241                console.log(textStatus + errorThrown);
242            }
243        });
244    }
245
246    /**
247     * Run procedure for when the user clicks on a search field.
248     *
249     * @param e Event object.
250     * @returns {Promise<void>}
251     */
252    focusSearch(e)
253    {
254        $(this.form).addClass('form-in-focus');
255    }
256
257    /**
258     * Generate an AJAX search response items in the results container.
259     *
260     * @param url
261     * @param content
262     * @param data
263     * @returns {Promise<void>}
264     */
265    #ajaxResponseItem(content, url = false, data = {}) {
266        let item;
267        if (!url) {
268            item = $(`<p class="ajax-result-item">${content}</p>`);
269        } else {
270            item = $(`<a class="ajax-result-item ajax-result-item--link" href="${url}">${content}</a>`);
271            if (data.stockCode && data.description && data.index) {
272                item.click(e => {
273                    gtmSearch({
274                        searchBy: this.searchBy,
275                        searchTerm: data.stockCode ?? '',
276                        searchBar: this.name,
277                    });
278
279                    gtmSelectItem({
280                        listId: this.name,
281                        listName: this.title,
282                        item: {
283                            id: data.stockCode,
284                            description: data.description,
285                            index: data.index,
286                        },
287                    });
288                });
289            }
290        }
291        this.resultsContainer.append(item);
292    }
293
294    #clearResponseItems() {
295        this.resultsContainer.hide();
296        this.resultsContainer.css('max-height', '0');
297        this.resultsContainer.empty();
298    }
299}
300
301export default MelettSearch;

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.