Source: searchers/arcGisSearcher.js

 /** 
 * 
 * @class Searches an arcGIS service.
 * @constructs Septima.Search.arcGisSearcher
 * @augments Septima.Search.Searcher
 * @param {Object} options arcGisSearcher expects these properties:<br/>
 * @param {string} options.host "Service endpoint (SpatialSuite Url) eg.: [http:]//sps-demo.septima.dk[:80]. Protocol and port are optional"
 * @param {string} options.datasources Comma separated list of indexed data sources to search. Default: "*"
 *  
 * @example 
 * var arcGisSearcherOptions = {
 *     catalogUrl : http:// <catalog-url>" eg: "http://kortservice.vejle.dk/gis/rest/services",
 *     serviceName: "[folder/]service" eg: "GISWEB2/FamilieSocial",
 *     onSelect : function(result) {
 *          jQuery('#log').prepend('<b>'+ ArcGIS objekt + ':</b> '+ result.title + '<br/>');
 *     },
 *     layers : "*" or ["layer1Name", "layer2Name"],
 *     matchesPhrase: "Begynder med ",
 *     searchDelay: 200
 * };
 * searchers.push({title : '', searcher : new Septima.Search.arcGisSearcher(arcGisSearcherOptions)});
 *
 */
Septima.Search.arcGisSearcher = Septima.Class (
		Septima.Search.Searcher, /**  @lends Septima.Search.arcGisSearcher# */{
		catalogUrl: null,
		serviceName: null,
		serviceLayers: [],
		serviceLayerIds: [],
		layersToSearch: "*", 

		/**
		 * This function is called when a result is selected.
		 * @callback Septima.Search.arcGisSearcher~selectCallback
		 * @param {Object} result
		 * @param result.title {string}
		 * @param result.description {string}
		 * @param result.data {object} arcGIS feature
		 * @param result.data.heading {string} title
		 */

	    initialize: function (options) {
	    	options.usesGeoFunctions = true;
			if (options === undefined){
				throw "New Septima.Search.arcGisSearcher(options): Options missing.";
			}
			this.Searcher(options);
			this.catalogUrl = options.catalogUrl;
			this.serviceName = options.serviceName;
		    this.serviceLayers = [];
		    if (options.layers === undefined){
			    this.layersToSearch = "*";
		    }else{
			    this.layersToSearch = options.layers;
			    if (this.layersToSearch === "" || this.layersToSearch === " "){
				    this.layersToSearch = "*";
			    }
		    }
			this.serviceURL = this.catalogUrl + '/' + this.serviceName + '/MapServer';
	    	
	    	this.getLayers();
	    	this.loadEsriLibs();

	    },
	    
	    getLayers: function(){
			jQuery.ajax({
	    		url: this.serviceURL + '/layers?f=json',
		        dataType: 'jsonp',
	            cache : false,
	            timeout : 10000,
	            crossDomain : true,
	            async:true,
	            success:  Septima.bind(function(data, textStatus,  jqXHR){
	            	this.indexedDatasources = [];
	            	if (typeof data.layers !== 'undefined'){
	                	for (var i=0;i<data.layers.length;i++){
	                		var layer = data.layers[i];
	                		if (layer.type === 'Feature Layer' && this.inLayersToSearch(layer)){
	                			layer.displayFieldName = this.getDisplayFieldName(layer); 
	                    		this.serviceLayers.push(layer);
	                    		this.serviceLayerIds.push(layer.id);
	        					this.registerTarget(layer.name);
	                		}
	                	}
	            	}
	            }, this),
	            error : Septima.bind(function (postCall, jqXHR, textStatus, errorThrown) {
	            	
	            },this)
	          });
	    },

	    inLayersToSearch: function(layer){
	    	if (this.layersToSearch === "*"){
	    		return true;
	    	}else{
	        	var arrLayersToSearch = this.layersToSearch.split(" ");
	        	for (var i=0;i<arrLayersToSearch.length;i++){
	        		if (arrLayersToSearch[i].toLowerCase()==layer.name.toLowerCase()){
	        			return true;
	        		}
	        	}
	    	}
	    	return false;
	    },

	    getDisplayFieldName: function(layer){
	    	if (layer.drawingInfo){
	    		if (layer.drawingInfo.labelingInfo){
	    			if (layer.drawingInfo.labelingInfo.length > 0){
	    				if (layer.drawingInfo.labelingInfo[0].labelExpression){
	    					var labelExpression = layer.drawingInfo.labelingInfo[0].labelExpression;
	    					if (labelExpression.indexOf('[') === 0){
	    						labelExpression = labelExpression.substring(1, labelExpression.length - 1);
	    						return labelExpression;
	    					}
	    				}
	    			}
	    		}
	    	}
	    	return layer.displayField;
	    },
	    
	    loadEsriLibs: function(){
	    	//http://terraformer.io/arcgis-parser/
			jQuery.ajax({
		        url: "http://cdn-geoweb.s3.amazonaws.com/terraformer/1.0.4/terraformer.min.js",
		        dataType: 'script',
		        cache : true,
		        success: function(){
					jQuery.ajax({
				        url: "http://cdn-geoweb.s3.amazonaws.com/terraformer-arcgis-parser/1.0.4/terraformer-arcgis-parser.min.js",
				        dataType: 'script',
				        cache : true 
					});
		        }
			});
	    },
	    
	    fetchData: function (query, caller) {
	    	if (this.serviceLayers === null || this.serviceLayers.length === 0){
	    		//Return immediately with no results if layers hasn't been initialized or if no layers are indexed
	    		caller.fetchSuccess(this.createQueryResult());
	    		return;
	    	}
	    	
	    	var layerIds = this.serviceLayerIds;
	    	if (query.hasTarget){
	    		if (this.hasTarget(query.target)){
	    			layerIds = [];
	    			layerIds.push(this.getlayerIdFromTarget(query.target));
	    		}
	    	}else{
		    	if(query.queryString === ''){
		    		caller.fetchSuccess(this.getLayersAsNewQueries());
		    		return;
		    	}
	    	}
	    	
	    	var searchText = query.queryString;
	    	var xhr;
	    	
	    	if (searchText === ''){
		    	if (layerIds.length === 1){
		    		var layer = this.getLayerById(layerIds[0]);
	        		xhr = jQuery.ajax({
	    	            url: this.serviceURL +  '/' + layerIds[0] + '/query?f=json',
	    	            data: {where: '1=1', outFields: '*', returnGeometry: false,  geometryPrecision: 3},
	        	        dataType: 'jsonp',
	        	        cache : false,
	        	        timeout : 10000,
	        	        crossDomain : true,
				        success : Septima.bind(function (caller, query, layer, data, textStatus,  jqXHR) {
			                data.query = query;
				        	this.querySuccess (caller, data, layer, textStatus,  jqXHR);
				        },this, caller, query, layer)
	                });
		    	}else{
		    		caller.fetchSuccess(this.getLayersAsNewQueries());
		    		return;
		    	}
	    	}else{
		    	xhr = jQuery.ajax({
		            url: this.serviceURL + '/find?f=json',
		            data: {returnGeometry: false, searchText: searchText, layers: layerIds.join()},
			        dataType: 'jsonp',
		            cache : false,
		            timeout : 30000,
		            crossDomain : true,
		            async:true,
			        success : Septima.bind(function (caller, query, data, textStatus,  jqXHR) {
		                data.query = query;
			        	this.findSuccess (caller, data, textStatus,  jqXHR);
			        },this, caller, query),
			        error : Septima.bind(function (caller, jqXHR, textStatus, errorThrown) {
			        	if (textStatus.toLowerCase() != 'abort' ){
				        	this.error (caller, jqXHR, textStatus, errorThrown);
			        	}
			        },this, caller)     
		          });
	    	}
	    	
		    caller.registerOnCancelHandler ( Septima.bind(function (xhr) {
		    	if (xhr && xhr.readystate != 4){
		    		xhr.abort();
		    	}
		    },this,xhr));
			    	
	    },
	    
	    getlayerIdFromTarget: function (target){
	    	for (var i=0;i<this.serviceLayers.length;i++){
	    		if (target.toLowerCase() === this.serviceLayers[i].name.toLowerCase()){
	    			return this.serviceLayers[i].id;
	    		}
	    	}
	    	return "";
	    },
	    
	    getLayersAsNewQueries:function(){
	    	var queryResult = this.createQueryResult();
	    	for (i=0;i<this.serviceLayers.length;i++){
	    		var layer = this.serviceLayers[i];
        		var result = queryResult.addNewQuery(layer.name, null, layer.name + ": ", null, null);
        		//result.image = this.folderIconURI;
	    	}
	    	return queryResult;
	    },
	    
	    findSuccess : function(caller, data, textStatus, jqXHR) {
			if (caller.isActive()) {
				if (jqXHR.status === 200) {
					caller.fetchSuccess(this.parseFindResult(data));
				} else {
					caller.fetchError(this, jqXHR.statusText);
				}
			}

		},
		
	    querySuccess : function(caller, data, layer, textStatus, jqXHR) {
			if (caller.isActive()) {
				if (jqXHR.status === 200) {
					caller.fetchSuccess(this.parseQueryResult(data, layer));
				} else {
					caller.fetchError(this, jqXHR.statusText);
				}
			}

		},
	    error: function(caller, jqXHR, textStatus, errorThrown){
	        caller.fetchError(this, errorThrown);
	    },
	    
	    parseQueryResult: function(data, layer){
	        var queryResult = this.createQueryResult();
	        var query = data.query;
	        var features = data.features;
	        if (query.hasTarget) {
	        	for (var m=0;m<Math.min(features.length, query.limit);m++){
	        		arcFeature = features[m];
	        		arcFeature.layerId = layer.id;
    				title = this.getTitle(arcFeature, layer);
    				result = queryResult.addResult(title, null, null, arcFeature);
	        		result.isComplete = false;
	        	}
	        }else{
        		var count = features.length;
        		if (count > query.limit){
            		queryResult.addNewQuery(layer.name +  " (" + count + ")", null, layer.name + ":", null, null);
        		}else{
        			for (var j=0;j<count;j++){
        				arcFeature = features[j];
    	        		arcFeature.layerId = layer.id; 
        				title = this.getTitle(arcFeature, layer);
                        result = queryResult.addResult(title + ' (' + layer.name + ')', null, null, arcFeature);
		        		result.isComplete = false;
        			}
        		}
	        }
	    	
	        return queryResult;
	    },
	    
	    parseFindResult: function (data) {
	        var queryResult = this.createQueryResult();
	        var query = data.query;
	        
    		var arcResult;
    		var result;
    		var title;
    		var description;

	        if (!query.hasTarget) {
		        var returnedLayerIds = this.uniqueBy(data.results, function(result){return result.layerId;});
		        if (returnedLayerIds.length>0){
			        var resultsPerLayer = Math.max(1, Math.floor(query.limit/returnedLayerIds.length));
		        	for (var i=0;i<returnedLayerIds.length;i++){
	        			var layer = this.getLayerById(returnedLayerIds[i].key);
		        		var count = returnedLayerIds[i].results.length;
		        		if (count > resultsPerLayer){
		            		queryResult.addNewQuery(layer.name +  " (" + count + ")", null, layer.name + ": " + query.queryString, null, null);
		        		}else{
		        			for (var j=0;j<count;j++){
		        				arcResult = returnedLayerIds[i].results[j]; 
		        				title = this.getTitle(arcResult);
		        				description = null;
		        				if (query.queryString.length > 0 && title !== arcResult.value){
			        				description = arcResult.value;
		        				}
		                        result = queryResult.addResult(title + ' (' + layer.name + ')', description, null, arcResult);
				        		result.isComplete = false;
		        			}
		        		}
		        	}
		        }
	        }else{
	        	for (var m=0;m<Math.min(data.results.length, query.limit);m++){
	        		arcResult = data.results[m];
    				title = this.getTitle(arcResult);
    				description = null;
    				if (query.queryString.length > 0 && title !== arcResult.value){
        				description = arcResult.value;
    				}
    				result = queryResult.addResult(title, description, null, arcResult);
	        		result.isComplete = false;
	        	}
	        }
	        return queryResult;
	    },
	    
	    uniqueBy: function (arr, fn) {
	    	  var count = {};
	    	  var distinct = [];
	    	  arr.forEach(function (x) {
	    	    var key = fn(x);
	    	    if (!count[key]) {
	    	    	count[key] = [x];
	    	    }else{
	    	    	count[key].push(x);
	    	    }
	    	  });
	    	    for (var prop in count){
	    	    	distinct.push({key: prop, results: count[prop]});
	    	    }
	    	  return distinct;
    	},
    	
    	getLayer: function(feature){
    		var layerId = feature.layerId;
	    	return this.getLayerById(layerId);
    	},
    	
    	getLayerById: function(layerId){
	    	for (var i=0;i<this.serviceLayers.length;i++){
	    		if ("" + layerId === "" + this.serviceLayers[i].id){
	    			return this.serviceLayers[i];
	    		}
	    	}
	    	return null;
    	},
    	
    	getTitle: function(feature, layerArg){
    		var layer;
    		if (typeof layerArg === 'undefined'){
        		layer = this.getLayer(feature);
    		}else{
    			layer = layerArg;
    		}
    		if (!layer.realDisplayFieldName || typeof feature.attributes[layer.realDisplayFieldName] === 'undefined'){
        		var displayFieldName = layer.displayFieldName;
        		for (var prop in feature.attributes){
        			if (prop.toLowerCase() === displayFieldName.toLowerCase()){
        				layer.realDisplayFieldName = prop;
        				return feature.attributes[layer.realDisplayFieldName];
        			}
        		}
    		}
			return feature.attributes[layer.realDisplayFieldName];
		},

		completeResult:function(result){
			var completeDeferred = jQuery.Deferred();
			if (result.isComplete){
				completeDeferred.resolve(result);			
			}else{
				result.isComplete = true;
				var params;
	        	var feature =  result.data;
	    		var layer = this.getLayer(feature);
        		var xhr = jQuery.ajax({
    	            url: this.serviceURL +  '/' + feature.layerId + '/query?f=json',
    	            data: {where: this.getIdFieldName(layer) + '=' + this.getFeatureId(feature), outFields: '', returnGeometry: true,  geometryPrecision: 3},
        	        dataType: 'jsonp',
        	        cache : false,
        	        timeout : 10000,
        	        crossDomain : true,
        	        success : Septima.bind(function (completeDeferred, result, data, textStatus,  jqXHR) {
        	        	if (data.features.length > 0 && data.features[0].geometry){
        	        		var geojson = Terraformer.ArcGIS.parse(data.features[0].geometry);
            	        	result.geometry = geojson;
        	        	}
    	        		completeDeferred.resolve(result);
        	        },this, completeDeferred, result)
                });
				
			}
			
			return completeDeferred.promise();
		},
		
		
		onSelect: function(result){
	        if (!result.newquery){
	        	var feature =  result.data;
	    		var layer = this.getLayer(feature);
        		var xhr = jQuery.ajax({
    	            url: this.serviceURL +  '/' + feature.layerId + '/query?f=json',
    	            data: {where: this.getIdFieldName(layer) + '=' + this.getFeatureId(feature), outFields: '', returnGeometry: true,  geometryPrecision: 3},
        	        dataType: 'jsonp',
        	        cache : false,
        	        timeout : 10000,
        	        crossDomain : true,
        	        success : Septima.bind(function (result, data, textStatus,  jqXHR) {
        	        	if (data.features.length > 0 && data.features[0].geometry){
        	        		var geojson = Terraformer.ArcGIS.parse(data.features[0].geometry);
            	        	result.geometry = geojson;
        	        	}
        	        	this._onSelectCallback(result);
        	        },this, result),
        	        
        	        error : Septima.bind(function (result, jqXHR, textStatus, errorThrown) {
        	        	this._onSelectCallback(result);
        	        },this, result)
                });
        		return;
	        }
		},
		
		getFeatureId: function(feature){
    		var layer = this.getLayer(feature);
			return feature.attributes[this.getIdFieldName(layer)];
		},
		
		getIdFieldName: function(layer){
    		if (!layer.idFieldName){
    			for (var i=0;i<layer.fields.length;i++){
    				var field = layer.fields[i];
    				if (field.type === 'esriFieldTypeOID'){
    					layer.idFieldName = field.name;
    					return layer.idFieldName;
    				}
    			}
    		}
    		return layer.idFieldName;
		},

	    CLASS_NAME: 'Septima.Search.arcGisSearcher'
			
});