Source: connection.js

 /** 
 * @class A connection to Septima Search
 * @constructs Septima.Search.Connection
 * @param {(Object|string)} client An object exposing callback functions
 * @param client.onReady] {Septima.Search.Connection~onReady} Called when connection is ready
 * @param client.onSearcherResults] {Septima.Search.Connection~onSearcherResults} Called when a searcher has results 
 * @param client.onSearchResults] {Septima.Search.Connection~onSearchResults} Called when all results are ready. 
 * @param client.onSelect {Septima.Search.Connection~onSelect} Called when a result is selected. 
 * @param client.doTextSearch] {Septima.Search.Connection~doTextSearch} Called with new search string
 * @param client.log] {Septima.Search.Connection~log} Called when log is written to (if options.log > 0). 
 * @param client.onError] {Septima.Search.Connection~onError} Called when an error occurs
 * @param {object} connectionOptions
 * @param [connectionOptions.log=0] {int} Set to 1 to receive log messages
 * @param [connectionOptions.stringify=false] {boolean} Set to true to receive serialized objects
 * @param connectionOptions.mode {string} Must be "static" or "configUrl". If value is "configUrl" a "url" MUST be given
 * @param [connectionOptions.url] {string} Url to controller configuration
 * 
 */
    Septima.Search.Connection = Septima.Class (
        /** @lends Septima.Search.Connection# */
    {
      initialize: function (client, configOptions) {
        this.options = configOptions;
        
        if (typeof client === 'string'){
            this.client = window[client];
        }else{
        	this.client = client;
        }
        
        this.lastSearch = {
                handle: 0,
                results: [],
                searchers: [],
                clear: function(searchers){
                    this.handle++;
                    this.results = [];
                    this.searchers = [];
                    for (var i=0;i<searchers.length;i++){
                        var thisSearcher = searchers[i];
                        thisSearcher.simpleResults = [];
                        this.searchers.push(thisSearcher);
                    }
                },
                putResults: function (ssResults){
                    if (ssResults.length > 0){
                        var firstResult = ssResults[0];
                        if (firstResult.searcher){
                            var searcherId = firstResult.searcher.getId();
                            var thisSearcher = null;
                            for (var i=0;i<this.searchers.length;i++){
                                if (this.searchers[i].id === searcherId){
                                    thisSearcher = this.searchers[i];
                                }
                            }
                            if (thisSearcher !== null){
                                for (var j = 0; j < ssResults.length;j++ ){
                                    var result = ssResults[j];
                                    this.results.push(result);
                                    var simpleResult = {
                                        id: this.results.length - 1,
                                        title: result.title,
                                        description: result.description,
                                        image: result.image,
                                        searcher: searcherId
                                    };
                                    thisSearcher.simpleResults.push(simpleResult);
                                }
                                return searcherId;
                            }
                        }
                    }
                    return null;
                },
                getSearcherResults: function(searcherId){
                    for (var i=0;i<this.searchers.length;i++){
                        if (this.searchers[i].id === searcherId){
                            return this.searchers[i].simpleResults;
                        }
                    }
                },
                getResults: function(){
                    var results = [];
                    for (var i=0;i<this.searchers.length;i++){
                        results = results.concat(this.searchers[i].simpleResults);
                    }
                    return results;
                },
                getRealResult: function(simpleResultId){
                    var id = simpleResultId;
                    if (this.results.length > id){
                        return this.results[id];
                    }else{
                        return null;
                    }
                }
        };
        
        this.controller = null;
        this.ready = false;
        
        this.logLevel = 0;
        if (this.options.log){
            this.logLevel = this.options.log;
        }
        
        this.stringify = false;
        if (this.options.stringify){
            this.stringify = this.options.stringify;
        }
        
        this.viewFacade = {
			doTextSearch: Septima.bind(function(text){
				this._clientDoTextSearch(text);
			}, this),
			doDetail: function(result){}
		};
        
        this._createController(this.options).done(Septima.bind(function(controller){
            this.controller = controller;
            this.controller.addOnSelectHandler(Septima.bind(this._onControllerSelect, this));
            this.controller.addErrorHandler(Septima.bind(this._onControllerError, this));
            this.ready = true;
            this._clientOnReady();
        }, this));
      
      },
    
    //Interface:
      /**
       * @typedef {Object} Septima.Search.Connection.SearcherInfo
       * @property {string}
       * @property {int}
       */
      
      /**
       * Get list of searchers active on this connection.  Serialized with JSON.stringify if connectionOptions.stringify === true.
       * @returns {Septima.Search.Connection.SearcherInfo[]} 
       */
    getSearchers: function(){
        this._log ("Connection.getSearchers");
        if (this.ready){
        	try{
       	    	return this._getSearchers();
            } catch (e) {
    	        this._error ("Connection.getSearchers failed: " + e.message);
                return null;
            }
        }else{
            this._log ("Connection not ready");
        	return [];
        }
    },

    /**
     * Initiate a search.
     * @param {string} queryString
     * @param {int} limit
     * @param {int} timeout
     * @returns {int} handle 
     */
   	search: function(queryString, limit, timeout){
  	    this._log("Connection.search: " + queryString);
        if (this.ready){
        	try{
        		return this._search(queryString, limit, timeout);
            } catch (e) {
    	        this._error ("Connection.search failed: " + e.message);
                return null;
            }
        }else{
            this._log ("Connection not ready");
        	return null;
        }
   	},
   	
    /**
     * Notify this connection that a result has been selected. Calling this function will result in either a call to {Septima.Search.Connection~onSelect} or {Septima.Search.Connection~doTextSearch}
     * @param {int} handle
     * @param {int} resultId
     */
   	select: function(searchHandle, simpleResultId){
        setTimeout(Septima.bind(function(searchHandle, simpleResultId){
            this._log ("Connection.select: " + searchHandle + ", " + simpleResultId);
            if (this.ready){
                try{
                    this._select(searchHandle, simpleResultId);
                } catch (e) {
                    this._error ("Connection.select failed: " + e.message);
                }
            }else{
                this._error ("Connection not ready");
            }
        }, this, searchHandle, simpleResultId), 100);
   	},
   	
    /**
     * @typedef {Object} Septima.Search.Connection.SimpleResult
     * @property {int} id
     * @property {string} title
     * @property {string} description
     * @property {string} image url of svg
     * @property {int} searcherId
     */
    
    /**
     * Get list of results from specific searcher. Serialized with JSON.stringify if connectionOptions.stringify === true.
     * @param {int} handle
     * @param {int} searcherId
     * @returns {Septima.Search.Connection.SimpleResult[]|string} 
     */
   	getSearcherResults: function (searchHandle, searcherId){
   	    var simpleResults = [];
   	    if (this.lastSearch.handle === searchHandle){
   	        simpleResults = this.lastSearch.getSearcherResults(searcherId);
   	    }
        if (this.stringify){
            return JSON.stringify(simpleResults);
        }else{
            return simpleResults;
        }
   	},
    	
    /**
     * Get list of results.  Serialized with JSON.stringify if connectionOptions.stringify === true.
     * @param {int} handle
     * @returns {Septima.Search.Connection.SimpleResult[]} 
     */
    getSearchResults: function (searchHandle){
        var simpleResults = [];
        if (this.lastSearch.handle === searchHandle){
            simpleResults =  this.lastSearch.getResults();
        }
        if (this.stringify){
            return JSON.stringify(simpleResults);
        }else{
            return simpleResults;
        }
    },
    
    //Calls to client
    /**
     * Function. Called when a searcher has results.
     * @callback Septima.Search.Connection~onSearcherResults
     * @param {int} handle
     * @param {int} searcherId
     */
    _onSearcherResults: function (searcherId){
        this._log ("Calling client.onSearcherResults");
        setTimeout(Septima.bind(function(searcherId){
            this.client.onSearcherResults(this.lastSearch.handle, searcherId, this.getSearcherResults(this.lastSearch.handle, searcherId));
        }, this, searcherId), 100);
    },
    
    /**
     * Function. Called when all results are ready.
     * @callback Septima.Search.Connection~onSearchResults
     * @param {int} handle
     */
    _onSearchResults: function (){
        this._log ("Calling client.onSearchResults");
        setTimeout(Septima.bind(function(){
            this.client.onSearchResults(this.lastSearch.handle, this.getSearchResults(this.lastSearch.handle));
        }, this), 100);
      },
      
      /**
       * Function which is called when a result is selected.
       * @callback Septima.Search.Connection~onSelect
       * @param {Septima.Search.Searcher.Result} result Returned as string of options.stringify === true
       */
      _clientOnSelect: function(extendedResult){
          this._log ("Calling client.onSelect");
          setTimeout(Septima.bind(function(extendedResult){
              if (this.stringify){
                  this.client.onSelect(JSON.stringify(extendedResult));
              }else{
                  this.client.onSelect(extendedResult);
              }
          }, this, extendedResult), 100);
      },
      
      /**
       * Function. Called when log is written to (if options.loglevel > 0).
       * @callback Septima.Search.Connection~log
       * @param {string} message
       */
      _clientLog: function(message){
          setTimeout(Septima.bind(function(message){
              this.client.log(message);
          }, this, message), 100);
      },
      
      /**
       * Function. Called when an error occurs.
       * @callback Septima.Search.Connection~onError
       * @param {string} message
       */
      _clientError: function(message){
          setTimeout(Septima.bind(function(message){
              this.client.onError(message);
          }, this, message), 100);
      },

      /**
       * Function. Called with new search string.
       * @callback Septima.Search.Connection~doTextSearch
       * @param {string} query
       */
      _clientDoTextSearch: function(query){
          this._log ("Calling client.doTextSearch");
          setTimeout(Septima.bind(function(query){
              this.client.doTextSearch(query);
          }, this, query), 100);
      },

      /**
       * Function. Called when connection is ready.
       * @callback Septima.Search.Connection~onReady
       */
      _clientOnReady: function(){
          setTimeout(Septima.bind(function(){
              this.client.onReady();
          }, this), 100);
      },
      
	//Internal functions
	_createController: function (configOptions){
        var deferred = jQuery.Deferred();
    	//{mode: , data: parms}
    	//modes:
    	//"test": {mode: "test"} Run Built-In test
    	//"static": {mode: "static"} Use built-In static configuration
    	//"controller": {mode: "attach"} Find a global controller instance (_septimaSearchController)
    	//"configUrl": {mode: "configUrl"} Retrieve json configuration from url
      try{
    	  if (configOptions.mode === 'test'){
    	  } else if (configOptions.mode === 'static'){
    	      deferred.resolve(this._getStaticController());
          } else if (configOptions.mode === 'attach'){
             //Find controller on page
          }else if (configOptions.mode === 'configUrl'){
        	  var controllerBuilder = new Septima.Search.ControllerBuilder();
        	  controllerBuilder.setUrl(configOptions.url);
        	  controllerBuilder.build().done(Septima.bind(function(deferred, controller){
                  deferred.resolve(controller);
        		}, this, deferred));
          }else{
           	deferred.reject ("No configOptions.mode");
          }

      } catch (e) {
	        this._error ("_createController failed: " + e.message);
      }
      return deferred.promise();
    },
    
    _getStaticController: function(){
    	var controller;
        var controllerOptions = {blankBehavior: "search"};
        controller = new Septima.Search.Controller([], controllerOptions);

        var dawaSearcherOptions = {kommunekode: 101};
        var dawaSearcher = new Septima.Search.DawaSearcher(dawaSearcherOptions);
        controller.addSearcher({"title": "Adresser", "searcher" : dawaSearcher});

        var planSearchOptions = {searchindexToken: 'septimaSEARCHDEMO-A7OLGHG2J4'};
        planSearcher = new Septima.Search.PlanSearcher(planSearchOptions);
        controller.addSearcher({"title": "Lokalplaner", "searcher" : planSearcher});

        var cvrSearcherOptions = {matchesPhrase: "match", searchindexToken: "septimaSEARCHDEMO-A7OLGHG2J4"};
    	controller.addSearcher({"title": "Virksomheder", "searcher" : new Septima.Search.CVR_enhedSearcher(cvrSearcherOptions)});

    	return controller;
    },

    _getSearchers: function(){
    	var returnSearchers = [];
    	var searchers = this.controller.getSearchers();
    	for (var i = 0;i < searchers.length; i++){
    		var thisSearcher = searchers[i];
    		returnSearchers.push({name: thisSearcher.title, id: thisSearcher.getId()});
    	}
		return returnSearchers;
	},
    
    _search: function(queryString, limit, timeout){
      this.lastSearch.clear(this._getSearchers());
      var callBack = Septima.bind(function(handle, ssResults, isLast){
    	  if (handle === this.lastSearch.handle){
    		try{
    		    var searcherId = this.lastSearch.putResults(ssResults);
    		    if (searcherId !== null){
    		        this._onSearcherResults(searcherId);
    		    }
    		    if (isLast){
    		        this._onSearchResults();
    		    }
    		}catch(e){
               	this._error ("Error in controller.fetchData.callBack: " + e.message);
    		}
    	  }
      }, this, this.lastSearch.handle);
      
      setTimeout(Septima.bind(function(queryString, callBack, limit, timeout){
          this.controller.fetchData(queryString, callBack, limit, timeout);
      }, this, queryString, callBack, limit, timeout), 100);

      return this.lastSearch.handle;
    },
    
//    _buildSimpleResultsFromResults: function(ssResults){
//    	var simpleResults = [];
//    	//Array af {id:123, title: "Albertslund skole", description: "Folkeskole, B√łnnevangen 4", image: "data:nnnnnn", searcher: _searcherId_}
//    	for (var i = 0; i < ssResults.length;i++ ){
//    		var result = ssResults[i];
//    		this.lastResults.push(result);
//    		var searcherId = 'none';
//    		if (result.searcher){
//    			searcherId = result.searcher.getId(); 
//    		}
//    		var simpleResult = {
//   				id: this.lastResults.length - 1,
//   				title: result.title,
//   				description: result.description,
//   				image: result.image,
//   				searcher: searcherId
//    		};
//    		simpleResults.push(simpleResult);
//    		this.lastSimpleResults.push(simpleResult);
//    	}
//    	return simpleResults;
//    },
//    
	_select: function(searchHandle, simpleResultId){
        this._log ("Connection._select: " + searchHandle + ", " + simpleResultId);
	    if (searchHandle === this.lastSearch.handle){
            this._log ("Connection._select: searchHandle:" + searchHandle + " matches this.lastSearch.handle: " + this.lastSearch.handle );
	        var realResult = this.lastSearch.getRealResult(simpleResultId);
	        if (realResult !== null){
	            this.controller.select(realResult, this.viewFacade);
	        }
	    }else{
            this._log ("Connection._select: searchHandle:" + searchHandle + " doesn't match this.lastSearch.handle: " + this.lastSearch.handle );
	    }
	},
	
    _onControllerSelect: function(result){
    	if (!result.hasOwnProperty("newquery")){
        	//{id:123, title: "Albertslund skole", description: "Folkeskole, B√łnnevangen 4", image: "data:nnnnnn", searcher: searcherId, target: "skole", geometry: wkt: data: {}}
    		var searcherId = 'none';
    		if (result.searcher){
    			searcherId = result.searcher.getId(); 
    		}
    		var wkt = null;
    		if (result.geometry){
    			wkt = this._translateGeoJsonObjectToWkt(result.geometry);
    		}
        	var extendedResult = {
       				title: result.title,
       				description: result.description,
       				image: result.image,
       				searcher: searcherId,
       				target: result.target,
       				geometry: wkt,
       				data: result.data
        		};
        	this._clientOnSelect(extendedResult);
    	}
    },
    
    _onControllerError: function(searcher, message){
    	if (searcher !== null){
            this._error ("Controller Error: Searcher: " + searcher.getId() + ", message: " + message);
    	}else{
            this._error ("Controller Error: " + message);
    	}
    },
    
	_translateGeoJsonObjectToWkt: function(geoJsonObject){
		var jtsGeometry =   _septimaSearcherJstsGeoJSONReader.read(geoJsonObject);
		var wkt = _septimaSearcherJstsWktWriter.write(jtsGeometry);
		return wkt;
	},

    _log: function(message){
    	if (this.logLevel > 0){
    		this._clientLog(message);
    	}
    },

    _error: function(message){
    		this._clientError(message);
    },

    CLASS_NAME: 'Septima.Search.Connection'

    });