2
* Copyright (C) 2007-2008 Camptocamp
4
* This file is part of MapFish Client
6
* MapFish Client is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation, either version 3 of the License, or
9
* (at your option) any later version.
11
* MapFish Client is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with MapFish Client. If not, see <http://www.gnu.org/licenses/>.
21
* @requires OpenLayers/Map.js
22
* @requires OpenLayers/Control/ModifyFeature.js
23
* @requires OpenLayers/Layer/Vector.js
26
Ext.namespace('mapfish.widgets');
28
Ext.namespace('mapfish.widgets.editing');
31
* Class: mapfish.widgets.editing.FeatureList
32
* Use this class to create an editable grid of features.
35
* - {Ext.grid.EditorGridPanel}
39
* Constructor: mapfish.widgets.editing.FeatureList
42
* config - {Object} The Grid config.
44
mapfish.widgets.editing.FeatureList = function(config) {
45
Ext.apply(this, config, {
46
sm: new Ext.grid.RowSelectionModel({singleSelect: true}),
50
mapfish.widgets.editing.FeatureList.superclass.constructor.call(this);
53
Ext.extend(mapfish.widgets.editing.FeatureList, Ext.grid.EditorGridPanel, {
56
* APIProperty: featureType
57
* {Ext.data.Record} Definition of a feature record,
58
* created by Ext.data.Record.create()
64
* {<OpenLayers.Map>} Where to display the selected geometries
70
* {<OpenLayers.Layer.Vector>} The Vector layer to use to draw the
71
* selected geometries.
76
* APIProperty: automaticMode
77
* {Boolean} If true, the geometry to edit can be selected on click.
78
* Otherwise, the user has to click on the geometry in the list to
79
* put it in edit mode.
84
* APIProperty: autoFocusMode
85
* {Integer} If 0, don't change the visible extent of the map.
86
* If 1, change the map's visible extent to match the selected geometry.
87
* If 2, make sure the selected geometry is visible (default).
92
* APIProperty: displayNotEdited
93
* {Boolean} If "false", display only the edited feature. If "true", shows
94
* the edited features and the others. Only relevant when automaticMode
97
displayNotEdited: true,
100
* APIMethod: editGeometryVisual
101
* How to represent a geometry in the grid. It will be the thing the user
102
* will have to click in order to edit the geometry.
105
* geometry - {<OpenLayers.Geometry>}
106
* record - {<Ext.data.Record>}
107
* edited - {Boolean} true if the geometry is being edited on the map.
109
editGeometryVisual: function(geometry, record, edited) {
110
return geometry ? (edited ? "->" : "X") : "";
115
* {Boolean} True if we are currently performing a drag and drop. Used
116
* when we receive a "remove" and "add" notification on the store, to
117
* know if it was a drag and drop.
123
* {Array(Object)} Save the columns parameter (dady nullifies the
124
* this.columns property).
129
* Property: modifyFeature
130
* {OpenLayers.Control.ModifyFeature} The control used to modify geometries.
135
* Method: initComponent
136
* Initialize the component.
138
initComponent: function() {
140
if (!this.map && !this.layer) {
141
OpenLayers.Console.error(
142
"Mandatory param for FeatureList missing: layer and/or map");
144
if (!this.featureType) {
145
OpenLayers.Console.error(
146
"Mandatory param for FeatureList missing: featureType");
149
// keep a reference on the definition of columns
150
this.colDefs = this.columns;
151
mapfish.widgets.editing.FeatureList.superclass.initComponent.call(this);
153
this.setGeoColRenderer();
155
// take care of the connections with the map
157
this.map = this.layer.map;
158
} else if (!this.layer) {
159
this.layer = new OpenLayers.Layer.Vector("Geometry editing");
160
this.map.addLayer(this.layer);
165
// define events for "start modifying a geometry", "modifying a geometry",
166
// and "done modifying a geometry"
167
this.addEvents({geomodifstart: true, geomodif: true, geomodifend: true});
169
// create modify feature control
170
var mode = OpenLayers.Control.ModifyFeature.RESHAPE |
171
OpenLayers.Control.ModifyFeature.DRAG;
172
this.modifyFeature = new OpenLayers.Control.ModifyFeature(this.layer, {
174
onModificationStart: function(feature) {
175
// temporarily activate the modify feature control
176
if (!self.automaticMode) {
179
self.refreshGeometryVisual(feature.data);
180
// select and show the row of the feature we are editing
181
var record = feature.data;
182
var row = self.getStore().findBy(function(r) {
183
return r.id == record.id;
185
self.getView().focusCell(row, 0);
186
self.getSelectionModel().selectRange(row, row);
187
self.fireEvent('geomodifstart', self, record, feature);
189
onModification: function(feature) {
190
self.fireEvent('geomodif', self, feature.data, feature);
192
onModificationEnd: function(feature) {
193
// deactivate the modify feature control
194
if (!self.automaticMode) {
198
self.refreshGeometryVisual(feature.data);
200
if (!self.displayNotEdited) {
201
self.layer.removeFeatures(feature);
203
self.fireEvent('geomodifend', self, feature.data, feature);
206
// add modify feature control to the map
207
this.map.addControl(this.modifyFeature);
209
// make sure the geometries are removed from the layer if a
210
// feature is removed.
211
this.getStore().on("remove", function(store, record, index) {
212
this.removeGeometries(record);
214
this.getStore().on("clear", function(store) {
215
store.each(this.removeGeometries, this);
218
// add added to the layer if a feature is added.
219
function add(store, records, index) {
220
if (self.displayNotEdited) {
221
for (var i = 0; i < records.length; ++i) {
222
self.addGeometries(records[i]);
227
this.getStore().on("add", add);
228
this.getStore().on("load", function(store, records, options) {
230
// On load, the data was cleared, but no other notification was
231
// fired. So we have to do some cleanup. Then, we can add the
233
if (this.modifyFeature.feature) {
234
this.modifyFeature.selectControl.unselect(
235
this.modifyFeature.feature);
239
add(store, records, 0);
246
* Called by EXT when the component is rendered.
248
onRender: function() {
249
mapfish.widgets.editing.FeatureList.superclass.onRender.apply(
252
// add the possibility to drag and drop rows for re-ordering them
254
var ddrow = new Ext.dd.DropTarget(this.getView().mainBody, {
256
notifyOver: function(source, e, data) {
257
var cindex = source.getDragData(e).rowIndex;
258
if (typeof cindex != "undefined") {
259
return this.dropAllowed;
261
return this.dropNotAllowed;
263
notifyDrop: function(dd, e, data) {
264
var dragData = dd.getDragData(e);
265
var destIndex = dragData.rowIndex;
266
if (typeof destIndex != "undefined") {
267
var record = data.selections[0];
269
data.grid.store.remove(record);
270
dragData.grid.store.insert(destIndex, record);
279
if (this.displayNotEdited) {
280
this.drawAllFeatures();
285
* Method: eachGeoColumn
286
* Iterate over each columns
289
* callback - {Function} What to call for each column.
291
eachGeoColumn: function(callback) {
292
for (var i = 0; i < this.colDefs.length; ++i) {
293
var col = this.colDefs[i];
294
var colDesc = this.featureType.prototype.fields.get(col.dataIndex);
295
if (colDesc.type == 'geo') {
296
callback.call(this, col, colDesc, i);
302
* Method: setGeoColRenderer.
303
* Sets the renderer to the geometry columns' definition.
305
setGeoColRenderer: function() {
306
this.eachGeoColumn(function(col, colDesc, colNum) {
307
col.renderer = OpenLayers.Function.bind(
308
function(value, cellMetaData, record, rowNum, colNum, store) {
310
var edited = (this.grid.modifyFeature.feature != null) &&
311
(this.grid.getFeatureByGeometry(value) ==
312
this.grid.modifyFeature.feature);
313
return '<div onclick="mapfish.widgets.editing.FeatureList.geometryClickHandler(\''
314
+ this.grid.id + '\', ' + record.id + ', \'' + this.colName + '\');">'
315
+ this.grid.editGeometryVisual(value, record, edited) + '</div>';
317
return this.grid.editGeometryVisual(value, record, false);
319
}, {grid: this, colName: colDesc.name}
325
* Method: drawAllFeatures
326
* Add all the geometries to the vector layer.
328
drawAllFeatures: function() {
330
if (this.displayNotEdited) {
332
this.eachGeoColumn(function(col, colDesc, colNum) {
333
this.store.each(function (record) {
334
var geometry = record.get(colDesc.name);
335
if (geometry && !this.getFeatureByGeometry(geometry)) {
336
var vector = new OpenLayers.Feature.Vector(
338
features.push(vector);
342
this.layer.addFeatures(features);
347
* Method: addGeometries
348
* Add all the geometries of a feature to the vector layer.
351
* record - {<Ext.data.Record>}
353
addGeometries: function(record) {
354
var layer = this.layer;
355
this.eachGeoColumn(function(col, colDesc, colNum) {
356
var geometry = record.get(colDesc.name);
357
if (geometry && !this.getFeatureByGeometry(geometry)) {
358
var vector = new OpenLayers.Feature.Vector(
360
layer.addFeatures(vector);
366
* Method: removeGeometries
367
* Remove all the geometries of a feature from the vector layer.
370
* record - {Ext.data.Record}
372
removeGeometries: function(record) {
373
this.eachGeoColumn(function(col, colDesc, colNum) {
374
var geometry = record.get(colDesc.name);
376
var feature = this.getFeatureByGeometry(geometry);
378
if (feature == this.modifyFeature.feature) {
379
// not to have bad stuff happening
380
// in modifyFeature.onModificationEnd
382
this.modifyFeature.selectControl.unselect(feature);
384
this.layer.removeFeatures([feature]);
393
* Method: getFeatureByGeometry
394
* Find the feature in the vector layer in function of the geometry.
397
* geometry - {<OpenLayers.Geometry>}
400
* {<OpenLayers.Feature.Vector>}
402
getFeatureByGeometry: function(geometry) {
403
var features = this.layer.features;
404
for (var i = 0; i < features.length; ++i) {
405
var cur = features[i];
406
if (cur.geometry == geometry) {
414
* APIMethod: editFirstGeometry
415
* Start to edit the first geometry of the given feature.
416
* Usefull only if not in automatic mode
419
* record - {<Ext.data.Record>}
421
editFirstGeometry: function(record) {
422
if (this.automaticMode) {
426
for (var i = 0; i < this.colDefs.length; ++i) {
427
var col = this.colDefs[i];
428
var colDesc = this.featureType.prototype.fields.get(col.dataIndex);
429
if (colDesc.type == 'geo') {
430
colName = colDesc.name;
434
this.editGeometry(record, colName, false);
438
* Method: editGeometry
439
* Start to edit one geometry of the given feature.
442
* record - {Ext.data.Record}
444
* focus - {Boolean} If true and in auto-focus mode, will zoom the map on
447
editGeometry: function(record, colName, focus) {
448
var geometry = record.get(colName);
452
var feature = this.getFeatureByGeometry(geometry);
453
if (!feature && !this.displayNotEdited) {
454
feature = new OpenLayers.Feature.Vector(
456
this.layer.addFeatures(feature);
459
var previousFeature = this.modifyFeature.feature;
460
if (previousFeature) {
461
this.modifyFeature.selectControl.unselect(
462
this.modifyFeature.feature);
464
if (previousFeature != feature) {
465
this.modifyFeature.selectControl.select(feature);
467
this.manageAutoFocus(geometry);
471
OpenLayers.Console.error(
472
"BUG: cannot find vector feature for: " + record);
477
* Method: manageAutoFocus
478
* Change the extent of the map in function of the configuration.
481
* geometry - {OpenLayers.Geometry}
483
manageAutoFocus: function (geometry) {
484
if (this.autoFocusMode == 1) {
485
this.map.zoomToExtent(geometry.getBounds());
486
} else if (this.autoFocusMode == 2) {
487
var extent = this.map.getExtent();
488
extent.extend(geometry.getBounds());
489
var margin = extent.getWidth() * 0.02;
490
extent.left += margin;
491
extent.right -= margin;
492
extent.top -= margin;
493
extent.bottom += margin;
494
this.map.zoomToExtent(extent);
499
* Method: refreshGeometryVisual
500
* Force the refresh of the icon representing the geometry in the grid.
503
* record - {Ext.data.Record}
505
refreshGeometryVisual: function(record) {
506
this.getView().refreshRow(record);
510
* APIMethod: setAutomaticMode
514
* automatic - {Boolean}
516
setAutomaticMode: function(automatic) {
517
if (automatic == this.automaticMode) {
520
this.automaticMode = automatic;
521
if (this.modifyFeature.feature) {
522
this.modifyFeature.selectControl.unselect(
523
this.modifyFeature.feature);
526
this.modifyFeature.activate();
528
this.modifyFeature.deactivate();
533
* APIMethod: setDisplayNotEdited
534
* Change the configuration of what should be displayed.
539
setDisplayNotEdited: function(value) {
540
if (value == this.displayNotEdited) {
543
this.displayNotEdited = value;
545
this.drawAllFeatures();
548
this.setAutomaticMode(false);
554
* Delete all the geometries from the layer with the exception of the
555
* currently edited feature (if any) and its handles.
560
clearLayer: function() {
562
var layer = this.layer;
563
var edited = this.modifyFeature.feature;
564
for (var i = 0; i < layer.features.length; ++i) {
565
var cur = layer.features[i];
567
cur.data && cur.data.endEdit) {
571
layer.removeFeatures(toRemove);
574
Ext.reg('featurelist', mapfish.widgets.editing.FeatureList);
577
* Method: geometryClickHandler
578
* Global function call by the onclick handler of the geometries' icons.
581
* gridId - {String} ID of the Grid
582
* recordId - {String} ID of the record
583
* colName - {String} Name of the geometry column
585
mapfish.widgets.editing.FeatureList.geometryClickHandler = function(
586
gridId, recordId, colName) {
588
var grid = Ext.getCmp(gridId);
590
var record = grid.store.getById(recordId);
592
grid.editGeometry(record, colName, true);
594
OpenLayers.Console.error("Cannot find record with id=" + recordId);
597
OpenLayers.Console.error("Cannot find grid with id=" + gridId);
602
* APIMethod: createRecord
603
* Generate a Record constructor for a specific record layout and with
604
* support for the 'geo' type. This function wraps Ext.data.Record.create.
607
* cols - {Array} An Array of field definition objects which specify
608
* field names, and optionally, data types, and a mapping for an
609
* Ext.data.Reader to extract the field's value from a data
610
* object. For further information see the Ext API reference
611
* (Ext.data.Record.create).
614
* {Function} - Record constructor
616
mapfish.widgets.editing.FeatureList.createRecord = function(cols) {
617
for (var i = 0; i < cols.length; ++i) {
619
if (col.type == 'geo') {
621
col.convert = function(v) {
626
col.sortType = Ext.data.SortTypes.none();
630
return Ext.data.Record.create.apply(null, arguments);