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/
Related
I have a project where I am using the vis.js timeline module as a type of image carousel where I have a start and an end time, plot the events on the timeline, and cycle through them automatically and show the image attached to each event in another container.
I already have this working and use something similar to the following to accomplish this, except one part:
var container = document.getElementById('visualization');
var data = [1,2,3,4,5];
var timeline = new vis.Timeline(container, data);
timeline.on('select', function (properties) {
// do some cool stuff
}
var i = 0;
(function timelapseEvents(i) {
setTimeout(function(){
timeline.setSelection(data[i], {focus: true, animation:true});
if (i < data.length - 1) {
timelapseEvents(i+1);
}
}, 2000);
})(i)
The timeline.setSelection() part above works, the timeline event is selected and focused on. However, the "select" event is NOT triggered. This is verified as working as expected in the documentation (under Events > timeline.select) where it says: Not fired when the method timeline.setSelection() is executed.
So my question is, does anyone know how to use the timeline.setSelection() method and actually trigger the select event? Seems unintuitive to me to invoke the timeline.setSelection()method and not actually trigger the select event.
Spent a few hours on this and came up short. I ended up just taking the code I had in my timeline.on('select', function (properties) { block and turning it into a function and calling it after the timeline.setSelection() call.
Basically, I didn't fix the issue but worked around it. Will keep an eye on this in case anyone actually is able to figure out how to add the select() event to the setSelection() method.
iOS doesn't allow Web Audio to be used without a user input to trigger it. To get around this, we have a touchend listener that does this:
initAudio: function () {
// create a blank buffer
var buffer = this._atx.createBuffer(1, 1, 22050); // this._atx is the AudioContext
var node = this._atx.createBufferSource();
node.buffer = buffer;
node.start(0); // or noteOn if the browser doesn't support this, removed that check/code for brevity
}
This is working fine in most cases. We have an overlay over the game, which intercepts the first click, and calls the above function. At the end of the function, this._atx.state is "running" (it is "suspended" before the node.start(0) call).
However, if the callback is triggered by a quick swipe on the screen, rather than a tap, it goes through this code, and at the end of the function, the state is still "suspended". It does exactly the same code both times, the only difference is the nature of the user input.
This is the code that adds the listeners:
this._boundSoundTapped = this._onSoundTapped.bind(this);
this._confirmPopup.addEventListener("touchend", this._boundSoundTapped);
this._confirmPopup.addEventListener("click", this._boundSoundTapped);
And the onSoundTapped function:
_onSoundTapped: function(e){
e.stopPropagation();
e.preventDefault();
if(this._soundsPressed === false) {
this._confirmPopup.removeEventListener("touchend", this._boundSoundTapped);
this._confirmPopup.removeEventListener("click", this._boundSoundTapped);
this._soundsPressed = true;
this._player.initAudio();
}
},
I really can't see why the swipe rather than the click would have a different effect, they both trigger touchend, and the same code gets executed either way.
I have been trying to use the WebSpeech Api through angularjs. Everything seems to work but the model doesn't get updated at once.
If I start the recognition again, the model updates. Seems like some inner loop/other construct is holding angular to see the changes.
Here is the codepen that I made.
Steps to reproduce:
1. Click start, and speak
2. After recognition detects end of speech hit start once again to start another recognition.
3. As soon as the second recognition is started, the model is updated with previous transcript.
Note: If a do console.log as below then it shows correct transcript, means the recognition part is working fine.
if(event.results[i].isFinal) {
self.final = self.final.concat(event.results[i][0].transcript);
console.log(event.results[i][0].transcript);
}
Everything seems perfect, except you forgot to call $scope.$apply(); when you modified values to get it effect on view. So it should be like this,
angular.module('speech',[]);
angular.module('speech').controller('speechController', function($scope) {
this.rec = new webkitSpeechRecognition();
this.interim = [];
this.final = '';
var self = this;
this.rec.continuous = false;
this.rec.lang = 'en-US';
this.rec.interimResults = true;
this.rec.onerror = function(event) {
console.log('error!');
};
this.start = function() {
self.rec.start();
};
this.rec.onresult = function(event) {
for(var i = event.resultIndex; i < event.results.length; i++) {
if(event.results[i].isFinal) {
self.final = self.final.concat(event.results[i][0].transcript);
console.log(event.results[i][0].transcript);
$scope.$apply();
} else {
self.interim.push(event.results[i][0].transcript);
}
}
};
});
I have updated your codepen with working solution.
AngularJs creates a "watch" internally for the all data-bindings created in view and call $scope.$digest() which inturns iterate thorugh all watches and checks if any of the watched variables have changed. When you call $scope.$apply() it internally calls $scope.$digest() so data-binding gets refreshed.
Listener directives, such as ng-click, register a listener with the DOM. When the DOM listener fires, the directive executes the associated expression and updates the view using the $apply() method.
When an external event (such as a user action, timer or XHR) is received, the associated expression must be applied to the scope through the $apply() method so that all listeners are updated correctly (ref).
So in your case view gets update when you click next start button again (ng-click) and not when recording event occurs.
Also would be usefult to read this
I'm writing an app in Parse, using the JavaScript framework. In my view, I have a link with the class 'new-page'. In the JS code, I have:
events: {
"click .new-page" : "createPage",
}
createPage is:
createReel: function() {
var self = this;
// Get current pagelist
var pages= new PageList;
pages.query = new Parse.Query(Page);
pages.query.equalTo("owner", Parse.User.current());
pages.query.ascending("order");
pages.fetch({
success: function(pagelist) {
var newPage = new Page;
newPage .save({
success: function(newpage) {
// Redirect to page edit
new PageEditView();
}
});
}
});
}
First time round, this works fine - the new page is created, and it goes to edit mode for that page. But if I then go back to the view with the 'Add Page' button and click it again, I get 2 new pages. If I do it again, I get 4, and so on.
I assume the event is 'building up', so that the more times the button is clicked, the more times the event gets fired.
I'm not sure where to start looking.
Late answer, but I just had a similar issue.
The mistake I made was that I created the view instance again every time I needed it.
That is not how Backbone is ment to work.
Views are (usually) created once. And when their model changes, the are being rendered again.
you seem to be instantiating your view every time the user saves at this line
PageEditView();
That's when the events stack up.
Instantiate that view once when you start up the page and then render it whenever you update the model for that view.
In native bakbone, event handlers can listen to model changes. Somehow it won't work in the Parse backbone version (or I can't get it to work). So for now, I do that manually with
PageEditView.model = model
PageEditView.render();
Make sure you undelegate events before calling the new PageEditView.
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