1/** 2 * Location filter component functionality 3 * @module locationfilter 4 * @param {jQuery} $ Instance of jQuery 5 * @param {Document} document dom document object 6 * @return {Object} list of methods for working with location filter 7*/ 8XA.component.search.locationfilter = (function ($, document) { 9 10 "use strict"; 11 /** 12 * This object stores all public api methods 13 * @type {Object.<Methods>} 14 * @memberOf module:locationfilter 15 */ 16 var api = {}, 17 locationFilters = [], 18 views = [], 19 queryModel, 20 urlHelperModel, 21 initialized = false, 22 scriptsLoaded = false, 23 mapsConnector = XA.connector.mapsConnector, 24 LocationFilterModel, 25 LocationFilterView, 26 initialize; 27 /** 28 * @memberOf module:locationfilter 29 * @alias module:locationfilter.initialize 30 * For each location filter component on a page creates instance of 31 * ["LocationFilterModel"]{@link module:locationfilter.LocationFilterModel} and 32 * ["LocationFilterView"]{@link module:locationfilter.LocationFilterView} 33 * @private 34 */ 35 initialize = function() { 36 var i, view; 37 for (i = 0; i < locationFilters.length; i++) { 38 //creates Backbone.js views - one view per component on the page 39 view = new LocationFilterView({el: locationFilters[i], model: new LocationFilterModel()}); 40 } 41 }; 42 /** 43 * @name module:locationfilter.LocationFilterModel 44 * @constructor 45 * @augments Backbone.Model 46 */ 47 LocationFilterModel = Backbone.Model.extend( 48 /** @lends module:locationfilter.LocationFilterModel.prototype **/ 49 { 50 /** 51 * Default model options 52 * @default 53 */ 54 defaults: { 55 dataProperties: {}, 56 sig: [] 57 }, 58 /** 59 * Initializes ["Bloodhound"]{@link https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md} 60 * suggestion engine with dataProperties data 61 * @memberof module:locationfilter.LocationFilterModel 62 * @alias module:locationfilter.LocationFilterModel#initAutocompleteEngine 63 */ 64 initAutocompleteEngine : function() { 65 var _this = this, 66 searchEngine; 67 68 //initializes predictive only when number of predictions is not zero 69 if(_this.get("dataProperties").p > 0){ 70 searchEngine = new Bloodhound({ 71 datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name"), 72 queryTokenizer: Bloodhound.tokenizers.whitespace, 73 limit : _this.get("dataProperties").p, 74 remote : { 75 url : "-", 76 replace : function(){ 77 return Date.now().toString(); 78 }, 79 transport : function(options, onSuccess, onError){ 80 var queryParams = _this.get("queryParams"); 81 if(!queryParams.text){ 82 onSuccess([]); 83 return; 84 } 85 mapsConnector.locationAutocomplete(queryParams, 86 function (results) { //success 87 var simplifiedResults = results.map(function(result) { 88 if (result.hasOwnProperty("text")) { 89 return result.text; 90 } 91 return result; 92 }); 93 94 onSuccess(simplifiedResults); 95 }, 96 function(){ //fail 97 onError("Could not autocomplete"); 98 }); 99 } 100 } 101 }); 102 searchEngine.initialize(); 103 this.set({"searchEngine" : searchEngine}); 104 } 105 } 106 });
107 /** 108 * @name module:locationfilter.LocationFilterView 109 * @constructor 110 * @augments Backbone.View 111 */ 112 LocationFilterView = XA.component.search.baseView.extend( 113 /** @lends module:locationfilter.LocationFilterView.prototype **/ 114 { 115 /** 116 * Initially sets data to model, initializes typeahead.js, and watches events on which 117 * view should be updated 118 * @memberof module:locationfilter.LocationFilterView 119 */ 120 initialize: function () { 121 var inst = this, 122 dataProperties = this.$el.data(), 123 $textBox = this.$el.find(".location-search-box-input"), 124 signatures = dataProperties.properties.searchResultsSignature.split(','); 125 126 if (dataProperties.properties.searchResultsSignature === null) { 127 dataProperties.properties.searchResultsSignature = ""; 128 } 129 130 this.model.set({ dataProperties: dataProperties.properties }); 131 this.model.set({ sig: signatures}); 132 this.model.set({ queryParams: { maxResults: dataProperties.p, text: "" } }); 133 this.model.initAutocompleteEngine(); 134 135 var autocompleteEngine = this.model.get("searchEngine"); 136 137 if(autocompleteEngine){ 138 $textBox.typeahead({ 139 hint : true, 140 minLength : 2 141 }, 142 { 143 source : autocompleteEngine.ttAdapter(), 144 templates : { 145 suggestion : function(data) { 146 return '<div class="suggestion-item">' + data + '</div>'; 147 } 148 } 149 }).on("typeahead:selected",function(args, selected){ 150 inst.translateUserLocation(selected); 151 $textBox.typeahead("val", selected); 152 }); 153 } 154 155 this.addressLookup(true); 156 157 XA.component.search.vent.on("hashChanged", function(hash) { 158 var address = hash[signatures.length > 0 && signatures[0] !== "" ? signatures[0] + "_a" : "a"]; 159 if (typeof address !== "undefined" && address !== null && address !== "") { 160 $textBox.val(address); 161 } 162 }); 163 }, 164 /** 165 * list of events for Backbone View 166 * @memberof module:locationfilter.LocationFilterView 167 * @alias module:locationfilter.LocationFilterView#events 168 */ 169 events: { 170 "click .location-search-box-button": "addressLookup", 171 "keypress .location-search-box-input": "searchTextChanges", 172 "keyup .location-search-box-input" : "autocomplete" 173 }, 174 /** 175 * Looks for an address based on a value that is specified by user 176 * in input 177 * @memberof module:locationfilter.LocationFilterView 178 * @alias module:locationfilter.LocationFilterView#addressLookup 179 */ 180 addressLookup: function(isInitialRender){ 181 var properties = this.model.get("dataProperties"), 182 lookupQuery = { 183 text: this.getAddress(isInitialRender), 184 maxResults: 1 185 }, 186 hashObj; 187 188 if (lookupQuery.text === "") { 189 hashObj = this.createHashObject("", "", ""); 190 this.updateHash(hashObj, properties); 191 } 192 193 switch (properties.mode) { 194 case "Location": { 195 //Uses browser to detect location 196 this.detectLocation(); 197 break; 198 } 199 case "UserProvided": { 200 //Takes address entered by user and tries to convert it to latitude and longitude 201 this.translateUserLocation(lookupQuery); 202 break; 203 } 204 case "Mixed": { 205 //Uses user address or tries to detect location by browser 206 if (typeof(lookupQuery.text) === "undefined" || lookupQuery.text === "") { 207 this.detectLocation(); 208 } else { 209 this.translateUserLocation(lookupQuery); 210 } 211 break; 212 } 213 } 214 }, 215 /** 216 * Get Address from input or from hash 217 * @memberof module:locationfilter.LocationFilterView 218 * @alias module:locationfilter.LocationFilterView#getAddress 219 * @returns {String} address from input or hash 220 */ 221 getAddress: function (isInitialRender) { 222 var $textBox = this.$el.find(".location-search-box-input.tt-input"), 223 text = $textBox.length !== 0 ? $textBox.val() : this.$el.find(".location-search-box-input").val(), 224 hash = queryModel.parseHashParameters(window.location.hash), 225 signatures = this.model.get("sig"), 226 address; 227 228 if (isInitialRender !== true && (text === "" || typeof text === "undefined")) { 229 return ""; 230 } 231 232 if (text === "" || typeof text === "undefined") { 233 address = hash[signatures.length > 0 && signatures[0] !== "" ? signatures[0] + "_a" : "a"]; 234 if (address !== "") { 235 text = address; 236 } 237 if (typeof text === "undefined") { 238 text = ""; 239 } 240 } 241 242 return text; 243 }, 244 /** 245 * Gets text selected from typeahead suggestion and sets to model 246 * @param {Event} args Event object 247 * @memberof module:locationfilter.LocationFilterView 248 * @alias module:locationfilter.LocationFilterView#autocomplete 249 */ 250 autocomplete : function(args){ 251 var $textBox, 252 queryParams, 253 properties = this.model.get("dataProperties"); 254 255 args.stopPropagation(); 256 if (args.keyCode === 13) { 257 return; 258 } 259 260 $textBox = this.$el.find(".location-search-box-input.tt-input"); 261 262 queryParams = { 263 text : $textBox.length !== 0 ? $textBox.val() : this.$el.find(".location-search-box-input").val(), 264 maxResults : properties.p 265 }; 266 this.model.set({queryParams : queryParams}); 267 }, 268 /** 269 * If enter was pressed, method calls ["addressLookup"]{@link module:locationfilter.addressLookup} 270 * @param {Event} e Event object 271 * @memberof module:locationfilter.LocationFilterView 272 * @alias module:locationfilter.LocationFilterView#searchTextChanges 273 * @returns {Boolean} False if pressed enter. All other cases: True 274 */ 275 searchTextChanges: function(e) { 276 e.stopPropagation(); 277 if (e.keyCode === 13) { 278 this.addressLookup(false); 279 return false; 280 } 281 return true; 282 }, 283 /** 284 * Looks for an address and calls ["XA.connector.mapsConnector.addressLookup"]{@link module:XA.connector.mapsConnector.addressLookup} 285 * @param {jQuery<DomElement>} lookupQuery Element where stored text for search location 286 * @memberof module:locationfilter.LocationFilterView 287 * @alias module:locationfilter.LocationFilterView#translateUserLocation 288 */ 289 translateUserLocation: function(lookupQuery) { 290 var that = this, 291 properties = this.model.get("dataProperties"), 292 text = typeof lookupQuery.text !== "undefined" ? lookupQuery.text : lookupQuery, 293 $textBox = this.$el.find(".location-search-box-input.tt-input"), 294 hashObj = {}; 295 296 if (text === "") { 297 return; 298 } 299 300 mapsConnector.addressLookup({ text: text }, function(data) { 301 hashObj = that.createHashObject(data[0] + "|" + data[1], properties.f + ",Ascending"); 302 that.updateHash(hashObj, properties); 303 }, function () {
304 console.error("Error while getting '" + text + "' location"); 305 }); 306 $textBox.blur(); 307 if ($textBox.val() === "") { 308 $textBox.val(text); 309 } 310 }, 311 /** 312 * Call ["locationService.addressLookup"]{@link module:locationService.detectLocation} 313 * @memberof module:locationfilter.LocationFilterView 314 * @alias module:locationfilter.LocationFilterView#detectLocation 315 */ 316 detectLocation: function () { 317 var properties = this.model.get("dataProperties"), 318 $textBox = this.$el.find(".location-search-box-input"), 319 sig = this.model.get("sig"), 320 hash = queryModel.parseHashParameters(window.location.hash), 321 param, 322 hashObj = {}, 323 that = this; 324 325 XA.component.locationService.detectLocation( 326 function (location) { 327 hashObj = that.createHashObject(location[0] + "|" + location[1], properties.f + ",Ascending"); 328 that.updateHash.call(that, hashObj, properties); 329 if ($textBox.length > 0) { 330 $textBox.attr("placeholder", properties.myLocationText); 331 } 332 }, 333 function (errorMessage) { 334 //Do not update the hash in any way when the location is not available 335 console.log(errorMessage); 336 } 337 ); 338 }, 339 /** 340 * Updates hash according to setup of component and selected values by user 341 * @memberof module:locationfilter.LocationFilterView 342 * @alias module:locationfilter.LocationFilterView#updateHash 343 */ 344 updateHash: function (params, properties) { 345 var sig = this.model.get("sig"), 346 signature, 347 searchModels = typeof XA.component.search !== "undefined" ? XA.component.search.results.searchResultModels : [], 348 $textBox = this.$el.find(".location-search-box-input.tt-input"), 349 text = $textBox.length !== 0 ? $textBox.val() : this.$el.find(".location-search-box-input").val(), 350 i, j; 351 352 //Clears load more offset in each of search results with the same signature when location is changed 353 //Now, this is needed to clear offset but should be handle in search service in the future 354 for (i = 0; i < searchModels.length; i++) { 355 for (j = 0; j < sig.length; j++) { 356 signature = sig[j]; 357 if (searchModels[i].get("dataProperties").sig === signature) { 358 searchModels[i].set("loadMoreOffset", 0); 359 } 360 } 361 } 362 363 if (text !== null && text !== "") { 364 for (i = 0; i < sig.length; i++) { 365 signature = sig[i]; 366 params[signature !== "" ? signature + "_a" : "a"] = text; 367 } 368 } 369 370 queryModel.updateHash(params, properties.targetUrl); 371 for (i = 0; i < sig.length; i++) { 372 XA.component.search.vent.trigger("my-location-coordinates-changed", { 373 sig: sig[i], 374 coordinates: params[sig[i] !== "" ? sig[i] + "_g" : "g"].split("|") 375 }); 376 } 377 }, 378 /** 379 * Updates hash according to setup of component and selected values by user 380 * @memberof module:locationfilter.LocationFilterView 381 * @alias module:locationfilter.LocationFilterView#updateHash 382 * @param {String} g location 383 * @param {String} o facet name and sort method 384 * @returns {Object} hash as an object 385 */ 386 createHashObject: function(g, o, a) { 387 var sig = this.model.get("sig"), 388 signature, 389 hashObj = {}, 390 i; 391 392 for (i = 0; i < sig.length; i++) { 393 signature = sig[i]; 394 hashObj[signature !== "" ? signature + "_g" : "g"] = g; 395 hashObj[signature !== "" ? signature + "_o" : "o"] = o; 396 if (typeof a !== "undefined") { 397 hashObj[signature !== "" ? signature + "_a" : "a"] = a; 398 } 399 } 400 401 return hashObj; 402 } 403 });
404 /** 405 * Fills locationFilters variable with location filter components, 406 * and loads script for proper map provider 407 * @memberOf module:locationfilter 408 * @alias module:locationfilter.init 409 */ 410 api.init = function() { 411 if ($("body").hasClass("on-page-editor") || initialized) { 412 return; 413 } 414 415 queryModel = XA.component.search.query; 416 urlHelperModel = XA.component.search.url; 417 418 var components = $(".location-filter:not(.initialized)"); 419 _.each(components, function(elem) { 420 var $el = $(elem), 421 properties = $el.data("properties"); 422 423 //Collects all found components - we will use them later to create views 424 locationFilters.push($el); 425 426 //Loads google or bing scripts in order to properly use address lookup functionality but only 427 //when we are not in Location mode (in this mode we are taking location from the browser) 428 if (!scriptsLoaded && properties.mode !== "Location") { 429 mapsConnector.loadScript(properties.key, XA.component.search.locationfilter.scriptsLoaded); 430 } else { 431 initialize(); 432 } 433 434 $el.addClass("initialized"); 435 }); 436 initialized = true; 437 }; 438 /** 439 * Calls ['initialize']{@link module:locationfilter.initialize} if it wasn`t 440 * loaded previously and marked as loaded 441 * @memberOf module:locationfilter 442 * @alias module:locationfilter.scriptsLoaded 443 */ 444 api.scriptsLoaded = function() { 445 if (!scriptsLoaded) { 446 console.log("Maps api loaded"); 447 scriptsLoaded = true; 448 initialize(); 449 } 450 }; 451 452 return api; 453 454}(jQuery, document)); 455 456XA.register("locationfilter", XA.component.search.locationfilter);
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.