PageSourceSearch

https://www.stlouisfed.org/-/media/base-themes/searchtheme/scripts/component-search-results.js

js stlouisfed.org collected 2026-09-24 06:39:43 UTC 16,440 bytes, 362 lines download raw bytes

1/**
2 * Search result component functionality
3 * @module searchResults
4 * @param  {jQuery} $ Instance of jQuery
5 * @param  {Document} document dom document object
6 * @return {Object} list of methods for working with component search result
7*/
8XA.component.search.results = (function ($, document) {
9
10    "use strict";
11    /**
12    * This object stores all public api methods
13    * @type {Object.<Methods>}
14    * @memberOf module:searchResults
15    */
16    var api = {},
17        searchResultViews = [],
18        searchResultModels = [],
19        urlHelperModel,
20        queryModel,
21        apiModel
22    /**
23    * @name module:searchResults.SearchResultModel
24    * @constructor
25    * @augments Backbone.Model
26    */
27    var SearchResultModel = Backbone.Model.extend(
28        /** @lends module:searchResults.SearchResultModel.prototype **/
29        {
30            defaults: {
31                template: "<% if(!results.length){ %><div class='no-results'><%= noResultsText %></div> <% }else { %>" +
32                    "<ul class='search-result-list'> " +
33                    "<% _.forEach(results, function(result){ %>" +
34                    "<li " + "<% if(result.Geospatial){%>data-id='<%= result.Id %>' data-longitude='<%= result.Geospatial.Longitude %>' data-latitude='<%= result.Geospatial.Latitude %>'<% } %>" + "><%= result.Html %></li>" +
35                    "<% }); %>" +
36                    "</ul>" +
37                    "<% } %>" +
38                    "<div class='search-result-overlay'>",
39                templateItems: "<% _.forEach(results, function(result){ %>" +
40                    "<li " + "<% if(result.Geospatial){%>data-id='<%= result.Id %>' data-longitude='<%= result.Geospatial.Longitude %>' data-latitude='<%= result.Geospatial.Latitude %>'<% } %>" + "><%= result.Html %></li>" +
41                    "<% }); %>",
42                dataProperties: {},
43                blockNextRequest: false,
44                noResultsText: "",
45                resultData: {},
46                loadingInProgress: false,
47                loadingMoreInProgress: false,
48                resultDataMore: {},
49                loadMoreOffset: 0,
50                loadMore: false
51            },
52            /**
53             * Listens for changes on data from server
54            * @listens module:XA.component.search.vent~event:facet-data-loaded
55            * @listens module:XA.component.search.vent~event:results-loading
56            */
57            initialize: function () {
58                var hashObj = queryModel.parseHashParameters(window.location.hash),
59                    signature = encodeURIComponent(this.get("dataProperties").sig),
60                    offsetKey = "e_" + signature;
61
62                //If there is "e" in hash object, then one of page selectors on the page is global (not assigned to any of the
63                //search results - in such case it will paginate all search results without signature
64                if (hashObj.hasOwnProperty("e") && signature === '') {
65                    this.set("loadMoreOffset", parseInt(hashObj.e));
66                }
67
68                //checks if there is page selector assigned to this specific search results
69                if (hashObj.hasOwnProperty(offsetKey)) {
70                    this.set("loadMoreOffset", parseInt(hashObj[offsetKey]));
71                }
72
73                XA.component.search.vent.on('new-search', this.resetModel.bind(this));
74                XA.component.search.vent.on("results-loaded", this.resultsLoaded.bind(this));
75                XA.component.search.vent.on('results-loading', this.resultsLoading.bind(this));                
76            },
77            /**
78             * Sets value of blockNextRequest variable
79             * @param {String} value new value of blockNextRequest variable
80             * @memberof module:searchResults.SearchResultModel
81             * @alias module:searchResults.SearchResultModel#blockRequests
82             */
83            blockRequests: function (value) {
84                this.set("blockNextRequest", value);
85            },
86            /**
87            * Returns value of blockNextRequest variable
88            * @memberof module:searchResults.SearchResultModel
89            * @alias module:searchResults.SearchResultModel#checkBlockingRequest
90            * @returns {String} value of blockNextRequest variable
91            */
92            checkBlockingRequest: function () {
93                return this.get("blockNextRequest");
94            },
95            /**
96            * Returns value of component offset from hash parameters
97            * @memberof module:searchResults.SearchResultModel
98            * @alias module:searchResults.SearchResultModel#getMyOffset
99            * @returns {String|0} value of offset
100            */
101            getMyOffset: function () {
102                var hash = queryModel.parseHashParameters(window.location.hash),
103                    signature = encodeURIComponent(this.get("dataProperties").sig);
104                if (hash.hasOwnProperty("e_" + signature)) {
105                    return hash["e_" + signature];
106                }
107                return 0;
108            },
109            /**
110            * Updates model with new data from resultData parameter
111            * @param {Object} resultsData new component data
112            * @memberof module:searchResults.SearchResultModel
113            * @alias module:searchResults.SearchResultModel#resultsLoaded
114            */
115            resultsLoaded: function (resultsData) {
116                var signature = encodeURIComponent(this.get("dataProperties").sig);
117
118                if (signature === resultsData.searchResultsSignature) {
119                    if (this.get("loadMore")) {
120                        this.set({ resultDataMore: resultsData });
121                        this.set({ loadingMoreInProgress: false });
122                        this.unset("loadMore", { silent: true });
123                    } else {
124                        this.set({ resultData: resultsData });
125                        this.set({ loadingInProgress: false });
126                        this.unset("resultDataMore", { silent: true });
127                    }
128                    this.blockRequests(false);
129                }
130            },
131            /**
132            * Manages loadingMoreInProgress model variable
133            * @param {Object} cid component id
134            * @memberof module:searchResults.SearchResultModel
135            * @alias module:searchResults.SearchResultModel#resultsLoading
136            */
137            resultsLoading: function (cid) {
138                if (this.cid == cid) {
139                    if (this.get("loadMore")) {
140                        this.set({ loadingMoreInProgress: true });
141                    } else {
142                        this.set({ loadingInProgress: true });
143                    }
144                }
145            },
146            /**
147            * Resets search results model
148            * @memberof module:searchResults.SearchResultModel
149            * @alias module:searchResults.SearchResultModel#resultsLoading
150            */            
151            resetModel: function() {
152                this.set({ loadMoreOffset: 0 });
153                this.unset("resultDataMore", { silent: true });
154            }
155        });
156    /**
157    * @name module:searchResults.SearchResultView
158    * @constructor
159    * @augments Backbone.View
160    */
161    var SearchResultView = Backbone.View.extend(
162        /** @lends module:searchResults.SearchResultView.prototype **/
163        {
164            /**
165            * Initially sets data to model and watches events on which
166            * view should be updated
167            * @listens module:searchResults.SearchResultModel~event:change:loadingInProgress
168            * @listens module:searchResults.SearchResultModel~event:change:loadingMoreInProgress
169            * @listens module:searchResults.SearchResultModel~event:change:resultData
170            * @listens module:searchResults.SearchResultModel~event:change:resultDataMore
171            * @listens module:XA.component.search.vent~event:add-variant-class
172            * @listens module:XA.component.search.vent~event:loadMore
173            * @listens module:XA.component.search.vent~event:my-location-coordinates-changed
174            * @memberof module:searchResults.SearchResultView
175            * @alias module:searchResults.SearchResultView#initialize
176            */
177            initialize: function () {
178                var dataProperties = this.$el.data(),
179                    noResultsText = this.$el.find(".no-results").html(),
180                    searchRouter = XA.component.search.router,
181                    searchRouterInstance = searchRouter ? searchRouter.routerInstance : undefined,
182                    maxHeight = 0,
183                    inst = this;
184
185                if (dataProperties.properties.sig === null) {
186                    dataProperties.properties.sig = "";
187                }
188
189                if (this.model) {
190                    this.model.set({ dataProperties: dataProperties.properties, noResultsText: noResultsText });
191                }
192
193                this.model.on("change:loadingInProgress", this.loading, this);
194                this.model.on("change:loadingMoreInProgress", this.loadingMore, this);
195                this.model.on("change:resultData", this.render, this);
196                this.model.on("change:resultDataMore", this.renderPart, this);
197
198                this.$el.on("remove", function () {
199                    searchResultModels = searchResultModels.filter(function (m) {
200                        return m.cid !== inst.model.cid
201                    })
202                })
203
204                XA.component.search.vent.on("add-variant-class", function (data) {
205                    var signature = inst.model.get("dataProperties").sig;
206                    if (data.sig === signature) {
207                        inst.$el.removeClass(inst.$el.attr("data-class-variant"));
208                        inst.$el.attr("data-class-variant", data.classes);
209                        inst.$el.addClass(data.classes);
210                    }
211                });
212
213                XA.component.search.vent.on("loadMore", function (data) {
214                    var signature = inst.model.get("dataProperties").sig;
215                    if (data.sig === signature) {
216                        XA.component.search.service.getData({
217                            loadMore: "true",
218                            p: inst.model.get("dataProperties").p,
219                            singleRequestMode: signature
220                        });
221                    }
222                });
223
224                XA.component.search.vent.on("my-location-coordinates-changed", function (data) {
225                    if (data.sig === inst.model.get("dataProperties").sig && inst.model.get("loadMore")) {
226                        inst.$el.find(".search-result-list").html("");
227                    }
228                });
229
230                if (searchRouterInstance && dataProperties.properties.autoFireSearch && this.$el.closest('.overlay-inner').length) {
231                    searchRouterInstance.checkUrl(window.location.hash, true)
232                }
233
234                this.render();
235            },
236            /**
237            * list of events for Backbone View
238            * @memberof module:searchResults.SearchResultView
239            * @alias module:searchResults.SearchResultView#events
240            */
241            events: {
242                'click .search-result-list > li[data-longitude][data-latitude]': "poiClick"
243            },
244            /**
245             * Manage 'loading-in-progress' css class
246            * @memberof module:searchResults.SearchResultView
247            * @alias module:searchResults.SearchResultView#loading
248            */
249            loading: function () {
250                if (this.model.get("loadingInProgress")) {
251                    this.$el.addClass("loading-in-progress");
252                } else {
253                    this.$el.removeClass("loading-in-progress");
254                }
255            },
256            /**
257             * Manage 'loading-more-in-progress' css class
258            * @memberof module:searchResults.SearchResultView
259            * @alias module:searchResults.SearchResultView#loadingMore
260            */
261            loadingMore: function () {
262                if (this.model.get("loadingMoreInProgress")) {
263                    this.$el.addClass("loading-more-in-progress");
264                } else {
265                    this.$el.removeClass("loading-more-in-progress");
266                }
267            },
268            /**
269            * Renders search result list
270            * @memberof module:searchResults.SearchResultView
271            * @alias module:searchResults.SearchResultView#renderPart
272            */
273            renderPart: function () {
274                var template = _.template(this.model.get("templateItems"));
275                var templateResult = template({ results: this.model.get("resultDataMore").data });
276                this.$el.find(".search-result-list").append(templateResult);
277            },
278            /**
279             * Renders view
280             * @memberof module:searchResults.SearchResultView
281             * @alias module:searchResults.SearchResultView#render
282             */
283            render: function () {
284                var inst = this,
285                    maxHeight = 0,
286                    results = inst.model.get("resultData").data;
287
288                //checks if page is opened from disc - if yes then we are in Creative Exchange mode
289                if (window.location.href.startsWith("file://")) {
290                    return;
291                }
292
293                if (typeof results === "undefined") {
294                    results = [];
295                }
296
297                var template = _.template(inst.model.get("template"));
298                var templateResult = template({ results: results, noResultsText: inst.model.get("noResultsText") });
299                this.$el.html(templateResult);
300            },
301            /**
302             * Renders view
303             * @param {Event} e Event object that contains target poi elment
304             * @fires XA.component.search.vent#center-map
305             * @memberof module:searchResults.SearchResultView
306             * @alias module:searchResults.SearchResultView#poiClick
307             */
308            poiClick: function (e) {
309                var li = $(e.currentTarget);
310
311                XA.component.search.vent.trigger("center-map", {
312                    sig: this.model.get("dataProperties").sig,
313                    coordinates: [li.data('latitude'), li.data('longitude')],
314                    id: li.data('id')
315                });
316            }
317        });
318    /**
319    * For each search result component on a page creates instance of 
320    * ["SearchResultModel"]{@link module:searchResults.SearchResultModel} and 
321    * ["SearchResultView"]{@link module:searchResults.SearchResultView} 
322    * @memberOf module:searchResults
323    * @alias module:searchResults.init
324    */
325    api.init = function () {
326        if ($("body").hasClass("on-page-editor")) {
327            return;
328        }
329
330        urlHelperModel = XA.component.search.url;
331        queryModel = XA.component.search.query;
332        apiModel = XA.component.search.ajax;
333
334        var searchResults = $(".search-results:not(.initialized)");
335
336        _.each(searchResults, function (elem) {
337            var $el = $(elem),
338                resultsModel = new SearchResultModel();
339
340            searchResultModels.push(resultsModel);
341            searchResultViews.push(new SearchResultView({ el: $el, model: resultsModel }));
342
343            $el.addClass("initialized");
344        });
345    };
346    /**
347       *Extends API methods with searchResultModels
348       * @alias module:searchResults.searchResultViews
349       */
350    api.searchResultViews = searchResultViews;
351    /**
352   * Extends API methods with searchResultModels
353   * @memberOf module:searchResults
354   * @alias module:searchResults.searchResultModels
355   */
356    api.searchResultModels = searchResultModels;
357
358    return api;
359
360}(jQuery, document));
361
362XA.register('searchResults', XA.component.search.results);

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.