PageSourceSearch

https://www.ed-info.net/-/media/Base-Themes/SearchTheme/Scripts/component-search-box.js

js ed-info.net collected 2026-09-25 04:27:43 UTC 21,203 bytes, 470 lines download raw bytes

1/**
2 * Search box component functionality
3 * @module searchBox
4 * @param  {jQuery} $ Instance of jQuery
5 * @param  {Document} document dom document object
6 * @return {Object} list of methods for working with search box
7*/
8XA.component.search.box = (function ($, document) {
9    var api = {},
10        searchBoxViews = [],
11        searchBoxModels = [],
12        queryModel,
13        urlHelperModel,
14        searchResultModels;
15    /**
16    * @name module:searchBox.SearchBoxModel
17    * @constructor
18    * @augments Backbone.Model
19    */
20    var SearchBoxModel = Backbone.Model.extend(
21        /** @lends module:searchBox.SearchBoxModel.prototype **/
22        {
23            /**
24            * Default model options
25            * @default
26            */
27            defaults: {
28                searchEngine: "",
29                typeahead: "",
30                dataProperties: {},
31                searchQuery: "",
32                loadingInProgress: false,
33                sig: []
34            },
35            /**
36            * create instance of 
37            * ["Bloodhound"]{@link https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md}
38            * width necessary options
39            * @memberOf module:searchBox.SearchBoxModel
40            * @method
41            * @alias module:searchBox.SearchBoxModel.initSearchEngine
42            */
43            initSearchEngine: function () {
44                var inst = this,
45                    siteName = XA.component.search.ajax.getPrameterByName("sc_site"),
46                    searchEngine = new Bloodhound({
47                        datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name"),
48                        queryTokenizer: Bloodhound.tokenizers.whitespace,
49                        limit: inst.get("dataProperties").p,
50                        remote: {
51                            url: urlHelperModel.createSiteUrl(inst.createSuggestionsUrl($.extend({ l: inst.getLanguage() }, inst.get("dataProperties")), inst.get("searchQuery")), siteName),
52                            filter: function (list) {
53                                var searchArr = [];
54                                return _.map(list.Results, function (item) { return { html: item.Html }; });
55                            },
56                            replace: function () {
57                                var valueProvider = inst.get("valueProvider"),
58                                    searchQuery = valueProvider(),
59                                    properties = $.extend({ l: inst.getLanguage() }, inst.get("dataProperties"));
60
61                                return urlHelperModel.createSiteUrl(inst.createSuggestionsUrl(properties, searchQuery), siteName);
62                            },
63                            ajax: {
64                                beforeSend: function () {
65                                    inst.set({ "loadingInProgress": true });
66                                },
67                                complete: function () {
68                                    inst.set({ "loadingInProgress": false });
69                                }
70                            }
71                        }
72                    });
73
74                searchEngine.initialize();
75                this.set({ searchEngine: searchEngine });
76            },
77            /**
78            * Creates url from which suggestion should be taken
79            * @memberOf module:searchBox.SearchBoxModel
80            * @method
81            * @param {Object} properties list of properties for suggestion functionality 
82            * @param {String} searchQuery text from search box
83            * @alias module:searchBox.SearchBoxModel.createSuggestionsUrl
84            * @returns {string} search url based on setups
85            */
86            createSuggestionsUrl: function (properties, searchQuery) {
87                var suggestionsMode = this.get("dataProperties").suggestionsMode,
88                    resultsEndpoint = this.get("dataProperties").endpoint,
89                    suggestionsEndpoint = this.get("dataProperties").suggestionEndpoint;
90
91                switch (suggestionsMode) {
92                    case "ShowPredictions": {
93                        return urlHelperModel.createPredictiveSearchUrl(suggestionsEndpoint, properties, searchQuery);
94                    }
95                    default: {
96                        return urlHelperModel.createPredictiveSearchUrl(resultsEndpoint, properties, searchQuery);
97                    }
98                }
99            },
100            /**
101            * get search box signature
102            * @memberOf module:searchBox.SearchBoxModel
103            * @method
104            * @alias module:searchBox.SearchBoxModel.getSignature
105            * @returns {string} search box signature
106            */
107            getSignature: function () {
108                var rawSignature = this.get("dataProperties").searchResultsSignature,
109                    signatures;
110
111                if (typeof rawSignature === "undefined" || rawSignature === null) {
112                    return "";
113                }
114
115                signatures = rawSignature.split(',');
116
117                if (rawSignature === "") {
118                    return "";
119                } else {
120                    return signatures[0];
121                }
122            },
123            /**
124            * get search box language
125            * @memberOf module:searchBox.SearchBoxModel
126            * @method
127            * @alias module:searchBox.SearchBoxModel.getLanguage
128            * @returns {string} selected language
129            */
130            getLanguage: function () {
131                var dataProperties = this.get("dataProperties"),
132                    searchResultModels = XA.component.search.results.searchResultModels,
133                    languageSource = dataProperties.languageSource,
134                    signature = this.getSignature(),
135                    model;
136
137                switch (languageSource) {
138                    case "CurrentLanguage":
139                    case "AllLanguages": {
140                        return dataProperties.l;
141                    }
142                    default: {
143                        model = searchResultModels.filter(function (element) {
144                            return element.get("dataProperties").sig === signature;
145                        })[0];
146                        if (typeof model !== "undefined") {
147                            return model.get("dataProperties").l;
148                        }
149                        break;
150                    }
151                }
152
153                return "";
154            }
155        });
156    /**
157    * @name module:searchBox.SearchBoxView
158    * @constructor
159    * @augments Backbone.View
160    */
161    var SearchBoxView = XA.component.search.baseView.extend(
162        /** @lends module:searchBox.SearchBoxView.prototype **/
163        {
164            /**
165             *Initializes typeahead for view, sets up watchers for change on model
166             *and call ["initSearchEngine"]{@link module:searchBox.SearchBoxModel}
167            * @memberof module:searchBox.SearchBoxView.prototype
168            */
169            initialize: function () {
170                var inst = this,
171                    dataProperties = this.$el.data(),
172                    typeahead;
173
174                dataProperties.properties.targetSignature = dataProperties.properties.targetSignature !== null ? dataProperties.properties.targetSignature : "";
175
176                this.model.set({ dataProperties: dataProperties.properties });
177                this.model.set("sig", this.translateSignatures(dataProperties.properties.searchResultsSignature, "q"));
178                this.model.initSearchEngine();
179                this.model.on("change:loadingInProgress", this.loading, this);
180
181                typeahead = this.$el.find(".search-box-input").typeahead({
182                    hint: true,
183                    minLength: dataProperties.properties.minSuggestionsTriggerCharacterCount
184                },
185                    {
186                        source: inst.model.get("searchEngine").ttAdapter(),
187                        displayKey: function () { return inst.$el.find(".search-box-input.tt-input").val(); },
188                        templates: {
189                            suggestion: function (data) {
190                                var suggestionsMode = dataProperties.properties.suggestionsMode,
191                                    text = data.html.replace(/(<([^>]+)>)/ig, ""),
192                                    suggestionText = text !== "" ? text : data.html;
193
194                                switch (suggestionsMode) {
195                                    case "ShowPredictions":
196                                    case "ShowSearchResultsAsPredictions": {
197                                        return '<div class="sugesstion-item">
197' + suggestionText + '</div>';
198                                    }
199                                    default: {
200                                        return '<div class="sugesstion-item">' + data.html + '</div>';
201                                    }
202                                }
203                            }
204                        }
205                    }).on('typeahead:selected', this.suggestionSelected.bind(inst));
206
207                //TODO: Seems like bellow line isn't needed as updateSearchBoxValue() function will be callend when hash will change - to be tested in non/multi signatures cases
208                //$searchBox.val(hashObj[this.model.get("sig")] !== undefined ? hashObj[this.model.get("sig")] : "");
209
210                this.model.set({ typeahead: typeahead });
211                this.model.set({
212                    valueProvider: function () {
213                        return inst.$el.find(".search-box-input.tt-input").val();
214                    }
215                });
216                XA.component.search.vent.on("hashChanged", this.updateSearchBoxValue.bind(this));
217            },
218            /**
219             * list of events for Backbone View
220             * @memberof module:searchBox.SearchBoxView
221             * @alias module:searchBox.SearchBoxView#events
222             */
223            events: {
224                "click .search-box-button": "updateQueryModelClick",
225                "click .search-box-button-with-redirect": "updateQueryWithRedirect",
226                "keypress .search-box-input.tt-input": "predictiveSearch",
227                "keydown .search-box-input.tt-input": "predictiveSearch"
228            },
229            /**
230            * Toggle css class "loading-in-progress"
231            * @memberof module:searchBox.SearchBoxView
232            * @alias module:searchBox.SearchBoxView#loading
233            */
234            loading: function () {
235                this.$el.toggleClass("loading-in-progress");
236            },
237            /**
238            * Called when selected value from search box and call
239            * ["performSearch"]{@link module:performSearch} or re
240            * @memberof module:searchBox.SearchBoxView
241            * @param {jQuery<Event>} event jQuery event object
242            * @param {DomElement} data DOM element that was selected
243            * @alias module:searchBox.SearchBoxView#suggestionSelected
244            */
245            suggestionSelected: function (event, data) {
246                event.preventDefault();
247
248                var suggestionsMode = this.model.get("dataProperties").suggestionsMode;
249                var text;
250                try {
251                    text = $(data.html).text();
252                }
253                catch (error) {
254                    text = "";
255                }
256
257                var suggestionText = text != "" ? text : data.html,
258                    link;
259
260                switch (suggestionsMode) {
261                    case "ShowPredictions":
262                    case "ShowSearchResultsAsPredictions": {
263                        this.performSearch(suggestionText);
264                        break;
265                    }
266                    default: {
267                        link = $(data.html).find("a");
268                        if (link.length) {
269                            window.location.href = $(link[0]).attr("href");
270                        }
271                        break;
272                    }
273                }
274            },
275            /**
276            * Updates hash value and makes redirect based on value from search box
277            * @memberof module:searchBox.SearchBoxView
278            * @param {jQuery<Event>} event jQuery event object
279            * @alias module:searchBox.SearchBoxView#updateQueryWithRedirect
280            */
281            updateQueryWithRedirect: function (event) {
282                event.preventDefault();
283
284                var resultPage = this.model.get("dataProperties").resultPage,
285                    targetSignature = this.model.get("dataProperties").targetSignature,
286                    searchResultsSignature = this.model.get("dataProperties").searchResultsSignature,
287                    query = encodeURIComponent(this.$el.find(".search-box-input.tt-input").val()),
288                    sig = this.model.get("sig"),
289                    queryWithSignature = {};
290
291                if (targetSignature !== "") {
292                    queryWithSignature = this.updateSignaturesHash([targetSignature + "_q"], query, this.createOffsetObject());
293                } else {
294                    queryWithSignature = this.updateSignaturesHash(sig, query, this.createOffsetObject());
295                }
296
297                window.location.href = urlHelperModel.createRedirectSearchUrl(resultPage, queryWithSignature, searchResultsSignature, targetSignature);
298            },
299            /**
300            * Takes search box value as a query value and calls
301            * ["updateQueryModel"]{@link module:module:searchBox.SearchBoxView#updateQueryModel}
302            * @memberof module:searchBox.SearchBoxView
303            * @param {jQuery<Event>} event jQuery event object
304            * @alias module:searchBox.SearchBoxView#updateQueryModelClick
305            */
306            updateQueryModelClick: function (event) {
307                event.preventDefault();
308
309                XA.component.search.vent.trigger("new-search");
310
311                var query = this.$el.find(".search-box-input.tt-input").val();
312                this.closeDropdown();
313                this.updateQueryModel(query);
314            },
315            /**
316             * Updates hash based on searchValue
317             * @memberof module:searchBox.SearchBoxView
318             * @param {String} query jQuery event object
319             * @alias module:searchBox.SearchBoxView#updateQueryModel
320             */
321            updateQueryModel: function (query) {
322                var searchValue = {},
323                    offsetSignatures = this.translateSignatures(this.model.get("dataProperties").searchResultsSignature, "e"),
324                    sig = this.model.get("sig"),
325                    i;
326
327                for (i = 0; i < sig.length; i++) {
328                    searchValue[sig[i]] = query;
329                    searchValue[offsetSignatures[i]] = 0;
330                }
331
332                queryModel.updateHash(searchValue, this.model.get("dataProperties").targetUrl);
333            },
334            /**
335            * Calls ["performSearch"]{@link module:searchBox.SearchBoxView#performSearch} 
336            * by pressing Enter key
337            * @memberof module:searchBox.SearchBoxView
338            * @param {Event} event Event object
339            * @alias module:searchBox.SearchBoxView#predictiveSearch
340            */
341            predictiveSearch: function (event) {
342                if (event.keyCode === 13) {
343                    event.preventDefault();
344                    this.performSearch(this.$el.find(".search-box-input.tt-input").val());
345                }
346            },
347            /**
348             * Makes search
349             * @memberof module:searchBox.SearchBoxView
350             * @param {String} query value from search box
351             * @alias module:searchBox.SearchBoxView#performSearch
352             */
353            performSearch: function (query) {
354                var properties = this.model.get("dataProperties"),
355                    targetSignature = properties.targetSignature,
356                    searchResultsSignature = properties.searchResultsSignature,
357                    resultPage = properties.resultPage,
358                    sig = this.model.get("sig"),
359                    queryWithSignature = {};
360
361                XA.component.search.vent.trigger("new-search");
362
363                this.closeDropdown();
364
365                if (resultPage === "") {
366                    this.updateQueryModel(query);
367                    $('.search-box-input').typeahead('val', query);
368                } else {
369                    query = encodeURIComponent(query);
370                    if (targetSignature !== "") {
371                        queryWithSignature = this.updateSignaturesHash([targetSignature + "_q"], query, this.createOffsetObject())
372                    } else {
373                        queryWithSignature = this.updateSignaturesHash(sig, query, this.createOffsetObject())
374                    }
375                    window.location.href = urlHelperModel.createRedirectSearchUrl(resultPage, queryWithSignature, searchResultsSignature, targetSignature);
376                }
377            },
378            /**
379             * Creates object that contain signatures
380             * @memberof module:searchBox.SearchBoxView
381             * @alias module:searchBox.SearchBoxView#createOffsetObject
382             */
383            createOffsetObject: function () {
384                var sig = this.model.get("sig"),
385                    targetSignature = this.model.get("dataProperties").targetSignature,
386                    signature = targetSignature !== "" ? targetSignature : this.model.get("dataProperties").searchResultsSignature,
387                    offsetSignatures = this.translateSignatures(signature, "e"),
388                    offsetObject = {},
389                    i;
390
391                for (i = 0; i < sig.length; i++) {
392                    offsetObject[offsetSignatures[i]] = 0;
393                }
394
395                return offsetObject;
396            },
397            /**
398             * Sets value into search box based on hash 
399             * @memberof module:searchBox.SearchBoxView
400             * @alias module:searchBox.SearchBoxView#updateSearchBoxValue
401             */
402            updateSearchBoxValue: function () {
403                var hashObj = queryModel.parseHashParameters(window.location.hash),
404                    el = this.$el.find(".search-box-input.tt-input"),
405                    sig = this.model.get("sig"),
406                    i;
407
408                for (i = 0; i < sig.length; i++) {
409                    if (hashObj.hasOwnProperty(sig[i])) {
410                        el.val(decodeURIComponent(hashObj[sig[i]]));
411                    } else {
412                        el.val("");
413                    }
414                }
415            },
416            /**
417             * Closes typeahead drop down
418             * @memberof module:searchBox.SearchBoxView
419             * @alias module:searchBox.SearchBoxView#closeDropdown
420             */
421            closeDropdown: function () {
422                this.$el.find(".search-box-input").typeahead('close');
423            }
424        });
425    /**
426     * For each search box on a page creates instance of 
427     * ["searchBoxModel"]{@link module:searchBox.SearchBoxModel} and 
428     * ["SearchBoxView"]{@link module:searchBox.SearchBoxView} 
429     * @memberOf module:searchBox
430     * @alias module:searchBox.init
431     */
432    api.init = function () {
433        if ($("body").hasClass("on-page-editor")) {
434            return;
435        }
436        queryModel = XA.component.search.query;
437        searchResultModels = XA.component.search.results.searchResultModels;
438        urlHelperModel = XA.component.search.url;
439
440        var searchBox = $(".search-box:not(.initialized)");
441
442        _.each(searchBox, function (elem) {
443            var $el = $(elem),
444                boxModel = new SearchBoxModel();
445
446            searchBoxModels.push(boxModel);
447            searchBoxViews.push(new SearchBoxView({ el: $el, model: boxModel }));
448            $el.addClass("initialized");
449        });
450    };
451    /**
452    * Extends search box API with 
453    * ["searchBoxViews"]{@link module:searchBox.searchBoxViews} 
454    * @memberOf module:searchBox
455    * @alias module:searchBox.searchBoxViews
456    */
457    api.searchBoxViews = searchBoxViews;
458    /**
459* Extends search box API with 
460* ["searchBoxModels"]{@link module:searchBox.searchBoxModels} 
461* @memberOf module:searchBox
462* @alias module:searchBox.searchBoxViews
463*/
464    api.searchBoxModels = searchBoxModels;
465
466    return api;
467
468}(jQuery, document));
469
470XA.register('searchBox', XA.component.search.box);

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.