1
|
/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
|
2
|
* full list of contributors). Published under the 2-clause BSD license.
|
3
|
* See license.txt in the OpenLayers distribution or repository for the
|
4
|
* full text of the license. */
|
5
|
|
6
|
|
7
|
/**
|
8
|
* @requires OpenLayers/Handler.js
|
9
|
*/
|
10
|
|
11
|
/**
|
12
|
* Class: OpenLayers.Handler.Feature
|
13
|
* Handler to respond to mouse events related to a drawn feature. Callbacks
|
14
|
* with the following keys will be notified of the following events
|
15
|
* associated with features: click, clickout, over, out, and dblclick.
|
16
|
*
|
17
|
* This handler stops event propagation for mousedown and mouseup if those
|
18
|
* browser events target features that can be selected.
|
19
|
*
|
20
|
* Inherits from:
|
21
|
* - <OpenLayers.Handler>
|
22
|
*/
|
23
|
OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
|
24
|
|
25
|
/**
|
26
|
* Property: EVENTMAP
|
27
|
* {Object} A object mapping the browser events to objects with callback
|
28
|
* keys for in and out.
|
29
|
*/
|
30
|
EVENTMAP: {
|
31
|
'click': {'in': 'click', 'out': 'clickout'},
|
32
|
'mousemove': {'in': 'over', 'out': 'out'},
|
33
|
'dblclick': {'in': 'dblclick', 'out': null},
|
34
|
'mousedown': {'in': null, 'out': null},
|
35
|
'mouseup': {'in': null, 'out': null},
|
36
|
'touchstart': {'in': 'click', 'out': 'clickout'}
|
37
|
},
|
38
|
|
39
|
/**
|
40
|
* Property: feature
|
41
|
* {<OpenLayers.Feature.Vector>} The last feature that was hovered.
|
42
|
*/
|
43
|
feature: null,
|
44
|
|
45
|
/**
|
46
|
* Property: lastFeature
|
47
|
* {<OpenLayers.Feature.Vector>} The last feature that was handled.
|
48
|
*/
|
49
|
lastFeature: null,
|
50
|
|
51
|
/**
|
52
|
* Property: down
|
53
|
* {<OpenLayers.Pixel>} The location of the last mousedown.
|
54
|
*/
|
55
|
down: null,
|
56
|
|
57
|
/**
|
58
|
* Property: up
|
59
|
* {<OpenLayers.Pixel>} The location of the last mouseup.
|
60
|
*/
|
61
|
up: null,
|
62
|
|
63
|
/**
|
64
|
* Property: clickTolerance
|
65
|
* {Number} The number of pixels the mouse can move between mousedown
|
66
|
* and mouseup for the event to still be considered a click.
|
67
|
* Dragging the map should not trigger the click and clickout callbacks
|
68
|
* unless the map is moved by less than this tolerance. Defaults to 4.
|
69
|
*/
|
70
|
clickTolerance: 4,
|
71
|
|
72
|
/**
|
73
|
* Property: geometryTypes
|
74
|
* To restrict dragging to a limited set of geometry types, send a list
|
75
|
* of strings corresponding to the geometry class names.
|
76
|
*
|
77
|
* @type Array(String)
|
78
|
*/
|
79
|
geometryTypes: null,
|
80
|
|
81
|
/**
|
82
|
* Property: stopClick
|
83
|
* {Boolean} If stopClick is set to true, handled clicks do not
|
84
|
* propagate to other click listeners. Otherwise, handled clicks
|
85
|
* do propagate. Unhandled clicks always propagate, whatever the
|
86
|
* value of stopClick. Defaults to true.
|
87
|
*/
|
88
|
stopClick: true,
|
89
|
|
90
|
/**
|
91
|
* Property: stopDown
|
92
|
* {Boolean} If stopDown is set to true, handled mousedowns do not
|
93
|
* propagate to other mousedown listeners. Otherwise, handled
|
94
|
* mousedowns do propagate. Unhandled mousedowns always propagate,
|
95
|
* whatever the value of stopDown. Defaults to true.
|
96
|
*/
|
97
|
stopDown: true,
|
98
|
|
99
|
/**
|
100
|
* Property: stopUp
|
101
|
* {Boolean} If stopUp is set to true, handled mouseups do not
|
102
|
* propagate to other mouseup listeners. Otherwise, handled mouseups
|
103
|
* do propagate. Unhandled mouseups always propagate, whatever the
|
104
|
* value of stopUp. Defaults to false.
|
105
|
*/
|
106
|
stopUp: false,
|
107
|
|
108
|
/**
|
109
|
* Constructor: OpenLayers.Handler.Feature
|
110
|
*
|
111
|
* Parameters:
|
112
|
* control - {<OpenLayers.Control>}
|
113
|
* layer - {<OpenLayers.Layer.Vector>}
|
114
|
* callbacks - {Object} An object with a 'over' property whos value is
|
115
|
* a function to be called when the mouse is over a feature. The
|
116
|
* callback should expect to recieve a single argument, the feature.
|
117
|
* options - {Object}
|
118
|
*/
|
119
|
initialize: function(control, layer, callbacks, options) {
|
120
|
OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
|
121
|
this.layer = layer;
|
122
|
},
|
123
|
|
124
|
/**
|
125
|
* Method: touchstart
|
126
|
* Handle touchstart events
|
127
|
*
|
128
|
* Parameters:
|
129
|
* evt - {Event}
|
130
|
*
|
131
|
* Returns:
|
132
|
* {Boolean} Let the event propagate.
|
133
|
*/
|
134
|
touchstart: function(evt) {
|
135
|
this.startTouch();
|
136
|
return OpenLayers.Event.isMultiTouch(evt) ?
|
137
|
true : this.mousedown(evt);
|
138
|
},
|
139
|
|
140
|
/**
|
141
|
* Method: touchmove
|
142
|
* Handle touchmove events. We just prevent the browser default behavior,
|
143
|
* for Android Webkit not to select text when moving the finger after
|
144
|
* selecting a feature.
|
145
|
*
|
146
|
* Parameters:
|
147
|
* evt - {Event}
|
148
|
*/
|
149
|
touchmove: function(evt) {
|
150
|
OpenLayers.Event.preventDefault(evt);
|
151
|
},
|
152
|
|
153
|
/**
|
154
|
* Method: mousedown
|
155
|
* Handle mouse down. Stop propagation if a feature is targeted by this
|
156
|
* event (stops map dragging during feature selection).
|
157
|
*
|
158
|
* Parameters:
|
159
|
* evt - {Event}
|
160
|
*/
|
161
|
mousedown: function(evt) {
|
162
|
// Feature selection is only done with a left click. Other handlers may stop the
|
163
|
// propagation of left-click mousedown events but not right-click mousedown events.
|
164
|
// This mismatch causes problems when comparing the location of the down and up
|
165
|
// events in the click function so it is important ignore right-clicks.
|
166
|
if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
|
167
|
this.down = evt.xy;
|
168
|
}
|
169
|
return this.handle(evt) ? !this.stopDown : true;
|
170
|
},
|
171
|
|
172
|
/**
|
173
|
* Method: mouseup
|
174
|
* Handle mouse up. Stop propagation if a feature is targeted by this
|
175
|
* event.
|
176
|
*
|
177
|
* Parameters:
|
178
|
* evt - {Event}
|
179
|
*/
|
180
|
mouseup: function(evt) {
|
181
|
this.up = evt.xy;
|
182
|
return this.handle(evt) ? !this.stopUp : true;
|
183
|
},
|
184
|
|
185
|
/**
|
186
|
* Method: click
|
187
|
* Handle click. Call the "click" callback if click on a feature,
|
188
|
* or the "clickout" callback if click outside any feature.
|
189
|
*
|
190
|
* Parameters:
|
191
|
* evt - {Event}
|
192
|
*
|
193
|
* Returns:
|
194
|
* {Boolean}
|
195
|
*/
|
196
|
click: function(evt) {
|
197
|
return this.handle(evt) ? !this.stopClick : true;
|
198
|
},
|
199
|
|
200
|
/**
|
201
|
* Method: mousemove
|
202
|
* Handle mouse moves. Call the "over" callback if moving in to a feature,
|
203
|
* or the "out" callback if moving out of a feature.
|
204
|
*
|
205
|
* Parameters:
|
206
|
* evt - {Event}
|
207
|
*
|
208
|
* Returns:
|
209
|
* {Boolean}
|
210
|
*/
|
211
|
mousemove: function(evt) {
|
212
|
if (!this.callbacks['over'] && !this.callbacks['out']) {
|
213
|
return true;
|
214
|
}
|
215
|
this.handle(evt);
|
216
|
return true;
|
217
|
},
|
218
|
|
219
|
/**
|
220
|
* Method: dblclick
|
221
|
* Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
|
222
|
*
|
223
|
* Parameters:
|
224
|
* evt - {Event}
|
225
|
*
|
226
|
* Returns:
|
227
|
* {Boolean}
|
228
|
*/
|
229
|
dblclick: function(evt) {
|
230
|
return !this.handle(evt);
|
231
|
},
|
232
|
|
233
|
/**
|
234
|
* Method: geometryTypeMatches
|
235
|
* Return true if the geometry type of the passed feature matches
|
236
|
* one of the geometry types in the geometryTypes array.
|
237
|
*
|
238
|
* Parameters:
|
239
|
* feature - {<OpenLayers.Vector.Feature>}
|
240
|
*
|
241
|
* Returns:
|
242
|
* {Boolean}
|
243
|
*/
|
244
|
geometryTypeMatches: function(feature) {
|
245
|
return this.geometryTypes == null ||
|
246
|
OpenLayers.Util.indexOf(this.geometryTypes,
|
247
|
feature.geometry.CLASS_NAME) > -1;
|
248
|
},
|
249
|
|
250
|
/**
|
251
|
* Method: handle
|
252
|
*
|
253
|
* Parameters:
|
254
|
* evt - {Event}
|
255
|
*
|
256
|
* Returns:
|
257
|
* {Boolean} The event occurred over a relevant feature.
|
258
|
*/
|
259
|
handle: function(evt) {
|
260
|
if(this.feature && !this.feature.layer) {
|
261
|
// feature has been destroyed
|
262
|
this.feature = null;
|
263
|
}
|
264
|
var type = evt.type;
|
265
|
var handled = false;
|
266
|
var previouslyIn = !!(this.feature); // previously in a feature
|
267
|
var click = (type == "click" || type == "dblclick" || type == "touchstart");
|
268
|
this.feature = this.layer.getFeatureFromEvent(evt);
|
269
|
if(this.feature && !this.feature.layer) {
|
270
|
// feature has been destroyed
|
271
|
this.feature = null;
|
272
|
}
|
273
|
if(this.lastFeature && !this.lastFeature.layer) {
|
274
|
// last feature has been destroyed
|
275
|
this.lastFeature = null;
|
276
|
}
|
277
|
if(this.feature) {
|
278
|
if(type === "touchstart") {
|
279
|
// stop the event to prevent Android Webkit from
|
280
|
// "flashing" the map div
|
281
|
OpenLayers.Event.preventDefault(evt);
|
282
|
}
|
283
|
var inNew = (this.feature != this.lastFeature);
|
284
|
if(this.geometryTypeMatches(this.feature)) {
|
285
|
// in to a feature
|
286
|
if(previouslyIn && inNew) {
|
287
|
// out of last feature and in to another
|
288
|
if(this.lastFeature) {
|
289
|
this.triggerCallback(type, 'out', [this.lastFeature]);
|
290
|
}
|
291
|
this.triggerCallback(type, 'in', [this.feature]);
|
292
|
} else if(!previouslyIn || click) {
|
293
|
// in feature for the first time
|
294
|
this.triggerCallback(type, 'in', [this.feature]);
|
295
|
}
|
296
|
this.lastFeature = this.feature;
|
297
|
handled = true;
|
298
|
} else {
|
299
|
// not in to a feature
|
300
|
if(this.lastFeature && (previouslyIn && inNew || click)) {
|
301
|
// out of last feature for the first time
|
302
|
this.triggerCallback(type, 'out', [this.lastFeature]);
|
303
|
}
|
304
|
// next time the mouse goes in a feature whose geometry type
|
305
|
// doesn't match we don't want to call the 'out' callback
|
306
|
// again, so let's set this.feature to null so that
|
307
|
// previouslyIn will evaluate to false the next time
|
308
|
// we enter handle. Yes, a bit hackish...
|
309
|
this.feature = null;
|
310
|
}
|
311
|
} else if(this.lastFeature && (previouslyIn || click)) {
|
312
|
this.triggerCallback(type, 'out', [this.lastFeature]);
|
313
|
}
|
314
|
return handled;
|
315
|
},
|
316
|
|
317
|
/**
|
318
|
* Method: triggerCallback
|
319
|
* Call the callback keyed in the event map with the supplied arguments.
|
320
|
* For click and clickout, the <clickTolerance> is checked first.
|
321
|
*
|
322
|
* Parameters:
|
323
|
* type - {String}
|
324
|
*/
|
325
|
triggerCallback: function(type, mode, args) {
|
326
|
var key = this.EVENTMAP[type][mode];
|
327
|
if(key) {
|
328
|
if(type == 'click' && this.up && this.down) {
|
329
|
// for click/clickout, only trigger callback if tolerance is met
|
330
|
var dpx = Math.sqrt(
|
331
|
Math.pow(this.up.x - this.down.x, 2) +
|
332
|
Math.pow(this.up.y - this.down.y, 2)
|
333
|
);
|
334
|
if(dpx <= this.clickTolerance) {
|
335
|
this.callback(key, args);
|
336
|
}
|
337
|
// we're done with this set of events now: clear the cached
|
338
|
// positions so we can't trip over them later (this can occur
|
339
|
// if one of the up/down events gets eaten before it gets to us
|
340
|
// but we still get the click)
|
341
|
this.up = this.down = null;
|
342
|
} else {
|
343
|
this.callback(key, args);
|
344
|
}
|
345
|
}
|
346
|
},
|
347
|
|
348
|
/**
|
349
|
* Method: activate
|
350
|
* Turn on the handler. Returns false if the handler was already active.
|
351
|
*
|
352
|
* Returns:
|
353
|
* {Boolean}
|
354
|
*/
|
355
|
activate: function() {
|
356
|
var activated = false;
|
357
|
if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
|
358
|
this.moveLayerToTop();
|
359
|
this.map.events.on({
|
360
|
"removelayer": this.handleMapEvents,
|
361
|
"changelayer": this.handleMapEvents,
|
362
|
scope: this
|
363
|
});
|
364
|
activated = true;
|
365
|
}
|
366
|
return activated;
|
367
|
},
|
368
|
|
369
|
/**
|
370
|
* Method: deactivate
|
371
|
* Turn off the handler. Returns false if the handler was already active.
|
372
|
*
|
373
|
* Returns:
|
374
|
* {Boolean}
|
375
|
*/
|
376
|
deactivate: function() {
|
377
|
var deactivated = false;
|
378
|
if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
|
379
|
this.moveLayerBack();
|
380
|
this.feature = null;
|
381
|
this.lastFeature = null;
|
382
|
this.down = null;
|
383
|
this.up = null;
|
384
|
this.map.events.un({
|
385
|
"removelayer": this.handleMapEvents,
|
386
|
"changelayer": this.handleMapEvents,
|
387
|
scope: this
|
388
|
});
|
389
|
deactivated = true;
|
390
|
}
|
391
|
return deactivated;
|
392
|
},
|
393
|
|
394
|
/**
|
395
|
* Method: handleMapEvents
|
396
|
*
|
397
|
* Parameters:
|
398
|
* evt - {Object}
|
399
|
*/
|
400
|
handleMapEvents: function(evt) {
|
401
|
if (evt.type == "removelayer" || evt.property == "order") {
|
402
|
this.moveLayerToTop();
|
403
|
}
|
404
|
},
|
405
|
|
406
|
/**
|
407
|
* Method: moveLayerToTop
|
408
|
* Moves the layer for this handler to the top, so mouse events can reach
|
409
|
* it.
|
410
|
*/
|
411
|
moveLayerToTop: function() {
|
412
|
var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
|
413
|
this.layer.getZIndex()) + 1;
|
414
|
this.layer.setZIndex(index);
|
415
|
|
416
|
},
|
417
|
|
418
|
/**
|
419
|
* Method: moveLayerBack
|
420
|
* Moves the layer back to the position determined by the map's layers
|
421
|
* array.
|
422
|
*/
|
423
|
moveLayerBack: function() {
|
424
|
var index = this.layer.getZIndex() - 1;
|
425
|
if (index >= this.map.Z_INDEX_BASE['Feature']) {
|
426
|
this.layer.setZIndex(index);
|
427
|
} else {
|
428
|
this.map.setLayerZIndex(this.layer,
|
429
|
this.map.getLayerIndex(this.layer));
|
430
|
}
|
431
|
},
|
432
|
|
433
|
CLASS_NAME: "OpenLayers.Handler.Feature"
|
434
|
});
|