1 /**
  2  * @fileOverview  
  3  * Copyright (c) 2013 Regione Autonoma della Sardegna
  4  * Published under the GPL license.<br>
  5  * See <a href="https://sardegnageoportale.it/webgis/license.txt">https://sardegnageoportale.it/webgis/license.txt</a>
  6  * for the full text of the license.
  7  * @version 0.1
  8  */
  9  
 10  /**
 11  * @require OpenLayers/Control/SelectFeature.js
 12  */
 13 
 14 /**
 15  * @namespace framework.plugins
 16  */
 17 Ext.namespace("framework.form");
 18 
 19 /**
 20  * Creates a combo box that handles results from a geocoding service. By
 21  *  default it uses Directory OpenLS service by Regione Autonoma della Sardegna (http://www.sardegnageoportale.it/index.php?xsl=1598&s=203310&v=2&c=9869&t=1), but it can be configured with a custom store
 22  *  to use other services standard OpenLS. If the user enters a valid address in the search
 23  *  box, the combo's store will be populated with records that match the
 24  *  address.
 25  *  @name_ GeocoderOpenLSComboBox
 26  * @class Creates a combo box that handles results from a geocoding service OpenLS.
 27  * @constructor
 28  @extends <a target="_blank" href="http://docs.sencha.com/extjs/3.4.0/#!/api/Ext.form.ComboBox">Ext.form.ComboBox</a>
 29  */
 30 framework.form.GeocoderOpenLSComboBox = Ext.extend(Ext.form.ComboBox,
 31         /** 
 32          * @lends framework.form.GeocoderOpenLSComboBox.prototype 
 33          */
 34                 {
 35                     /**
 36                      * Text to display for an empty field
 37                      * @public
 38                      * @type String
 39                      */
 40                     emptyText: "Search with OpenLS",
 41                     /**
 42                      * The srs used by the geocoder service.
 43                      * Default is "EPSG:3003".
 44                      * @public
 45                      * @type String
 46                      */
 47                     srs: "EPSG:4326",
 48                     /** 
 49                      * The minimum zoom level to use when zooming to a location.
 50                      * Not used when zooming to a bounding box. Default is 12.
 51                      * @public
 52                      * @type number
 53                      */
 54                     zoom: 12,
 55                     /** 
 56                      * The minimum zoom level to use when zooming to a municipality.
 57                      * Not used when zooming to a bounding box. Default is 8.
 58                      * @public
 59                      * @type number
 60                      */
 61                     zoomMunicipality: 8,
 62                     /** 
 63                      * If provided, a marker will be drawn on this
 64                      * layer with the location returned by the geocoder. The location will be
 65                      * cleared when the map panned. 
 66                      * @public
 67                      * @type OpenLayers.Layer.Vector
 68                      */
 69                     layer: null,
 70                     /** 
 71                      * style point result
 72                      * @public
 73                      * @type OpenLayers.Layer.Vector
 74                      */
 75                     vectorStyle: null,
 76                     /** 
 77                      * Delay before the search occurs.
 78                      * Default is 200ms.
 79                      * @public
 80                      * @type number
 81                      */
 82                     queryDelay: 200,
 83                     /**
 84                      * Field from selected record to use when the combo's
 85                      * :meth:`getValue` method is called.  Default is "bounds". This field is
 86                      * supposed to contain an array of [left, bottom, right, top] coordinates
 87                      * for a bounding box or [x, y] for a location. 
 88                      * @public
 89                      * @type String
 90                      */
 91                     valueField: "bounds",
 92                     /** 
 93                      * The field to display in the combo boy. Default is
 94                      *  "name" for instant use with the default store for this component.
 95                      * @public
 96                      * @type String
 97                      */
 98                     displayField: "text",
 99                     /**
100                      * The field to get the location from. This field is supposed
101                      * to contain an array of [x, y] for a location. Default is "lonlat" for
102                      * instant use with the default store for this component.
103                      * @public
104                      * @type String
105                      */
106                     locationField: "lonlat",
107                     /** URL template for querying the geocoding service. If a
108                      * :obj:`store` is configured, this will be ignored. Note that the
109                      * :obj:`queryParam` will be used to append the user's combo box
110                      * input to the url. Default is
111                      * "http://nominatim.openstreetmap.org/search?format=json", for instant
112                      * use with the OSM Nominatim geolocator. However, if you intend to use
113                      * that, note the
114                      * `Nominatim Usage Policy <http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy>`_.
115                      * @public
116                      * @type String
117                      */
118                     url: 'http://webgis.regione.sardegna.it/followmeplus/OLService?serviceName=locationservice&exactStreetName=false',
119                     /** 
120                      * The query parameter for the user entered search text.
121                      * Default is "q" for instant use with OSM Nominatim.
122                      * @public
123                      * @type String
124                      */
125                     queryParam: "xmlRequest",
126                     /** 
127                      * Minimum number of entered characters to trigger a search.
128                      * Default is 4.
129                      * @public
130                      * @type Number
131                      */
132                     minChars: 4,
133                     /**
134                      * See http://www.dev.sencha.com/deploy/dev/docs/source/BoxComponent.html#cfg-Ext.BoxComponent-width,
135                      * default value is 240.
136                      * @public
137                      * @type Number
138                      */
139                     width: 240,
140                     /**
141                      * See http://www.dev.sencha.com/deploy/dev/docs/source/Combo.html#cfg-Ext.form.ComboBox-listWidth,
142                      * default value is 350.
143                      * @public
144                      * @type Number
145                      */
146                     listWidth: 400,
147                     /**
148                      * See http://www.dev.sencha.com/deploy/dev/docs/source/Combo.html#cfg-Ext.form.ComboBox-loadingText,
149                      * default value is "Search in Geozet...".
150                      * @public
151                      * @type String
152                      */
153                     loadingText: 'Searching...',
154                     /**
155                      * Hide trigger of the combo.
156                      * @private
157                      * @type boolean
158                      */
159                     hideTrigger: true,
160                     /**
161                      * Force selection.
162                      * @private
163                      * @type boolean
164                      */
165                     forceSelection: false,
166                     /** private: property[countryCode]
167                      * countryCode, default is IT.
168                      * @private
169                      * @type String
170                      */
171                     countryCode: 'IT',
172                     
173                     textSearch: '',
174                     
175                     /** 
176                      *  Override init component
177                      *  @private 
178                      */ 
179                     initComponent: function() {
180 
181                         var epsgOutput = this.srs.replace("EPSG:", "");
182 
183                         if (!this.map) {
184                             var mapPanel = this.findParentBy(function(cmp) {
185                                 return cmp instanceof GeoExt.MapPanel;
186                             });
187                             this.map = mapPanel.map;
188                         }
189                         if (Ext.isString(this.srs)) {
190                             this.srs = new OpenLayers.Projection(this.srs);
191                         }
192                         if (this.proxy)
193                             var sUrl = this.proxy + escape(this.url + '&epsgOutput=' + epsgOutput);
194                         else
195                             var sUrl = this.url + '&epsgOutput=' + epsgOutput;
196                         if (!this.store) {
197                             this.store = new Ext.data.Store({
198                                 proxy: new Ext.data.HttpProxy({
199                                     url: sUrl,
200                                     method: 'POST'
201                                 }),
202                                 fields: [
203                                     {name: "lon", type: "number"},
204                                     {name: "lat", type: "number"},
205                                     "text",
206                                     "xml",
207                                     "type"
208                                 ],
209                                 reader: new framework.data.GeocoderOpenLS_XLSReader()
210                             });
211                         }
212 
213                         this.on({
214                             added: this.handleAdded,
215                             select: this.handleSelect,
216                             focus: function() {
217                                 this.clearResult();
218                                 if (this.store.data.items.length > 0)
219                                     this.expand();
220                                 this.setValue(this.textSearch);
221                                 //  this.removeLocationFeature();
222                             },
223                             blur: function(textfield) {
224                               //  textfield.setValue("");
225                                 textfield.setValue(this.textSearch);
226                             },
227                             keyup: function(textfield, eventObject) {
228                                 this.textSearch = this.el.dom.value;
229                                 if (eventObject.getCharCode() == Ext.EventObject.ENTER) {
230                                     this.focus();
231                                     this.setValue(this.textSearch);
232                                     this.doQuery(this.el.dom.value, true);
233                                 } else {
234                                     if (this.isExpanded()) {
235                                         this.list.hide();
236                                 }
237                                 }
238                             },
239                             scope: this
240                         });
241                         
242                         if (this.layer) {
243                             
244                             this.map.addLayer(this.layer);
245                             
246                             this.selectCtrl = new OpenLayers.Control.SelectFeature([this.layer], {
247                                 toggle: true,
248                                 clickout: true
249                             });
250 
251                             this.map.addControl(this.selectCtrl);
252                             this.selectCtrl.activate();
253                         }
254 
255 
256                         return framework.form.GeocoderOpenLSComboBox.superclass.initComponent.apply(this, arguments);
257                     },
258                     /** 
259                      *  When this component is added to a container, see if it has a parent
260                      *  MapPanel somewhere and set the map
261                      *  @private
262                      */
263                     handleAdded: function() {
264                         var mapPanel = this.findParentBy(function(cmp) {
265                             return cmp instanceof GeoExt.MapPanel;
266                         });
267                         if (mapPanel) {
268                             this.setMap(mapPanel);
269                         }
270 
271                     },
272                     /**
273                      *  Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion.
274                      *  @private
275                      */
276                     collapse: function() {
277                         if (!this.isExpanded()) {
278                             return;
279                         }
280                         this.list.hide();
281                         this.el.blur();
282                         Ext.getDoc().un('mousewheel', this.collapseIf, this);
283                         Ext.getDoc().un('mousedown', this.collapseIf, this);
284                         this.fireEvent('collapse', this);
285                     },
286                     /** 
287                      * Zoom to the selected location, and also set a location marker if this
288                      * component was configured with an :obj:`layer`.
289                      * @private
290                      * @param {Object} combo comnoBox object.
291                      * @param {Object} record selected record.
292                      * @param {Object} idx index of selected record.
293                      */
294                     handleSelect: function(combo, record, index) {
295                         
296                         if(record.data.lon == 0 && record.data.lat == 0){
297                             return;
298                         }
299                         
300                          if (this.layer) {
301                              
302                              if (this.map.getLayerIndex(this.layer) == -1) {
303                                 this.map.addLayer(this.layer)
304                             }
305                             
306                             var controlosSelectFeature = this.map.getControlsByClass("OpenLayers.Control.SelectFeature");
307                             if(!controlosSelectFeature || controlosSelectFeature.length == 0){
308                             
309                                 this.selectCtrl = new OpenLayers.Control.SelectFeature([this.layer], {
310                                     toggle: true,
311                                     clickout: true
312                                 });
313 
314                                 this.map.addControl(this.selectCtrl);
315                                 this.selectCtrl.activate();
316                             }
317                         }
318 
319                         //strip HTML tags from string 
320                         var div = document.createElement("div");
321                         div.innerHTML = record.data.text;
322                         var text = div.textContent || div.innerText || "";
323 
324                         this.setValue(text); // put the selected name in the box
325 
326 
327                         var position = new OpenLayers.LonLat(record.data.lon, record.data.lat);
328 
329                         // Reproject (if required)
330                         position.transform(
331                                 new OpenLayers.Projection(this.srs.projCode),
332                                 this.map.getProjectionObject()
333                                 );
334 
335                         // zoom in on the location
336                         if (this.zoom) {
337                             var zoomResult = null;
338                             if (record.data.type == "municipality")
339                                 zoomResult = this.zoomMunicipality;
340                             else
341                                 zoomResult = this.zoom;
342 
343                             this.map.setCenter(position, zoomResult);
344                         }
345 
346                         //insert marker
347 
348                         if (this.layer) {
349                            //remove marker and popup
350                             this.layer.destroyFeatures();
351                             removePopup();
352                             
353                             var feature = new OpenLayers.Feature.Vector(
354                                     new OpenLayers.Geometry.Point(position.lon, position.lat),
355                                     null,
356                                     this.vectorStyle);
357 
358                             feature.text = record.data.text;
359 
360                             this.layer.addFeatures(feature);
361 
362                             createPopup(feature);
363                             
364                             this.map.searchName = this.id;                  
365                         
366                         // create popup on "featureselected"
367                           this.layer.events.on({
368                                   featureselected: function(e) {
369                                           createPopup(e.feature);
370                                   }
371                           });
372                         }
373                         
374                         function createPopup(feature) {
375                             removePopup();
376                             var feature_ = feature;
377                             this.popupAddr = new GeoExt.Popup({
378                                 //title: 'Ricerca indirizzo',
379                                 //map: this.map,
380                                 location: feature,
381                                 width: 200,
382                                 border: false,
383                                 html: '<div class="popupAddr">' + feature.text + '</div>',
384                                 maximizable: false,
385                                 collapsible: false
386                             });
387 //                            // unselect feature when the popup
388 //                            // is closed
389 //                            this.popupAddr.on({
390 //                                close: function() {
391 //                                    if (OpenLayers.Util.indexOf(feature_.layer.selectedFeatures, feature_) > -1) {
392 //                                        selectCtrl.unselect(feature_);
393 //                                    }
394 //                                },
395 //                                scope: this
396 //                            });
397                             this.popupAddr.show();
398                         }
399                         
400                     
401                     function removePopup() {
402                             if (this.popupAddr) {
403                                 this.popupAddr.close();
404                                 this.popupAddr.destroy();
405                             }
406 
407                     }
408   
409                         // blur the combo box
410                         //TODO Investigate if there is a more elegant way to do this.
411                         (function() {
412                             this.triggerBlur();
413                             this.el.blur();
414                         }).defer(100, this);
415                     },
416                     /** 
417                      *  Handler for the map's moveend event. Clears the selected location
418                      *  when the map center has changed.
419                      *  @private
420                      */
421                     clearResult: function() {
422                         this.clearValue();
423                     },
424                     /** 
425                      *  Set the :obj:`map` for this instance.
426                      *  @private
427                      *  @param {GeoExt.MapPanel||OpenLayers.Map} map object
428                      */
429                     setMap: function(map) {
430                         if (map instanceof GeoExt.MapPanel) {
431                             map = map.map;
432                         }
433                         this.map = map;
434                         map.events.on({
435                             // "moveend": this.clearResult,
436                             //  "click": this.removeFocus,
437                             scope: this
438                         });
439                     },
440                     /** 
441                      *  Called by a MapPanel if this component is one of the items in the panel.
442                      *  @private
443                      */
444                     addToMapPanel: Ext.emptyFn,
445                             
446                     /**
447                      * Execute a query to filter the dropdown list.  Fires the {@link #beforequery} event prior to performing the
448                      * query allowing the query action to be canceled if needed.
449                      * @private
450                      * @param {String} query The SQL query to execute
451                      * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
452                      * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option.  It
453                      * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
454                      */
455                     doQuery: function(q, forceAll) {
456                         qc = Ext.isEmpty(q) ? '' : q;
457                         q = Ext.isEmpty(q) ? '' : '<GeocodeRequest xmlns="http://www.opengis.net/xls">'+
458                                 '<Address countryCode="' + this.countryCode + '">'+
459                                 '<freeFormAddress>' + q + '</freeFormAddress>'+
460                                 '</Address>'+
461                                 '</GeocodeRequest>';
462                         var qe = {
463                             query: q,
464                             forceAll: forceAll,
465                             combo: this,
466                             cancel: false
467                         };
468                         if (this.fireEvent('beforequery', qe) === false || qe.cancel) {
469                             return false;
470                         }
471                         q = qe.query;
472                         forceAll = qe.forceAll;
473                         if (forceAll === true || (qc.length >= this.minChars)) {
474                             if (this.lastQuery !== q) {
475                                 this.lastQuery = q;
476                                 if (this.mode == 'local') {
477                                     this.selectedIndex = -1;
478                                     if (forceAll) {
479                                         this.store.clearFilter();
480                                     } else {
481                                         this.store.filter(this.displayField, q);
482                                     }
483                                     this.onLoad();
484                                 } else {
485                                     this.store.baseParams[this.queryParam] = q;
486                                     this.store.load({
487                                         params: this.getParams(q)
488                                     });
489                                     this.expand();
490                                 }
491                             } else {
492                                 this.selectedIndex = -1;
493                                 this.onLoad();
494                             }
495                         }
496                     }
497 
498 
499                 });
500 
501         /** api: xtype = gx_geocoderopenlscombobox */
502         Ext.reg("framework_geocoderopenlscombobox", framework.form.GeocoderOpenLSComboBox);
503