component-search-location-filter.js

/**
 * Location filter component functionality
 * @module locationfilter
 * @param  {jQuery} $ Instance of jQuery
 * @param  {Document} document dom document object
 * @return {Object} list of methods for working with location filter
*/
XA.component.search.locationfilter = (function ($, document) {

    "use strict";
  /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:locationfilter
    */
    var api = {},
        locationFilters = [],
        views = [],
        queryModel,
        urlHelperModel,
        initialized = false,
        scriptsLoaded = false,
        mapsConnector = XA.connector.mapsConnector,
        LocationFilterModel,
        LocationFilterView,
        initialize;
	/**
     * @memberOf module:locationfilter
     * @alias module:locationfilter.initialize
     * For each location filter component on a page creates instance of 
     * ["LocationFilterModel"]{@link module:locationfilter.LocationFilterModel} and 
     * ["LocationFilterView"]{@link module:locationfilter.LocationFilterView} 
     * @private
     */
    initialize = function() {
        var i, view;
        for (i = 0; i < locationFilters.length; i++) {
            //creates Backbone.js views - one view per component on the page
            view = new LocationFilterView({el: locationFilters[i], model: new LocationFilterModel()});
        }
    };
	/**
    * @name module:locationfilter.LocationFilterModel
    * @constructor
    * @augments Backbone.Model
    */
    LocationFilterModel = Backbone.Model.extend(
	        /** @lends module:locationfilter.LocationFilterModel.prototype **/
	{
		/**
        * Default model options
        * @default
        */
        defaults: {
            dataProperties: {},
            sig: []
        },
		/**
        * Initializes ["Bloodhound"]{@link https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md}
        * suggestion engine with dataProperties data
        * @memberof module:locationfilter.LocationFilterModel
        * @alias module:locationfilter.LocationFilterModel#initAutocompleteEngine
        */
        initAutocompleteEngine : function() {
            var _this = this,
                searchEngine;

            //initializes predictive only when number of predictions is not zero
            if(_this.get("dataProperties").p > 0){
                    searchEngine = new Bloodhound({
                        datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name"),
                        queryTokenizer: Bloodhound.tokenizers.whitespace,
                        limit :  _this.get("dataProperties").p,
                        remote : {
                            url : "-",
                            replace : function(){
                                return Date.now().toString();
                            },
                            transport : function(options, onSuccess, onError){
                                var queryParams =  _this.get("queryParams");
                                if(!queryParams.text){
                                    onSuccess([]);
                                    return;
                                }
                                mapsConnector.locationAutocomplete(queryParams,
                                    function (results) { //success
                                        var simplifiedResults = results.map(function(result) {
                                            if (result.hasOwnProperty("text")) {
                                                return result.text;
                                            }
                                            return result;
                                        });

                                        onSuccess(simplifiedResults);
                                    },
                                    function(){ //fail
                                        onError("Could not autocomplete");
                                    });
                            }
                        }
                });
                searchEngine.initialize();
                this.set({"searchEngine" : searchEngine});
            }
        }
    });
    /**
    * @name module:locationfilter.LocationFilterView
    * @constructor
    * @augments Backbone.View
    */
    LocationFilterView = XA.component.search.baseView.extend(
	    /** @lends module:locationfilter.LocationFilterView.prototype **/
	{
		/**
        * Initially sets data to model, initializes typeahead.js, and watches events on which
        * view should be updated
        * @memberof module:locationfilter.LocationFilterView
        */
        initialize: function () {
            var inst = this,
                dataProperties = this.$el.data(),
                $textBox = this.$el.find(".location-search-box-input"),
                signatures = dataProperties.properties.searchResultsSignature.split(',');

            if (dataProperties.properties.searchResultsSignature === null) {
                dataProperties.properties.searchResultsSignature = "";
            }

            this.model.set({ dataProperties: dataProperties.properties });
            this.model.set({ sig: signatures});
            this.model.set({ queryParams: { maxResults: dataProperties.p, text: "" } });
            this.model.initAutocompleteEngine();

            var autocompleteEngine = this.model.get("searchEngine");

            if(autocompleteEngine){
                $textBox.typeahead({
                    hint : true,
                    minLength : 2
                },
                {
                    source : autocompleteEngine.ttAdapter(),
                    templates : {
                        suggestion : function(data) {
                            return '<div class="suggestion-item">' + data + '</div>';
                        }
                    }
                }).on("typeahead:selected",function(args, selected){
                    inst.translateUserLocation(selected);
                    $textBox.typeahead("val", selected);
                });
            }

            this.addressLookup(true);

            XA.component.search.vent.on("hashChanged", function(hash) {
                var address = hash[signatures.length > 0 && signatures[0] !== "" ? signatures[0] + "_a" : "a"];
                if (typeof address !== "undefined" && address !== null && address !== "") {
                    $textBox.val(address);
                }
            });
        },
		/**
        * list of events for Backbone View
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#events
        */
        events: {
            "click .location-search-box-button": "addressLookup",
            "keypress .location-search-box-input": "searchTextChanges",
            "keyup .location-search-box-input" : "autocomplete"
        },
		/**
        * Looks for an address based on a value that is specified by user
        * in input
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#addressLookup
        */
        addressLookup: function(isInitialRender){
            var properties = this.model.get("dataProperties"),
                lookupQuery = {
                    text: this.getAddress(isInitialRender),
                    maxResults: 1
                },
                hashObj;

            if (lookupQuery.text === "") {
                hashObj = this.createHashObject("", "", "");
                this.updateHash(hashObj, properties);
            }

            switch (properties.mode) {
                case "Location": {
                    //Uses browser to detect location
                    this.detectLocation();
                    break;
                }
                case "UserProvided": {
                    //Takes address entered by user and tries to convert it to latitude and longitude
                    this.translateUserLocation(lookupQuery);
                    break;
                }
                case "Mixed": {
                    //Uses user address or tries to detect location by browser
                    if (typeof(lookupQuery.text) === "undefined" || lookupQuery.text === "") {
                        this.detectLocation();
                    } else {
                        this.translateUserLocation(lookupQuery);
                    }
                    break;
                }
            }
        },
		/**
        * Get Address from input or from hash 
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#getAddress
        * @returns {String} address from input or hash 
        */
        getAddress: function (isInitialRender) {
            var $textBox = this.$el.find(".location-search-box-input.tt-input"),
                text = $textBox.length !== 0 ? $textBox.val() : this.$el.find(".location-search-box-input").val(),
                hash = queryModel.parseHashParameters(window.location.hash),
                signatures = this.model.get("sig"),
                address;

            if (isInitialRender !== true && (text === "" || typeof text === "undefined")) {
                return "";
            }

            if (text === "" || typeof text === "undefined") {
                address = hash[signatures.length > 0 && signatures[0] !== "" ? signatures[0] + "_a" : "a"];
                if (address !== "") {
                    text = address;
                }
                if (typeof text === "undefined") {
                    text = "";
                }
            }

            return text;
        },
		/**
        * Gets text selected from typeahead suggestion and sets to model
        * @param {Event} args Event object
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#autocomplete
        */
        autocomplete : function(args){
            var $textBox,
                queryParams,
                properties = this.model.get("dataProperties");

            args.stopPropagation();
            if (args.keyCode === 13) {
                return;
            }

            $textBox = this.$el.find(".location-search-box-input.tt-input");

            queryParams = {
                text : $textBox.length !== 0 ? $textBox.val() : this.$el.find(".location-search-box-input").val(),
                maxResults : properties.p
            };
            this.model.set({queryParams : queryParams});
        },
		/**
        * If enter was pressed, method calls ["addressLookup"]{@link module:locationfilter.addressLookup}
        * @param {Event} e Event object
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#searchTextChanges
        * @returns {Boolean}  False if pressed enter. All other cases: True
        */
        searchTextChanges: function(e) {
            e.stopPropagation();
            if (e.keyCode === 13) {
                this.addressLookup(false);
                return false;
            }
            return true;
        },
		/**
        * Looks for an address and calls ["XA.connector.mapsConnector.addressLookup"]{@link module:XA.connector.mapsConnector.addressLookup}
        * @param {jQuery<DomElement>} lookupQuery Element where stored text for search location
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#translateUserLocation
        */
        translateUserLocation: function(lookupQuery) {
            var that = this,
                properties = this.model.get("dataProperties"),
                text = typeof lookupQuery.text !== "undefined" ? lookupQuery.text : lookupQuery,
                $textBox = this.$el.find(".location-search-box-input.tt-input"),
                hashObj = {};

            if (text === "") {
                return;
            }

            mapsConnector.addressLookup({ text: text }, function(data) {
                hashObj = that.createHashObject(data[0] + "|" + data[1], properties.f + ",Ascending");
                that.updateHash(hashObj, properties);
            }, function () {
                console.error("Error while getting '" + text + "' location");
            });
            $textBox.blur();
            if ($textBox.val() === "") {
                $textBox.val(text);
            }
        },
		/**
        * Call ["locationService.addressLookup"]{@link module:locationService.detectLocation}
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#detectLocation
        */
        detectLocation: function () {
            var properties = this.model.get("dataProperties"),
                $textBox = this.$el.find(".location-search-box-input"),
                sig = this.model.get("sig"),
                hash = queryModel.parseHashParameters(window.location.hash),
                param,
                hashObj = {},
                that = this;

            XA.component.locationService.detectLocation(
                function (location) {                    
                    hashObj = that.createHashObject(location[0] + "|" + location[1], properties.f + ",Ascending");
                    that.updateHash.call(that, hashObj, properties);
                    if ($textBox.length > 0) {
                        $textBox.attr("placeholder", properties.myLocationText);
                    }
                },
                function (errorMessage) {
                    //Do not update the hash in any way when the location is not available
                    console.log(errorMessage);
                }
            );
        },
		/**
        * Updates hash according to setup of component and selected values by user
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#updateHash
        */
        updateHash: function (params, properties) {
            var sig = this.model.get("sig"),
                signature,
                searchModels = typeof XA.component.search !== "undefined" ? XA.component.search.results.searchResultModels : [],
                $textBox = this.$el.find(".location-search-box-input.tt-input"),
                text = $textBox.length !== 0 ? $textBox.val() : this.$el.find(".location-search-box-input").val(),
                i, j;

            //Clears load more offset in each of search results with the same signature when location is changed
            //Now, this is needed to clear offset but should be handle in search service in the future
            for (i = 0; i < searchModels.length; i++) {
                for (j = 0; j < sig.length; j++) {
                    signature = sig[j];
                    if (searchModels[i].get("dataProperties").sig === signature) {
                        searchModels[i].set("loadMoreOffset", 0);
                    }
                }
            }

            if (text !== null && text !== "") {
                for (i = 0; i < sig.length; i++) {
                    signature = sig[i];
                    params[signature !== "" ? signature + "_a" : "a"] = text;
                }
            }

            queryModel.updateHash(params, properties.targetUrl);
            for (i = 0; i < sig.length; i++) {
                XA.component.search.vent.trigger("my-location-coordinates-changed", {
                    sig: sig[i],
                    coordinates: params[sig[i] !== "" ? sig[i] + "_g" : "g"].split("|")
                });
            }
        },
		/**
        * Updates hash according to setup of component and selected values by user
        * @memberof module:locationfilter.LocationFilterView
        * @alias module:locationfilter.LocationFilterView#updateHash
        * @param {String} g location
        * @param {String} o facet name and sort method
        * @returns {Object} hash as an object
        */
        createHashObject: function(g, o, a) {
            var sig = this.model.get("sig"),
                signature, 
                hashObj = {},
                i;

            for (i = 0; i < sig.length; i++) {
                signature = sig[i];
                hashObj[signature !== "" ? signature + "_g" : "g"] = g;
                hashObj[signature !== "" ? signature + "_o" : "o"] = o;
                if (typeof a !== "undefined") {
                    hashObj[signature !== "" ? signature + "_a" : "a"] = a;
                }
            }

            return hashObj;
        }
    });
   /**
    * Fills locationFilters variable with location filter components,
    * and loads script for proper map provider
    * @memberOf module:locationfilter
    * @alias module:locationfilter.init
    */
    api.init = function() {
        if ($("body").hasClass("on-page-editor") || initialized) {
            return;
        }

        queryModel = XA.component.search.query;
        urlHelperModel = XA.component.search.url;

        var components = $(".location-filter:not(.initialized)");
        _.each(components, function(elem) {
            var $el = $(elem),
                properties = $el.data("properties");

            //Collects all found components - we will use them later to create views
            locationFilters.push($el);

            //Loads google or bing scripts in order to properly use address lookup functionality but only
            //when we are not in Location mode (in this mode we are taking location from the browser)
            if (!scriptsLoaded && properties.mode !== "Location") {
                mapsConnector.loadScript(properties.key, XA.component.search.locationfilter.scriptsLoaded);
            } else {
                initialize();
            }

            $el.addClass("initialized");
        });
        initialized = true;
    };
	/**
     * Calls ['initialize']{@link module:locationfilter.initialize} if it wasn`t 
     * loaded previously and marked as loaded
     * @memberOf module:locationfilter
     * @alias module:locationfilter.scriptsLoaded
    */
    api.scriptsLoaded = function() {
        if (!scriptsLoaded) {
            console.log("Maps api loaded");
            scriptsLoaded = true;
            initialize();
        }
    };

    return api;

}(jQuery, document));

XA.register("locationfilter", XA.component.search.locationfilter);