I am working with a map that uses a large set of vector features. In some browsers, there is significant lag when the OpenLayers is handling pointermove events interactions. For example:
function selectOnHover(map, handler, styleFn) {
var selectMove = new ol.interaction.Select({
condition: ol.events.condition.pointerMove,
style: styleFn
});
map.addInteraction(selectMove);
selectMove.on('select', handler);
}
In other situations that handle continuous input (e.g. handling scroll events) and require significant processing, I would normally debounce the handler for the event - so that significant work is only done when the input has paused (in this case, determining the intersecting features). Is there a way to insert a debounce between browser event dispatch and OpenLayers checking intersections without circumventing OpenLayers interaction handling?
I've tried handling the pointermove/mousemove events directly, debouncing them (redispatching manually created synthetic events) and then using the interaction's condition to handle only the synthetic ones. This worked except that Internet Explorer's synthetic events weren't picked up by OpenLayers.
I'm considering circumventing OpenLayers interaction - for example by using forEachFeatureAtPixel and manually updating the style.
In fact, even using the standard API you can wrap a select interaction into a custom interaction and debounce the handleEvent function:
var app = {};
app.DebounceSelect = function() {
this.selectInteraction = new ol.interaction.Select({
condition: ol.events.condition.pointerMove
});
var handleEventDebounce = debounce(function(evt) {
return ol.interaction.Select.handleEvent.call(this.selectInteraction, evt);
}.bind(this), 100);
ol.interaction.Interaction.call(this, {
handleEvent: function(evt) {
handleEventDebounce(evt);
// always return true so that other interactions can
// also process the event
return true;
}
});
};
ol.inherits(app.DebounceSelect, ol.interaction.Interaction);
app.DebounceSelect.prototype.setMap = function(map) {
this.selectInteraction.setMap(map);
};
var select = new app.DebounceSelect();
map.addInteraction(select);
http://jsfiddle.net/n9nbrye8/3/
For reference an example on how to write custom interactions: http://openlayers.org/en/master/examples/custom-interactions.html
And the documentation for ol.interaction.Select.handleEvent
Related
I developed a Javascript web application using dojo and the ESRI Javascript API. The main page of the application is a map view where the user can add points on the map.
On my desktop web browser, when I click the map a single new point is added and if I debug I can see that my onClick handler is called only once.
On my iPad, when I tap the map 2 points are added in the exact same location. When I debug the app on the iPad via Safari on my Macbook Pro I can see that the onClick handler is being called twice. Upon further debugging, I have made sure that the code that creates my onClick handler is only being called once.
startEditing : function(template) {
main.selectHandle.pause();
main.moveHandle.pause();
var drawingTool = template.template.drawingTool;
switch(drawingTool) {
case FeatureTemplate.TOOL_POINT:
drawingTool = Draw.POINT;
break;
}
this.drawEndHandle = on(this.drawingToolbar, "draw-end", lang.hitch(this, this.createFeature, template));
this.drawingToolbar.activate(drawingTool);
},
stopEditing : function() {
this.drawingToolbar.deactivate();
this.drawEndHandle.remove();
main.selectHandle.resume();
main.moveHandle.resume();
},
createFeature : function(template, evt) {
var featureLayer = template.featureLayer;
template = template.template;
var prototype = template.prototype;
var geometry = evt.geometry;
var graphic = new Graphic(prototype.toJson());
graphic.setGeometry(geometry);
this.initAttributes(graphic, featureLayer).then(function() {
var features = [graphic];
featureLayer.applyEdits(features).then(function(addResults) {
var objectIds = array.map(addResults, function(addResult) {
return addResult.objectId;
});
var q = new Query();
q.objectIds = objectIds;
featureLayer.selectFeatures(q).then(function(features) {
main.openForm(features);
});
});
});
},
The drawingToolbar in the startEditing function above is provided by the ESRI Javascript API, but handles the onClick event internally and passes it onto the onDrawEnd event that I am handling in my code. I have other code that handles the onClick event directly and it also fires twice.
UPDATE
I just tested the same functionality on my Android smartphone and it is also firing the onClick event twice with a single tap.
I have the exact same issue in an application I am building. I am also using the ESRI Javascript API.
I feel like there must be an event handler in either the map, the feature layer, or one of the dijit containers that passes along a tap event differently than the click.
In the end, I just debounced my click handler as described here: http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
I'm working on the plugin flot.touch.js which add touch interactivity (pan and zoom) on the chart for webkit browsers.
I want to make it work on IE10 too but I don't know how to retrieve the space between my touch points (I need this for calculate the scale).
On webkit browsers, we can do this by using these variables :
evt.originalEvent.touches[0].pageX
evt.originalEvent.touches[0].pagey
evt.originalEvent.touches[1].pageX
evt.originalEvent.touches[1].pageY
With IE's pointer events, a separate event is fired for each touch point. Unlike iOS touch events (as implemented by other browsers too), the state of each "pointer" is tracked separately. Consider it a more generic event that groups several pointer-based input devices.
Each event object is given a pointerId property that can be used to track its state. To track multiple touches, you'll need to store that pointerId along with any other variables in an object outside the scope of the event handler's function, along with any other data you might need. For example:
var pointers = {};
function pointerDown(evt) {
if (evt.preventManipulation)
evt.preventManipulation();
pointers[evt.pointerId] = [evt.PageX, evt.PageY];
for (var k in pointers) {
// loop over your other active pointers
}
}
function pointerUp(evt) {
delete pointers[evt.pointerId];
}
Further reading:
IEBlog - Handling Multi-touch and Mouse Input in All Browsers
Like Andy E said. Track how many pointers you have on your screen and store properties from each of them (in your case x and y coordinates, acquired form event)
A nice example of multitouch can be found here: http://ie.microsoft.com/testdrive/Graphics/TouchEffects/ and you can go into the code with F12 tools and get all the code under the Script tag: http://ie.microsoft.com/testdrive/Graphics/TouchEffects/Demo.js
There you can find this part of code:
function addTouchPoint(e) {
if(touchCount == 0) {
document.addEventListener(moveevent, moveTouchPoint, false);
}
var pID = e.pointerId || 0;
if(!touchPoints[pID]) {
touchCount++;
touchPoints[pID] = {x : e.clientX, y : e.clientY};
}
}
Hope it helps
If you're on a Windows 8 platform, IE10 supports the MSGesture object. This object can be used like the iOS version of gesture events. To initialize the object, we have to set the target object as well as add the MSPointer on MSPointerDown. For example:
var myGesture = new MSGesture();
var myElement = document.getElementById("MyCanvas");
myGesture.target = myElement; //sets target
myElement.addEventListener("MSPointerDown", function (event){
redGesture.addPointer(evt.pointerId); // adds pointer to the MSGesture object
}, false);
From here, you can add an event listener for the MSGestureChange function to process the event.scale property. Note that if the target is not set, an InvalidStateError exception will occur.
In Ember guides:
http://emberjs.com/guides/view_layer/
below the table of built-in events, an example was given of adding custom events:
App = Ember.Application.create({
customEvents: {
// add support for the loadedmetadata media
// player event
'loadedmetadata': "loadedMetadata"
}
});
However, I am still a bit confused as to how exactly to implement a custom event. For example, if I want to map a custom event 'leftArrow' to keycode 37, how would I name it ?
For your kind of event, I wouldn't class it as a 'custom event'. Custom events are types of events that are supported natively by the browser or by jQuery that have not been implemented yet in Ember.
What you want to achieve already exists as a 'key' event and you just need to capture the keycode and respond accordingly. For example:
App.SomeView = Em.View.extend({
keyDown: function (e) {
if (e.keycode === 37) {
// do your stuff or call a function
}
}
});
The application create constructor is not the place to do this, and you will want to handle the key events within your views or controllers depending on what you want to do. Take a look at the section on the EmberJs doc page just above custom events. Those are the events you can handle.
I am trying to simulate keypresses in a web application, it is for an embedded system but it uses a Webkit derived browser. I have tested the code in Chrome and get the same error.
I tried to use code snippets from this example from Yahoo, but I keep getting the same error when firing the event using dispatchEvent. "target" is an HTML element in the DOM tree.
function fireEvent(target) {
var evt = document.createEvent("UIEvent");
evt.initEvent("keypress", true, true);
target.dispatchEvent(evt);
}
It always throws:
"Error: UNSPECIFIED_EVENT_TYPE_ERR: DOM Events Exception 0"
I have tried createEvent("Events") as well and it always boils down to the same exception, both on the embedded system and in Chrome.
Ok, when doing further testing, it seemed that when all key event parameters was properly initialised, then dispatchEvent worked without fireing an exception.
The following code works.
function fireEvent(target) {
var evt = document.createEvent("Events");
evt.initEvent("keypress", true, true);
evt.view = window;
evt.altKey = false;
evt.ctrlKey = false;
evt.shiftKey = false;
evt.metaKey = false;
evt.keyCode = 0;
evt.charCode = 'a';
target.dispatchEvent(evt);
}
Keypress is an UIEvent. You should use initUIEvent( 'type', bubbles, cancelable, windowObject, detail ) rather than initEvent(). But for firefox, which implements a keyEvents, you should create a KeyEvents and initKeyEvents().
This one is old thread, just to update it I am adding another answer so that it makes more sense to any one.
initEvent() is deprecated It is still supported in some browsers but avoid using it.
There is better concise way to create events like this
function fireEvent(target) {
var event = new Event('build');
// Listen for the event.
target.addEventListener('build', function (e) { ... }, false);
// Dispatch the event.
target.dispatchEvent(event);
}
To add more data to the event object, the CustomEvent interface exists and the detail property can be used to pass custom data.
For example, the event could be created as follows:
var event = new CustomEvent('build', { 'detail': target.dataset.time });
Reference: Creating and Triggering Events
Are there any Event Driven Architecture jQuery plugins?
Step 1: Subscribing
The subscribers subscribe to the event handler in the middle, and pass in a callback method, as well as the name of the event they are listening for...
i.e. The two green subscribers will be listening for p0 events. And the blue subscriber will be listening for p1 events.
Step 2: The p0 event is fired by another component to the Event Handler
A p0 event is fired to the Event Handler
The event handler notifies it's subscribers of the event, calling the callback methods they specified when they subscribed in Step 1: Subscribing.
Note that the blue subscriber is not notified because it was not listening for p0 events.
Step 3: The p1 event is fired a component to the Event Handler
The p1 event is fired by another component
Just as before except that now the blue subscriber receives the event through its callback and the other two green subscribers do not receive the event.
Images by leeand00, on Flickr
I can't seem to find one, but my guess is that they just call it something else in Javascript/jquery
Also is there a name for this pattern? Because it isn't just a basic publisher/subscriber, it has to be called something else I would think.
You probably don't need a plugin to do this. First of all, the DOM itself is entirely event driven. You can use event delegation to listen to all events on the root node (a technique that jQuery live uses). To handle custom events as well that may not be DOM related, you can use a plain old JavaScript object to do the job. I wrote a blog post about creating a central event dispatcher in MooTools with just one line of code.
var EventBus = new Class({Implements: Events});
It's just as easy to do in jQuery too. Use a regular JavaScript object that acts as a central broker for all events. Any client object can publish and subscribe to events on this object. See this related question.
var EventManager = {
subscribe: function(event, fn) {
$(this).bind(event, fn);
},
unsubscribe: function(event, fn) {
$(this).unbind(event, fn);
},
publish: function(event) {
$(this).trigger(event);
}
};
// Your code can publish and subscribe to events as:
EventManager.subscribe("tabClicked", function() {
// do something
});
EventManager.publish("tabClicked");
EventManager.unsubscribe("tabClicked");
Or if you don't care about exposing jQuery, then simply use an empty object and call bind and trigger directly on the jQuery wrapped object.
var EventManager = {};
$(EventManager).bind("tabClicked", function() {
// do something
});
$(EventManager).trigger("tabClicked");
$(EventManager).unbind("tabClicked");
The wrappers are simply there to hide the underlying jQuery library so you can replace the implementation later on, if need be.
This is basically the Publish/Subscribe or the Observer pattern, and some good examples would be Cocoa's NSNotificationCenter class, EventBus pattern popularized by Ray Ryan in the GWT community, and several others.
Though not a jQuery plugin, Twitter released a JavaScript framework called Flight which allows you to create component-based architectures, which communicate via events.
Flight is a lightweight, component-based JavaScript framework from Twitter. Unlike other JavaScript frameworks which are based around the MVC pattern, Flight maps behavior directly to DOM nodes.
Flight is agnostic to how requests are routed or which templating library you decide to use. Flight enforces strict separation of concerns. Components in Flight do not engage each other directly.
They broadcast their actions as events and those components subscribed to those events can take actions based on them. To make use of Flight, you will need the ES5-shim and jQuery along with an AMD loader.
Flight - A Lightweight, Component-Based JavaScript Framework From Twitter
There are actually two of them:
Listen (faster): http://plugins.jquery.com/project/Listen
Intercept (more advanced): http://plugins.jquery.com/project/Intercept
Could this serve as a ligthweight message passing framework?
function MyConstructor() {
this.MessageQueues = {};
this.PostMessage = function (Subject) {
var Queue = this.MessageQueues[Subject];
if (Queue) return function() {
var i = Queue.length - 1;
do Queue[i]();
while (i--);
}
}
this.Listen = function (Subject, Listener) {
var Queue = this.MessageQueues[Subject] || [];
(this.MessageQueues[Subject] = Queue).push(Listener);
}
}
then you could do:
var myInstance = new MyConstructor();
myInstance.Listen("some message", callback());
myInstance.Listen("some other message", anotherCallback());
myInstance.Listen("some message", yesAnotherCallback());
and later:
myInstance.PostMessage("some message");
would dispatch the queues
This can easily be accomplished using a dummy jQuery node as a dispatcher:
var someModule = (function ($) {
var dispatcher = $("<div>");
function init () {
_doSomething();
}
/**
#private
*/
function _doSomething () {
dispatcher.triggerHandler("SOME_CUSTOM_EVENT", [{someEventProperty: 1337}]);
}
return {
dispatcher: dispatcher,
init: init
}
}(jQuery));
var someOtherModule = (function ($) {
function init () {
someModule.dispatcher.bind("SOME_CUSTOM_EVENT", _handleSomeEvent)
}
/**
#private
*/
function _handleSomeEvent (e, extra) {
console.log(extra.someEventProperty) //1337
}
return {
init: init
}
}(jQuery));
$(function () {
someOtherModule.init();
someModule.init();
})
A recent development is msgs.js "Message oriented programming for JavaScript. Inspired by Spring Integration". It also supports communication via WebSockets.
msgs.js applies the vocabulary and patterns defined in the 'Enterprise Integration Patterns' book to JavaScript extending messaging oriented programming into the browser and/or server side JavaScript. Messaging patterns originally developed to integrate loosely coupled disparate systems, apply just as well to loosely coupled modules within a single application process.
[...]
Tested environments:
Node.js (0.6, 0.8)
Chrome (stable)
Firefox (stable, ESR, should work in earlier versions)
IE (6-10)
Safari (5, 6, iOS 4-6, should work in earlier versions)
Opera (11, 12, should work in earlier versions)
I have used the OpenAjax Hub for its publish/subscribe services. It's not a jQuery plugin, but a standalone JavaScript module. You can download and use the reference implementation from SourceForge. I like the hierarchical topic naming and the support for subscribing to multiple topics using wildcard notation.