Project

General

Profile

Download (13.5 KB) Statistics
| Branch: | Tag: | Revision:
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
});
(4-4/12)