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.