PageSourceSearch

https://horacemann.com/-/media/Base-Themes/SearchTheme/Scripts/component-search-results-filter.js

js horacemann.com collected 2026-09-24 18:23:26 UTC 25,612 bytes, 534 lines download raw bytes

1/**
2 * Facet facet result filter component functionality
3 * @module facetResultsFilter
4 * @param  {jQuery} $ Instance of jQuery
5 * @param  {Document} document dom document object
6 * @return {Object} list of methods for working with component facet result filter
7*/
8XA.component.search.facet.resultsfilter = (function ($, document) {
9    /**
10    * This object stores all public api methods
11    * @type {Object.<Methods>}
12    * @memberOf module:facetResultsFilter
13    */
14    var api = {},
15        urlHelperModel,
16        queryModel,
17        apiModel;
18    /**
19    * @name module:facetResultsFilter.FacetResultsFilterModel
20    * @constructor
21    * @augments Backbone.Model
22    */
23    var FacetResultsFilterModel = XA.component.search.baseModel.extend(
24        /** @lends module:facetResultsFilter.FacetResultsFilterModel.prototype **/
25        {
26            /**
27            * Default model options
28            * @default
29            */
30            defaults: {
31                template: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " +
32                    "_.forEach(facet.Values, function(value,key){" +
33                    "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" +
34                    "<span><%= value.Name !== '' ? value.Name : emptyText %> " +
35                    "<span class='facet-count'>(<%= value.Count %>)</span>" +
36                    "</span>" +
37                    "</p><%" +
38                    " }); %>" +
39                    "<% if(highlightBehaviour>=1 && resultsCount>highlightBehaviour){ %>" +
40                    "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" +
41                    "<%}%>" +
42                    "</div>",
43
44                templateMulti: "<div class='facet-search-filter <% if(!showAllFacets){%>facet-hided<%}%>'><% " +
45                    "_.forEach(facet.Values, function(value,key){" +
46                    "%><p class='facet-value <% if(highlightBehaviour<=key){ %> hide-facet-value <% } %>' data-facetValue='<%= value.Name !== '' ? encodeURI(value.Name) : '_empty_' %>'>" +
47                    "<input type='checkbox' name='facetValue' />" +
48                    "<label for='facetName'><%= value.Name !== '' ? value.Name : emptyText %> " +
49                    "<span class='facet-count' data-facetCount='<%= value.Count %>'>(<%= value.Count %>)</span>" +
50                    "</label>" +
51                    "</p><%" +
52                    " }); %>" +
53                    "<% if(highlightBehaviour>=1 && resultsCount>highlightBehaviour){ %>" +
54                    "<div class='toogle-facet-visibility'><% if(showAllFacets){ %><%=showLessText%><%}else{%><%=showMoreText%><%} %></div>" +
55                    "<%}%>" +
56                    "</div>",
57                dataProperties: {},
58                blockNextRequest: false,
59                resultData: {},
60                timeStamp: '',
61                showAllFacets: false,
62                sig: []
63            },
64            /**
65            * Listens to changes on facets and hash
66           * @listens module:XA.component.search.vent~event:facet-data-loaded
67           * @listens module:XA.component.search.vent~event:facet-data-filtered
68           * @listens module:XA.component.search.vent~event:facet-data-partial-filtered
69           * @listens module:XA.component.search.vent~event:hashChanged
70           */
71            initialize: function () {
72                //event to get data at the begining or in case that there are no hash parameters in the url - one request for all controls
73                XA.component.search.vent.on("facet-data-loaded", this.processData.bind(this));
74                //if in the url hash we have this control facet name (someone clicked this control) then we have to listen for partial filtering
75                XA.component.search.vent.on("facet-data-partial-filtered", this.processData.bind(this));
76                //in case that we are not filtering by this control (not clicked)
77                XA.component.search.vent.on("facet-data-filtered", this.processData.bind(this));
78                //event after change of hash
79                XA.component.search.vent.on("hashChanged", this.updateComponent.bind(this));
80
81                this.set({ facetArray: [] });
82            },
83            /**
84             * Toggles value of blockNextRequest variable
85             * @memberof module:facetResultsFilter.FacetResultsFilterModel
86             * @alias module:facetResultsFilter.FacetResultsFilterModel#toggleBlockRequests
87             */
88            toggleBlockRequests: function () {
89                var state = this.get("blockNextRequest");
90                this.set(this.get("blockNextRequest"), !state);
91            },
92            /**
93            * Processes data that comes as parameter update
94            * model and sort facets
95            * @param {Object} data Data from server with facet values
96            * @memberof module:facetResultsFilter.FacetResultsFilterModel
97            * @alias module:facetResultsFilter.FacetResultsFilterModel#processData
98            */
99            processData: function (data) {
100                var inst = this,
101                    dataProperties = this.get('dataProperties'),
102                    sig = dataProperties.searchResultsSignature.split(','),
103                    sortOrder = dataProperties.sortOrder,
104                    i;
105
106                if (data.Signature === null) {
107                    data.Signature = "";
108                }
109
110
111                for (i = 0; i < sig.length; i++) {
112                    if (data.Facets.length > 0 && (data.Signature === sig[i])) {
113                        var facedData = _.find(data.Facets, function (f) {
114                            return f.Key.toLowerCase() === inst.get('dataProperties').f.toLowerCase();
115                        });
116                        if (facedData !== undefined) {
117                            this.sortFacetArray(sortOrder, facedData.Values);
118                            inst.set({ resultData: facedData });
119                        }
120                    } else {
121                        inst.set({ resultData: [] });
122                    }
123                }
124            },
125            /**
126             * Updates model value 'facetArray' with values from valuesString parameter
127             * @param {String} valuesString facet values separated by coma
128             * @memberof module:facetResultsFilter.FacetResultsFilterModel
129             * @alias module:facetResultsFilter.FacetResultsFilterModel#updateFacetArray
130             */
131            updateFacetArray: function (valuesString) {
132                if (valuesString) {
133                    var values = valuesString.split("||"),
134                        array = [];
135                    for (var i = 0; i < values.length; i++) {
136                        array.push(values[i]);
137                    }
138                    this.set({ facetArray: _.unique(array) });
139                }
140            },
141            /**
142             * Sets option selected value to model based on hash
143             * @param {Object} hash Hash stored as an object
144             * @memberof module:facetResultsFilter.FacetResultsFilterModel
145             * @alias module:facetResultsFilter.FacetResultsFilterModel#updateComponent
146             */
147            updateComponent: function (hash) {
148                var sig = this.get("sig");
149                for (i = 0; i < sig.length; i++) {
150                    if (!hash.hasOwnProperty(sig[i])) {
151                        this.set({ facetArray: [] });
152                    } else {
153                        this.updateFacetArray(hash[sig[i]]);
154                    }
155                    //in some cases change of facetArray doesn't trigger model change event (why?) and view isn't updates
156                    //and because of that timeStamp is updated which properly triggers model change event
157                    this.set("timeStamp", (new Date()).getTime());
158                }
159            }
160        });
161    /**
162    * @name module:facetResultsFilter.FacetResultsFilterView
163    * @constructor
164    * @augments Backbone.View
165    */
166    var FacetResultsFilterView = XA.component.search.baseView.extend(
167        /** @lends module:facetResultsFilter.FacetResultsFilterView.prototype **/
168        {
169            /**
170            * Initially sets data to model and watches events on which
171            * view should be updated
172            * @listens module:facetResultsFilter.FacetResultsFilterModel~event:change
173            * @memberof module:facetResultsFilter.FacetResultsFilterView
174            * @alias module:facetResultsFilter.FacetResultsFilterView#initialize
175            */
176            initialize: function () {
177                var dataProperties = this.$el.data(),
178                    hash = queryModel.parseHashParameters(window.location.hash),
179                    properties = dataProperties.properties,
180                    signatures,
181                    i;
182
183
184                if (dataProperties.properties.searchResultsSignature === null) {
185                    dataProperties.properties.searchResultsSignature = "";
186                }
187
188                signatures = this.translateSignatures(properties.searchResultsSignature, properties.f);
189
190                this.model.set({ dataProperties: properties });
191                this.model.set("sig", signatures);
192
193                for (i = 0; i < signatures.length; i++) {
194                    if (!jQuery.isEmptyObject(_.pick(hash, signatures[i]))) {
195                        var values = _.values(_.pick(hash, signatures[i]))[0];
196                        this.model.updateFacetArray(values)
197                    }
198                }
199
200                this.model.on("change", this.render, this);
201            },
202            /**
203             * list of events for Backbone View
204            * @memberof module:facetResultsFilter.FacetResultsFilterView
205            * @alias module:facetResultsFilter.FacetResultsFilterView#events
206             */
207            events: {
208                'click .facet-value': 'updateFacet',
209                'click .filterButton': 'updateFacet',
210                'click .clear-filter': 'removeFacet',
211                'click .bottom-remove-filter > button': 'removeFacet',
212                'click .toogle-facet-visibility': 'toogleFacetVisibility'
213            },
214
215            toogleFacetVisibility: function () {
216                var showFacets = this.model.get('showAllFacets');
217                this.model.set('showAllFacets', !showFacets);
218
219
220            },
221            /**
222            * Updates model 'facetArray' based on params
223            * @param {Event}  param Event object with current target
224            * @memberof module:facetResultsFilter.FacetResultsFilterView
225            * @alias module:facetResultsFilter.FacetResultsFilterView#updateFacet
226            */
227            updateFacet: function (param) {
228                var currentFacet = $(param.currentTarget),
229                    facetArray = this.model.get('facetArray'),
230                    properties = this.model.get('dataProperties'),
231                    facetClose = this.$el.find('.facet-heading > span'),
232                    facetGroup = currentFacet.parents('.component-content').find('.facet-search-filter'),
233                    facetName = properties.f.toLowerCase(),
234                    facetDataValue = currentFacet.data('facetvalue'),
235                    facetValue = typeof facetDataValue !== "undefined" ? decodeURIComponent(facetDataValue) : facetDataValue,
236                    sig = this.model.get('sig'),
237                    index,
238                    hash = {},
239                    i;
240
241                if (properties.multi) {
242                    if (facetValue) {
243                        if (currentFacet.is(':not(.active-facet)')) {
244                            this.setActiveFacet(facetName, facetValue);
245                            facetArray.push(facetValue);
246                        } else {
247                            currentFacet.removeClass('active-facet');
248
249                            currentFacet.find('[type=checkbox]').prop('checked', false);
250                            currentFacet.find('[type=checkbox] + label:after').css({ 'background': '#fff' });
251
252                            index = facetArray.indexOf(facetValue);
253                            if (index > -1) {
254                                facetArray.splice(index, 1);
255                            }
256
257                            if (facetArray.length == 0) {
258                                facetClose.removeClass('has-active-facet');
259                            }
260                        }
261                        this.model.set({ facetArray: facetArray });
262                    }
263
264                    //is there any better way to check what action start method?
265                    if (currentFacet[0].type == "button") {
266                        for (i = 0; i < sig.length; i++) {
267                            hash[sig[i]] = _.uniq(facetArray, function (item) {
268                                return JSON.stringify(item);
269                            }).join("||");
270                        }
271                        queryModel.updateHash(hash);
272                    }
273                } else {
274                    if (facetValue) {
275                        for (i = 0; i < sig.length; i++) {
276                            hash[sig[i]] = facetValue;
277                        }
278                        facetGroup.data('active-facet', hash);
279                        this.setActiveFacet(facetName, facetValue);
280                        queryModel.updateHash(hash);
281                    }
282                }
283
284            },
285            /**
286            * Sets default values for Search Result Filter and updates hash accordingly
287            * @param {Event} evt Event object
288            * @memberof module:facetResultsFilter.FacetResultsFilterView
289            * @alias module:facetResultsFilter.FacetResultsFilterView#removeFacet
290            */
291            removeFacet: function (evt) {
292                evt.preventDefault();
293
294                var facets = this.$el,
295                    facetClose = facets.find('.facet-heading > span'),
296                    facetValues = facets.find('.facet-value'),
297                    sig = this.model.get('sig');
298
299                queryModel.updateHash(this.updateSignaturesHash(sig, "", {}));
300
301                facetClose.removeClass('has-active-facet');
302
303                _.each(facetValues, function (single) {
304                    var $single = $(single);
305                    if ($single.hasClass('active-facet')) {
306                        $single.removeClass('active-facet');
307                        $single.find('[type=checkbox]').prop('checked', false);
308                        $single.find('[type=checkbox] + label:after').css({ 'background': '#fff' });
309                    }
310                });
311
312                this.model.set({ facetArray: [] });
313            },
314            /**
315            * Renders view
316            * @memberof module:facetResultsFilter.FacetResultsFilterView
317            * @alias module:facetResultsFilter.FacetResultsFilterView#render
318            */
319            render: function () {
320                var inst = this,
321                    resultData = this.model.get("resultData"),
322                    facetClose = this.$el.find('.facet-heading > span'),
323                    facetNames = this.model.get('dataProperties').f.split('|'),
324                    emptyValueText = this.model.get('dataProperties').emptyValueText,
325                    highlightThreshold = this.model.get('dataProperties').highlightThreshold,
326                    showLessText = this.model.get('dataProperties').showLessText,
327                    showMoreText = this.model.get('dataProperties').showMoreText,
328                    highlightBehaviour = parseInt(this.model.get('dataProperties').highlightBehaviour),
329                    showAllFacets = this.model.get('showAllFacets'),
330                    hash = queryModel.parseHashParameters(window.location.hash),
331                    sig = this.model.get('sig'),
332                    template, facetName, templateResult;
333
334                //checks if page is opened from disc - if yes then we are in Creative Exchange mode
335                if (window.location.href.startsWith("file://")) {
336                    return;
337                }
338                this.manageVisibilityByData(this.$el, resultData)
339                if (resultData !== undefined) {
340                    if (inst.model.get('dataProperties').multi === true) {
341                        template = _.template(inst.model.get("templateMulti"));
342                    } else {
343                        template = _.template(inst.model.get("template"));
344                    }
345                    templateResult = template({
346                        facet: resultData, emptyText: emptyValueText, showLessText: showLessText,
347                        showMoreText: showMoreText, highlightBehaviour: highlightBehaviour, showAllFacets: showAllFacets,
348                        resultsCount: resultData.Values ? resultData.Values.length : -1
349                    });
350                }
351
352                inst.$el.find(".contentContainer").html(templateResult);
353
354                //checks url hash for facets and runs setActiveFacet method for each facet filter
355                _.each(facetNames, function (val) {
356                    facetName = val.toLowerCase();
357                    for (var i = 0; i < sig.length; i++) {
358                        if (!jQuery.isEmptyObject(_.pick(hash, sig))) {
359                            var values = _.values(_.pick(hash, sig))[0];
360                            if (values) {
361                                inst.setActiveFacet(facetName, values);
362
363                                //If this rendering is supporting multiple signatures, we will mark active facet once.
364                                return;
365                            }
366                        }
367                    }
368                });
369
370                //highlights facets count greater than chosen threshold
371                if (highlightThreshold) {
372                    this.handleThreshold(highlightThreshold);
373                }
374
375                //if no facet is selected, remove previously highlighted cross icon (while back button)
376                if (this.model.get("facetArray").length === 0) {
377                    facetClose.removeClass('has-active-facet');
378                } else {
379                    facetClose.addClass('has-active-facet');
380                }
381            },
382            /**
383            * Manages search result filter active state
384            * @param {String} facetGroupName facet name
385            * @param {String} facetValueName facet value
386            * @memberof module:facetResultsFilter.FacetResultsFilterView
387            * @alias module:facetResultsFilter.FacetResultsFilterView#setActiveFacet
388            */
389            setActiveFacet: function (facetGroupName, facetValueName) {
390                var properties = this.model.get('dataProperties'),
391                    facetChildren = this.$el.find('p[data-facetvalue]'),
392                    facetClose = this.$el.find('.facet-heading > span'),
393                    inst = this,
394                    facetValue,
395                    values;
396
397                facetValueName = facetValueName.toString().toLowerCase();
398                facetValue = this.$el.find("[data-facetvalue]").filter(function () {
399                    return decodeURIComponent($(this).attr("data-facetvalue").toLowerCase()) === facetValueName;
400                });
401
402
403                if (typeof (facetValueName) !== "undefined" && facetValueName !== null) {
404                    values = facetValueName.split("||");
405                } else {
406                    return;
407                }
408
409                if (values.length > 1) {
410                    properties.multi = true;
411                }
412
413                if (properties.multi) {
414                    //multi selection facet search results
415                    _.each(facetChildren, function (val) {
416
417                        if (values.length > 1) {
418                            for (var i = 0, l = values.length; i < l; i++) {
419                                facetValue = inst.$el.find("[data-facetvalue]").filter(function () {
420                                    return decodeURIComponent($(this).attr('data-facetvalue').toLowerCase()) === values[i];
421                                });
422                                if (val === facetValue[0]) {
423                                    $(val).addClass('active-facet');
424                                    $(val).find('[type=checkbox]').prop('checked', true);
425                                }
426                            }
427                        }
428
429                        if (val === facetValue[0]) {
430                            $(val).addClass('active-facet');
431                            $(val).find('[type=checkbox]').prop('checked', true);
432                        }
433
434
435                    });
436                } else {
437                    //single selection facet search results filter allows only one facet type to be selected
438                    _.each(facetChildren, function (val) {
439                        if (val !== facetValue[0]) {
440                            $(val).removeClass('active-facet');
441                            $(val).find('[type=checkbox]').prop('checked', false);
442                            $(val).find('[type=checkbox] + label:after').css({ 'background': '#fff' });
443                        } else {
444                            $(val).addClass('active-facet');
445                        }
446                    });
447                }
448
449                //adds active class to group close button
450                facetClose.addClass('has-active-facet');
451            },
452            /**
453            * Manages search result filter highlight of threshold
454            * @param {Number} highlightThreshold value of threshold highlight
455            * @memberof module:facetResultsFilter.FacetResultsFilterView
456            * @alias module:facetResultsFilter.FacetResultsFilterView#handleThreshold
457            */
458            handleThreshold: function (highlightThreshold) {
459                var facets = this.$el.find('.facet-search-filter').children('p');
460
461                _.each(facets, function (single) {
462                    var $facet = $(single),
463                        $facetCount = $facet.find('.facet-count'),
464                        facetCount = $facetCount.data('facetcount');
465
466                    if (facetCount > highlightThreshold) {
467                        $facetCount.addClass('highlighted');
468                    }
469                });
470            }
471        });
472    /**
473    * For each search result component on a page creates instance of
474    * ["FacetResultsFilterModel"]{@link module:facetResultsFilter.FacetResultsFilterModel} and
475    * ["FacetResultsFilterView"]{@link module:facetResultsFilter.FacetResultsFilterView}
476    * @memberOf module:facetResultsFilter
477    * @alias module:facetResultsFilter.init
478    */
479    api.init = function () {
480        if ($("body").hasClass("on-page-editor")) {
481            return;
482        }
483
484        queryModel = XA.component.search.query;
485        apiModel = XA.component.search.ajax;
486        urlHelperModel = XA.component.search.url;
487
488        var facetResultsFilterList = $(".facet-single-selection-list:not(.initialized)");
489
490        _.each(facetResultsFilterList, function (elem) {
491            var $el = $(elem),
492                model = new FacetResultsFilterModel(),
493                view = new FacetResultsFilterView({ el: $el, model: model });
494        
495            $el.addClass("initialized");
496        });
497    };
498    /**
499    * Returns information about facet component
500    * @memberOf module:facetResultsFilter
501    * @alias module:facetResultsFilter.getFacetDataRequestInfo
502    * @returns {Array<FacetDataRequestInfo>} facet data needed for request
503    */
504    api.getFacetDataRequestInfo = function () {
505        var facetList = $(".facet-single-selection-list"),
506            result = [];
507
508        _.each(facetList, function (elem) {
509            var properties = $(elem).data().properties,
510                signatures = properties.searchResultsSignature.split(','),
511                i;
512
513            for (i = 0; i < signatures.length; i++) {
514                result.push({
515                    signature: signatures[i] === null ? "" : signatures[i],
516                    facetName: properties.f,
517                    endpoint: properties.endpoint,
518                    showMoreText: properties.showMoreText,
519                    showLessText: properties.showLessText,
520                    highlightBehaviour: properties.highlightBehaviour,
521                    s: properties.s,
522                    filterWithoutMe: !properties.collapseOnSelection
523                });
524            }
525        });
526
527        return result;
528    };
529
530    return api;
531
532}(jQuery, document));
533
534XA.register('facetResultsFilter', XA.component.search.facet.resultsfilter);

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.