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  * @namespace framework.plugins
 12  */
 13 Ext.namespace("framework.widgets.search");
 14 
 15 /** 
 16  * Class to manage loads of local layer in several formats.
 17  * @name_ ImportLocalLayerAction
 18  * @class Class to manage loads of local layer in several formats.
 19  * @constructor
 20  * @extends <a target="_blank" href="http://gxp.opengeo.org/master/doc/lib/plugins/Tool.html">gxp.plugins.Tool</a>
 21  */
 22 
 23 
 24 /** api: example
 25  *  Sample code showing how to configure a FeatureGridPanel. In this case
 26  *  a popup ExtJS Window is created with a single FeatureGridPanel (xtype: 'hr_featuregridpanel').
 27  *
 28  *  .. code-block:: javascript
 29  *
 30  *      Ext.onReady(function () {
 31  *          // create a panel and add the map panel and grid panel
 32  *          // inside it
 33  *          new Ext.Window({
 34  *              title: __('Click Map or Grid to Select - Double Click to Zoom to feature'),
 35  *              layout: "fit",
 36  *              x: 50,
 37  *              y: 100,
 38  *              height: 400,
 39  *              width: 280,
 40  *              items: [{
 41  *                      xtype: 'hr_featuregridpanel',
 42  *                      id: 'hr-featuregridpanel',
 43  *                      title: __('Parcels'),
 44  *                      header: false,
 45  *                      columns: [
 46  *                          {
 47  *                              header: "Fid",
 48  *                              width: 60,
 49  *                              dataIndex: "id",
 50  *                              type: 'string'
 51  *                          },
 52  *                          {
 53  *                              header: "ObjectNum",
 54  *                              width: 180,
 55  *                              dataIndex: "objectnumm",
 56  *                              type: 'string'
 57  *                          }
 58  *                      ],
 59  *                      hropts: {
 60  *                          storeOpts: {
 61  *                              proxy: new GeoExt.data.ProtocolProxy({
 62  *                                  protocol: new OpenLayers.Protocol.HTTP({
 63  *                                      url: 'data/parcels.json',
 64  *                                      format: new OpenLayers.Format.GeoJSON()
 65  *                                  })
 66  *                              }),
 67  *                              autoLoad: true
 68  *                          },
 69  *                          zoomOnRowDoubleClick: true,
 70  *                          zoomOnFeatureSelect: false,
 71  *                          zoomLevelPointSelect: 8,
 72  *                          separateSelectionLayer: true
 73  *                      }
 74  *                  }
 75  *              ]
 76  *          }).show();
 77  *      });
 78  *
 79  *
 80  */
 81 
 82 
 83 /**
 84 * Show features both in a grid and on the map and have them selectable. 
 85 * @name_ FeatureGridPanel
 86 * @class Show features both in a grid and on the map and have them selectable.
 87 * @constructor
 88 @extends <a target="_blank" href="http://docs.sencha.com/extjs/3.4.0/#!/api/Ext.form.ComboBox">Ext.form.ComboBox</a>
 89 */
 90 framework.widgets.search.FeatureGridPanel = Ext.extend(Ext.grid.GridPanel, 
 91 /** 
 92  * @lends framework.widgets.search.FeatureGridPanel.prototype 
 93  */
 94 {
 95 
 96     /** api: config[downloadable]
 97      * ``Boolean``
 98      *  Should the features in the grid be downloadble?
 99      *  Download can be effected in 3 ways:
100      *  1. via Grid export (CSV and XLS only)
101      *  2. downloading the original feature format (GML2)
102      *  3. (GeoServer only) requesting the server for a triggered download (all Geoserver WFS formats),
103      * @public
104      * @type Boolean
105      */
106     downloadable: true,
107 
108     /** api: config[exportFormats]
109      *
110      * Array of document formats to be used when exporting the content of a GFI response. This requires the server-side CGI script
111      * ``heron.cgi`` to be installed. Exporting results in a download of a document with the contents of the (Grid) Panel.
112      * For example when 'XLS' is configured, exporting will result in the Excel (or compatible) program to be
113      * started with the GFI data in an Excel worksheet.
114      * Option values are 'CSV' and/or 'XLS', default is, ``null``, meaning no export (results in no export menu).
115      * The value ['CSV', 'XLS'] configures a menu to choose from a ``.csv`` or ``.xls`` export document format.
116      * @public
117      * @type [object] String Array 
118      * */
119     exportFormats: ['CSV', 'XLS', 'GMLv2', 'GeoJSON', 'WellKnownText'],
120 
121     /** api: config[columnCapitalize]
122      * 
123      *  Should the column names be capitalized when autoconfig is true?
124      *  @public
125      *  @type Boolean 
126      */
127     columnCapitalize: true,
128 
129     /** api: config[showTopToolbar]
130      * 
131      *  Should a top toolbar with feature count, clear button and download combo be shown? Default ``true``.
132      *  @public
133      *  @type Boolean 
134      */
135     showTopToolbar: true,
136 
137     /** api: config[showGeometries]
138      *  ``Boolean``
139      *  Should the feature geometries be shown? Default ``true``.
140      */
141     showGeometries: true,
142 
143     /** api: config[featureSelection]
144      *  
145      *  Should the feature geometries that are shown be selectable in grid and map? Default ``true``.
146      *  @public
147      *  @type Boolean 
148      */
149     featureSelection: true,
150 
151     /** api: config[loadMask]
152      *  
153      *  @public
154      *  @type Boolean 
155      */
156     loadMask: true,
157 
158      /** api: config[bbar]
159      *  
160      *  @public
161      *  @type Object Ext.PagingToolbar
162      */
163     /* bbar: new Ext.PagingToolbar({
164 		pageSize: 25,
165 		store: store,
166 		displayInfo: true,
167 		displayMsg: 'Displaying objects {0} - {1} of {2}',
168 		emptyMsg: "No objects to display"
169 	}),*/
170 
171     /** api: config[exportConfigs]
172      *  
173      *  The supported configs for formatting and exporting feature data. Actual presented download options
174      *  are configured with exportFormats.
175      *  @public
176      *  @type Object 
177      */
178     exportConfigs: {
179         CSV: {
180             formatter: 'CSVFormatter',
181             fileExt: '.csv',
182             mimeType: 'text/csv'
183         },
184         XLS: {
185             formatter: 'ExcelFormatter',
186             fileExt: '.xls',
187             mimeType: 'application/vnd.ms-excel'
188         },
189         GMLv2: {
190             formatter: 'OpenLayersFormatter',
191             format: new OpenLayers.Format.GML.v2({featureType: 'heronfeat', featureNS: 'http://heron-mc.org'}),
192             fileExt: '.gml',
193             mimeType: 'text/xml'
194         },
195         GeoJSON: {
196             formatter: 'OpenLayersFormatter',
197             format: 'OpenLayers.Format.GeoJSON',
198             fileExt: '.json',
199             mimeType: 'text/plain'
200         },
201         WellKnownText: {
202             formatter: 'OpenLayersFormatter',
203             format: 'OpenLayers.Format.WKT',
204             fileExt: '.wkt',
205             mimeType: 'text/plain'
206         }
207     },
208 
209     /** api: config[separateSelectionLayer]
210      *  
211      *  Should selected features be managed in separate overlay Layer (handy for printing) ?.
212      *  @public
213      *  @type Boolean
214      */
215     separateSelectionLayer: false,
216 
217     /** api: config[zoomOnFeatureSelect]
218      *  
219      *  Zoom to feature (extent) when selected ?.
220      *  @public
221      *  @type Boolean
222      */
223     zoomOnFeatureSelect: false,
224 
225     /** api: config[zoomOnRowDoubleClick]
226      *  
227      *  Zoom to feature (extent) when row is double clicked ?.
228      *  @public
229      *  @type Boolean
230      */
231     zoomOnRowDoubleClick: true,
232 
233     /** api: config[zoomLevelPointSelect]
234      *  
235      *  Zoom level for point features when selected, default ``10``.
236      *  @public
237      *  @type Integer
238      */
239     zoomLevelPointSelect: 10,
240 
241     /** api: config[zoomLevelPoint]
242      *  
243      *  Zoom level when layer is single point feature, default ``10``.
244      *  @public
245      *  @type Integer
246      */
247     zoomLevelPoint: 10,
248 
249     /** api: config[zoomToDataExtent]
250      *  
251      *  Zoom to layer data extent when loaded ?.
252      *  @public
253      *  @type Boolean
254      */
255     zoomToDataExtent: false,
256 
257     /** api: config[autoConfig]
258      *  
259      *  Should the store and grid columns autoconfigure from loaded features?.
260      *  @public
261      *  @type Boolean
262      */
263     autoConfig: true,
264 
265     /** api: config[vectorLayerOptions]
266      *  
267      *  Options to be passed on Vector constructors.
268      *  @public
269      *  @type Object
270      */
271     //vectorLayerOptions: {noLegend: true, displayInLayerSwitcher: false},
272     vectorLayerOptions: {
273         noLegend: true, 
274         displayInLayerSwitcher: false, 
275         styleMap: new OpenLayers.StyleMap({
276           'default': new OpenLayers.Style({
277               fill: false,
278               strokeColor: "#ffff00", 
279               strokeWidth: 2,
280               strokeOpacity: 0.8
281             }),
282             'select': new OpenLayers.Style({
283               fill: true,
284               fillColor: "#d80303",
285               fillOpacity: 0.2,
286               strokeColor: "#d80303", 
287               strokeWidth: 3
288             })
289         })
290     },
291     /**
292      * If it's true there will be added a button to perform "showAerialPhoto" event,
293      * otherwise it will be performed using double click on selected row.
294      * Default is true.
295      * @public
296      * @type boolean
297      */
298     openOnButton: true,
299     
300     /**
301      * Image icon button.
302      * @public
303      * @type String
304      */
305     iconButton: 'theme/app/img/camera16x16.png',
306     
307     /** 
308      *  Override init component
309      *  @private 
310      */ 
311     initComponent: function () {
312         // If columns specified we don't do autoconfig (column guessing from features)
313         if (this.columns) {
314             this.autoConfig = false;
315         }
316 
317         // specific config (besides GridPanel config)
318         Ext.apply(this, this.hropts);
319 
320         // If we have feature selection enabled we must show geometries
321         if (this.featureSelection) {
322             this.showGeometries = true;
323         }
324 
325         if (this.showGeometries) {
326             // Define OL Vector Layer to display search result features
327             var layer = this.layer = new OpenLayers.Layer.Vector(this.title, this.vectorLayerOptions);
328 
329             if (!this.map)
330                 this.map = this.target.mapPanel.map;
331             this.map.addLayer(this.layer);
332 
333             var self = this;
334             if (this.featureSelection && this.zoomOnFeatureSelect) {
335                 // See http://www.geoext.org/pipermail/users/2011-March/002052.html
336                 layer.events.on({
337                     "featureselected": function (e) {
338                         self.zoomToFeature(self, e.feature.geometry);
339                     },
340                     "dblclick": function (e) {
341                         self.zoomToFeature(self, e.feature.geometry);
342                     },
343                     "scope": layer
344                 });
345             }
346 
347             // May zoom to feature when grid row is double-clicked.
348             if (this.zoomOnRowDoubleClick) {
349                 this.on('celldblclick', function (grid, rowIndex, columnIndex, e) {
350                     var record = grid.getStore().getAt(rowIndex);
351                     var feature = record.getFeature();
352                     self.zoomToFeature(self, feature.geometry);
353                     if (!this.openOnButton)
354                         this.fireEvent('showAerialPhoto',record);
355                 });
356             }
357 
358             if (this.separateSelectionLayer) {
359                 this.selLayer = new OpenLayers.Layer.Vector(this.title + '_Sel', {noLegend: true, displayInLayerSwitcher: false});
360                 // selLayer.style = layer.styleMap.styles['select'].clone();
361                 this.selLayer.styleMap.styles['default'] = layer.styleMap.styles['select'];
362                 this.selLayer.style = this.selLayer.styleMap.styles['default'].defaultStyle;
363                 // this.selLayer.style = layer.styleMap.styles['select'].clone();
364                 layer.styleMap.styles['select'] = layer.styleMap.styles['default'].clone();
365                 layer.styleMap.styles['select'].defaultStyle.fillColor = 'white';
366                 layer.styleMap.styles['select'].defaultStyle.fillOpacity = 0.0;
367                 this.map.addLayer(this.selLayer);
368                 this.map.setLayerIndex(this.selLayer, this.map.layers.length - 1);
369                 this.layer.events.on({
370                     featureselected: this.updateSelectionLayer,
371                     featureunselected: this.updateSelectionLayer,
372                     scope: this
373                 });
374             }
375         }
376 
377         this.setupStore(this.features);
378 
379         // Will take effort to support paging...
380         // http://dev.sencha.com/deploy/ext-3.3.1/examples/grid/paging.html
381        	/* 
382          this.bbar = new Ext.PagingToolbar({
383             pageSize: 5,
384             store: this.store,
385             displayInfo: true,
386             displayMsg: 'Displaying objects {0} - {1} of {2}',
387             emptyMsg: "No objects to display"
388          });
389         */
390          
391 
392         // Enables the interaction between features on the Map and Grid
393         if (this.featureSelection && !this.sm) {
394             this.sm = new GeoExt.grid.FeatureSelectionModel();
395         }
396 
397         if (this.showTopToolbar) {
398             this.tbar = this.createTopToolbar();
399         }
400 
401         framework.widgets.search.FeatureGridPanel.superclass.initComponent.call(this);
402 
403         // ExtJS lifecycle events
404         this.addListener("afterrender", this.onPanelRendered, this);
405         this.addListener("show", this.onPanelShow, this);
406         this.addListener("hide", this.onPanelHide, this);
407     },
408 
409 
410     /** api: method[createTopToolbar]
411      * Create the top toolbar.
412      * @private
413      */
414     createTopToolbar: function () {
415 
416         // Top toolbar text, keep var for updating
417         var tbarItems = [this.tbarText = new Ext.Toolbar.TextItem({text: __(' ')})];
418         tbarItems.push('->');
419 
420         if (this.downloadable) {
421 
422             // Multiple display types configured: add toolbar tabs
423             // var downloadMenuItems = ['<b class="menu-title">' + __('Choose an Export Format') + '</b>'];
424             var downloadMenuItems = [];
425             var item;
426             for (var j = 0; j < this.exportFormats.length; j++) {
427                 var exportFormat = this.exportFormats[j];
428                 item = {
429                     text: __('as') + ' ' + exportFormat,
430                     cls: 'x-btn',
431                     iconCls: 'icon-table-export',
432                     scope: this,
433                     exportFormat: exportFormat,
434                     handler: function (evt) {
435                         this.exportData(evt.exportFormat);
436                     }
437                 };
438                 downloadMenuItems.push(item);
439             }
440 
441             if (this.downloadInfo && this.downloadInfo.downloadFormats) {
442                 var downloadFormats = this.downloadInfo.downloadFormats;
443                 for (var k = 0; k < downloadFormats.length; k++) {
444                     var downloadFormat = downloadFormats[k];
445                     item = {
446                         text: __('as') + ' ' + downloadFormat.name,
447                         cls: 'x-btn',
448                         iconCls: 'icon-table-export',
449                         downloadFormat: downloadFormat.outputFormat,
450                         fileExt: downloadFormat.fileExt,
451                         scope: this,
452                         handler: function (evt) {
453                             this.downloadData(evt.downloadFormat, evt.fileExt);
454                         }
455                     };
456                     downloadMenuItems.push(item);
457                 }
458             }
459 
460             if (downloadMenuItems.length > 0) {
461                 /* Add to toolbar. */
462                 tbarItems.push({
463                     text: __('Download'),
464                     cls: 'x-btn-text-icon',
465                     iconCls: 'icon-table-save',
466                     tooltip: __('Choose a Download Format'),
467                     menu: new Ext.menu.Menu({
468                         style: {
469                             overflow: 'visible'	 // For the Combo popup
470                         },
471                         items: downloadMenuItems
472                     })
473                 });
474             }
475         }
476 
477         tbarItems.push('->');
478         tbarItems.push({
479             text: __('Clear'),
480             cls: 'x-btn-text-icon',
481             iconCls: 'icon-table-clear',
482             tooltip: __('Remove all results'),
483             scope: this,
484             handler: function () {
485                 this.removeFeatures();
486             }
487         });
488 
489         return new Ext.Toolbar({enableOverflow: true, items: tbarItems});
490     },
491 
492     /** api: method[loadFeatures]
493      * Loads array of feature objects in store and shows them on grid and map.
494      * @private
495      */
496     loadFeatures: function (features, featureType) {
497         this.removeFeatures();
498         this.featureType = featureType;
499 
500         // Defensive programming
501         if (!features || features.length == 0) {
502             return;
503         }
504 
505         this.showLayer();
506         this.store.loadData(features);
507         this.updateTbarText();
508 
509         // Whenever Paging is supported...
510         // http://dev.sencha.com/deploy/ext-3.3.1/examples/grid/paging.html
511         // this.store.load({params:{start:0, limit:25}});
512 
513         if (this.zoomToDataExtent) {
514             if (features.length == 1 && features[0].geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
515                 var point = features[0].geometry.getCentroid();
516                 this.map.setCenter(new OpenLayers.LonLat(point.x, point.y), this.zoomLevelPoint);
517             } else if (this.layer) {
518                 this.map.zoomToExtent(this.layer.getDataExtent());
519             }
520         }
521     },
522 
523     /** api: method[hasFeatures]
524      * Does this Panel have features?.
525      * @private
526      */
527     hasFeatures: function () {
528         return this.store && this.store.getCount() > 0;
529     },
530 
531     /** api: method[removeFeatures]
532      * Removes all feature objects from store .
533      * @private
534      */
535     removeFeatures: function () {
536         if (this.store) {
537             this.store.removeAll(false);
538         }
539         if (this.selLayer) {
540             this.selLayer.removeAllFeatures({silent: true});
541         }
542         this.updateTbarText();
543     },
544 
545     /** api: method[showLayer]
546      * Show the layer with features on the map.
547      * @private
548      */
549     showLayer: function () {
550         // this.removeFeatures();
551         if (this.layer) {
552             if (this.selLayer) {
553                 this.map.setLayerIndex(this.selLayer, this.map.layers.length - 1);
554                 this.map.setLayerIndex(this.layer, this.map.layers.length - 2);
555             } else {
556                 this.map.setLayerIndex(this.layer, this.map.layers.length - 1);
557             }
558             if (!this.layer.getVisibility()) {
559                 this.layer.setVisibility(true);
560             }
561             if (this.selLayer && !this.selLayer.getVisibility()) {
562                 this.selLayer.setVisibility(true);
563             }
564         }
565     },
566 
567     /** api: method[hideLayer]
568      * Hide the layer with features on the map.
569      * @private
570      */
571     hideLayer: function () {
572         // this.removeFeatures();
573         if (this.layer && this.layer.getVisibility()) {
574             this.layer.setVisibility(false);
575         }
576         if (this.selLayer && this.selLayer.getVisibility()) {
577             this.selLayer.setVisibility(false);
578         }
579     },
580 
581     /** api: method[hideLayer]
582      * Hide the layer with features on the map.
583      * @private
584      */
585     zoomToFeature: function (self, geometry) {
586         if (!geometry) {
587             return;
588         }
589 
590         // For point features center map otherwise zoom to geometry bounds
591         if (geometry.getVertices().length == 1) {
592             var point = geometry.getCentroid();
593             self.map.setCenter(new OpenLayers.LonLat(point.x, point.y), self.zoomLevelPointSelect);
594         } else {
595             self.map.zoomToExtent(geometry.getBounds());
596         }
597     },
598 
599     /** api: method[zoomButtonRenderer]
600      * 
601      * @private
602      */
603      zoomButtonRenderer: function () {
604         var id = Ext.id();
605 
606         (function () {
607             new Ext.Button({
608                 renderTo: id,
609                 text: 'Zoom'
610             });
611 
612         }).defer(25);
613 
614         return (String.format('<div id="{0}"></div>', id));
615     },
616 
617     /** private: method[setupStore]
618      *  :param features: ``Array`` optional features.
619      *  @private
620      */
621     setupStore: function (features) {
622         if (this.store && !this.autoConfig) {
623             return;
624         }
625         
626         // Prepare fields array for store from columns in Grid config.
627         var storeFields = [];
628         if (this.autoConfig && features) {
629               this.columns = [];
630               if (features.length > 0) {
631                 var feature = features[0];
632                 var fieldName;
633                 //if requested, we'll add a button
634                 if (this.openOnButton) {
635                   var column = {
636                     xtype: 'actioncolumn',
637                     width: 32,
638                     sortable: false,
639                     items: [{
640                         icon   : this.iconButton,
641                         tooltip: 'Apri foto aerea',
642                         handler: function(grid, rowIndex, colIndex) {
643                             this.fireEvent('showAerialPhoto', grid.getStore().getAt(rowIndex));
644                         },
645                         scope: this
646                     }]
647                   };
648                   this.columns.push(column);
649                   storeFields.push({name: 'button'});
650                 }
651                 for (fieldName in feature.attributes) {
652                     // Capitalize header names
653                     var column = {
654                         header: this.columnCapitalize ? fieldName.substr(0, 1).toUpperCase() + fieldName.substr(1).toLowerCase() : fieldName,
655                         width: 100,
656                         dataIndex: fieldName,
657                         sortable: true
658                     };
659 
660                     // Look for custom rendering
661                     if (this.gridCellRenderers && this.featureType) {
662                         var gridCellRenderer;
663                         for (var k = 0; k < this.gridCellRenderers.length; k++) {
664                             gridCellRenderer = this.gridCellRenderers[k];
665                             if (gridCellRenderer.attrName && fieldName == gridCellRenderer.attrName) {
666                                 if (gridCellRenderer.featureType && this.featureType == gridCellRenderer.featureType || !gridCellRenderer.featureType) {
667                                     column.options = gridCellRenderer.renderer.options;
668                                     column.renderer = gridCellRenderer.renderer.fn;
669                                 }
670                             }
671                         }
672                     }
673                     this.columns.push(column);
674                     storeFields.push({name: column.dataIndex});
675                 } //end of attributes scanning
676                 
677             }
678         } else {
679             Ext.each(this.columns, function (column) {
680                 if (column.dataIndex) {
681                     storeFields.push({name: column.dataIndex, type: column.type});
682                 }
683                 column.sortable = true;
684             });
685         }
686 
687         // this.columns.push({ header: 'Zoom', width: 60, sortable: false, renderer: self.zoomButtonRenderer });
688 
689         // Define the Store
690         var storeConfig = { layer: this.layer, fields: storeFields};
691 
692         // Optional extra store options in config
693         Ext.apply(storeConfig, this.hropts.storeOpts);
694 
695         this.store = new GeoExt.data.FeatureStore(storeConfig);
696     },
697 
698     /** private: method[updateSelectionLayer]
699      *  :param evt: ``Object`` An object with a feature property referencing
700      *                         the selected or unselected feature.
701      *  @private
702      */
703     updateSelectionLayer: function (evt) {
704         if (!this.showGeometries) {
705             return;
706         }
707         this.selLayer.removeAllFeatures({silent: true});
708         var features = this.layer.selectedFeatures;
709         for (var i = 0; i < features.length; i++) {
710             var feature = features[i].clone();
711             this.selLayer.addFeatures(feature);
712         }
713     },
714 
715     /** api: method[onPanelRendered]
716      *  Called when Panel has been rendered.
717      *  @private
718      */
719     onPanelRendered: function () {
720         if (this.ownerCt) {
721             this.ownerCt.addListener("parenthide", this.onParentHide, this);
722             this.ownerCt.addListener("parentshow", this.onParentShow, this);
723         }
724     },
725 
726     /** private: method[onPanelShow]
727      * Called after our panel is shown.
728      * @private
729      */
730     onPanelShow: function () {
731         if (this.selModel && this.selModel.selectControl) {
732             this.selModel.selectControl.activate();
733         }
734     },
735 
736     /** private: method[onPanelHide]
737      * Called  before our panel is hidden.
738      * @private
739      */
740     onPanelHide: function () {
741         if (this.selModel && this.selModel.selectControl) {
742             this.selModel.selectControl.deactivate();
743         }
744     },
745 
746     /** private: method[onParentShow]
747      * Called usually before our panel is created.
748      * @private
749      */
750     onParentShow: function () {
751         this.showLayer();
752     },
753 
754     /** private: method[onParentHide]
755      * Cleanup usually before our panel is hidden.
756      * @private
757      */
758     onParentHide: function () {
759         this.removeFeatures();
760         this.hideLayer();
761     },
762 
763     /** private: method[cleanup]
764      * Cleanup usually before our panel is destroyed.
765      * @private
766      */
767     cleanup: function () {
768         this.removeFeatures();
769         if (this.selModel && this.selModel.selectControl) {
770             this.selModel.selectControl.deactivate();
771             this.selModel = null;
772         }
773 
774         if (this.layer) {
775             this.map.removeLayer(this.layer);
776         }
777 
778         if (this.selLayer) {
779             this.map.removeLayer(this.selLayer);
780         }
781         return true;
782     },
783 
784     /** private: method[updateTbarText]
785      * Update text message in top toolbar.
786      * @private
787      */
788     updateTbarText: function () {
789         if (!this.tbarText) {
790             return;
791         }
792         var objCount = this.store ? this.store.getCount() : 0;
793         this.tbarText.setText(objCount + ' ' + (objCount != 1 ? __('Results') : __('Result')));
794     },
795 
796     /** private: method[exportData]
797      * Callback handler function for exporting and downloading the data to specified format.
798      * @private
799      */
800     exportData: function (exportFormat) {
801 
802         var store = this.store;
803 
804         // Get config from preconfigured configs
805         var config = this.exportConfigs[exportFormat];
806         if (!config) {
807             Ext.Msg.alert(__('Warning'), __('Invalid export format configured: ' + exportFormat));
808             return;
809         }
810 
811         // Create the filename for download
812         var featureType = this.featureType ? this.featureType : 'heron';
813         config.fileName = featureType + config.fileExt;
814 
815         // Use only the columns from the original data, not the internal feature store columns
816         // 'fid', 'state' and the feature object itthis, see issue 181. These are the first 3 fields in
817         // a GeoExt FeatureStore.
818         config.columns = (store.fields && store.fields.items && store.fields.items.length > 3) ? store.fields.items.slice(3) : null;
819 
820         // Format the feature or grid data to chosen format and force user-download
821         var data = framework.data.DataExporter.formatStore(store, config, true);
822         framework.data.DataExporter.download(data, config);
823     },
824 
825     /** private: method[downloadData]
826      * Callback handler function for direct downloading the data in specified format.
827      * @private
828      */
829     downloadData: function (downloadFormat, fileExt) {
830 
831         var downloadInfo = this.downloadInfo;
832         downloadInfo.params.outputFormat = downloadFormat;
833         downloadInfo.params.filename = downloadInfo.params.typename + fileExt;
834 
835         var paramStr = OpenLayers.Util.getParameterString(downloadInfo.params);
836 
837         var url = OpenLayers.Util.urlAppend(downloadInfo.url, paramStr);
838         if (url.length > 2048) {
839             Ext.Msg.alert(__('Warning'), __('Download URL string too long (max 2048 chars): ') + url.length);
840             return;
841         }
842 
843         // Force user-download
844         framework.data.DataExporter.directDownload(url);
845     }
846 
847 });
848 
849 /** api: xtype = framework_featuregridpanel */
850 Ext.reg('framework_featuregridpanel', framework.widgets.search.FeatureGridPanel);
851