How to execute some code whenever the user interacts with the map? - openlayers-3

I have an OpenLayers 3.9.0 map. I also have a pair of LonLat coordinates that I am tracking from an external source and updating onto the map. I am continuously re-centering the map on these coordinates:
function gets_called_when_I_have_updated_coords() {
map.getView.setCenter(coords);
}
What I want is to disable this auto-centering whenever the user interacts with the map. In other words, I want this:
var auto_center = true;
function gets_called_when_I_have_updated_coords() {
if (auto_center) {
map.getView.setCenter(coords);
}
}
function user_started_interacting() {
auto_center = false;
}
// But where should this function be attached to?
// where.on('what?', user_started_interacting);
I don't know how to detect a user interaction.
I expected that the default interactions had some kind of event, so that when the user starts dragging/rotating/zooming the map, an event would be triggered and my code would run. I could not find such event.

Here, the user is dragging:
map.on('pointermove', function(evt){
if(evt.dragging){
//user interacting
console.info('dragging');
}
});
Here, the user is changing resolution:
map.getView().on('change:resolution', function(evt){
console.info(evt);
});
UPDATE
Some options to detect keyboard interaction:
//unofficial && undocumented
map.on('key', function(evt){
console.info(evt);
console.info(evt.originalEvent.keyIdentifier);
});
//DOM listener
map.getTargetElement().addEventListener('keydown', function(evt){
console.info(evt);
console.info(evt.keyIdentifier);
});
A fiddle to test.

An other approach could be to listen to the pointerdown and pointerup events occurring on the map and while the down is in action, disable your auto-center feature.
map.on('pointerdown', function() {
auto_center = false;
}, this);
map.on('pointerup', function() {
auto_center = true;
}, this);
This approach may need more work, but it would be a start. Thoughts ?

As an workaround, I can attach an event to a change in the view:
map.getView().on('change:center', function(ev){…});
But I must take extra steps to distinguish from code-initiated changes from user-initiated ones. The complete code looks somewhat like this:
var auto_center = true;
var ignore_change_events = false;
function gets_called_when_I_have_updated_coords() {
if (auto_center) {
ignore_change_events = true;
map.getView.setCenter(coords);
ignore_change_events = false;
}
}
map.getView().on('change:center', function() {
if (ignore_change_events) {
return;
}
auto_center = false;
});
Note that this approach breaks if any animation is used (and there is no callback or event for when an animation finishes).
Depending on your project, you may want to try either this solution or Jonatas Walker's solution.

Related

How to play a MovieClip x times

The MovieClip class in the EaselJS module has a loop property which can be set to true or false, causing the movie clip to play infinitely or only once. http://www.createjs.com/docs/easeljs/classes/MovieClip.html
I need to play a movie clip (banner ad) three times. How can that be done?
This is the init function:
<script>
var canvas, stage, exportRoot;
function init() {
// --- write your JS code here ---
canvas = document.getElementById("canvas");
images = images||{};
var loader = new createjs.LoadQueue(false);
loader.addEventListener("fileload", handleFileLoad);
loader.addEventListener("complete", handleComplete);
loader.loadManifest(lib.properties.manifest);
}
function handleFileLoad(evt) {
if (evt.item.type == "image") { images[evt.item.id] = evt.result; }
}
function handleComplete(evt) {
exportRoot = new lib.banner_728x90();
stage = new createjs.Stage(canvas);
stage.addChild(exportRoot);
stage.update();
stage.enableMouseOver();
createjs.Ticker.setFPS(lib.properties.fps);
createjs.Ticker.addEventListener("tick", stage);
}
</script>
There is currently no loop count support, nor any events from MovieClip indicating when an animation finishes. The latter isn't a bad idea, feel free to log a bug in GitHub.
One solution you could use would be to dispatch some custom events from a timeline script in Animate:
this.dispatchEvent("walkend");
Then you can listen for the event, and handle it yourself.
var loopCount = 0;
exportRoot.myClip.on("walkend", function(event) {
loopCount++;
if (loopCount > 2) {
doSomething();
event.remove(); // No longer get this event.
}
});
Hope that helps.

Blank Firefox addon panel page with multiple windows

I've followed MDN's document to create a toggle button addon.
Everything works fine except one problem:
Open a second browser window (cmd+n or ctrl+n) and click on the toggle button there
Click on the toggle button on the original browser window without closing the toggle button on the second window
the toggle button's panel becomes blank with the following error message:
JavaScript error: resource:///modules/WindowsPreviewPerTab.jsm, line 406: NS_ERR
OR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIT
askbarTabPreview.invalidate]
// ./lib/main.js
var { ToggleButton } = require("sdk/ui/button/toggle");
var panels = require("sdk/panel");
var self = require("sdk/self");
var buttonIndex = 0;
var lastKnownButtonIndex = 0;
var button = ToggleButton({
id: "button",
label: "my button",
icon: {
"16": "./icon-16.png"
},
onClick: handleChange,
});
var panel = panels.Panel({
contentURL: self.data.url("menu.html"),
onHide: handleHide
});
function handleChange(state) {
if (state.checked) {
panel.show({
position: button
});
}
}
function handleHide() {
button.state('window', {checked: false});
}
function assignButtonIndex() {
return (buttonIndex++).toString();
}
The complete addon is here: https://goo.gl/9N3jle
To reproduce: Extract the zip file and $ cd testButton; cfx run and follow the above steps.
I really hope someone can help me with this. Thank you in advance!
It's a bug; you're not doing anything wrong. It's a racing condition between the windows' focus events, and the panel's event, that prevent somehow the panel's hidden event to be emitted properly.
You can try to mitigate with a workaround the issue until is properly fixed. I added some explanation in the bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1174425#c2 but in short, you can try to add a setTimeout to delay a bit when the panel is shown, in order to avoid the racing condition with the window's focus. Something like:
const { setTimeout } = require("sdk/timers");
/* ... your code here ... */
function handleChange(state) {
if (state.checked) {
setTimeout(() => panel.show({ position: button }), 100);
}
}
I am currently using a workaround where I dynamically create a new Panel every time the user presses the toolbar button.
It is faster than the 100ms workaround and also handles a scenario where the user outright closes one of the browser windows while the panel is open. (The 100ms workaround fails in this case and a blank panel is still displayed.)
It works like this:
let myPanel = null;
const toolbarButton = ToggleButton({
...,
onChange: function (state) {
if (state.checked) {
createPanel();
}
}
});
function createPanel(){
// Prevent memory leaks
if(myPanel){
myPanel.destroy();
}
// Create a new instance of the panel
myPanel = Panel({
...,
onHide: function(){
// Destroy the panel instead of just hiding it.
myPanel.destroy();
}
});
// Display the panel immediately
myPanel.show();
}

How to set currentTime in video.js in Safari for iOS

Other is ok, but the setting of currentTime:
I tried some ways like:
videojs("example_video_1", {}, function() {
this.currentTime(200);
this.play();
});
And this is not working.
videojs("example_video_1").ready(function(){
this.currentTime(200);
});
This also doesn't work.
var dom = document.getElementById('example_video_1');
dom.addEventListener("loadedmetadata", function() {
dom.currentTime = 100;
}, false); // ok for no video.js
videojs(dom);
Not working. Even reverse the line of addEventListener and init videojs.
And I tried the videojs event.
var player = videojs("example_video_1");
alert(player.addEvent);
alert(player.addEventListener);
these API all not exists, so how do I bind Event in videojs?
This is how I got the video playback on a defined position at startup (it removes the problem that the video first started and only afterwards jumped to a video position):
$(document).ready(function()
{
var timecode = 120;
var initdone = false;
// wait for video metadata to load, then set time
video.on("loadedmetadata", function(){
video.currentTime(timecode);
});
// iPhone/iPad need to play first, then set the time
// events: https://www.w3.org/TR/html5/embedded-content-0.html#mediaevents
video.on("canplaythrough", function(){
if(!initdone)
{
video.currentTime(timecode);
initdone = true;
}
});
}); // END ready
Oh, by lucky, i found an example in https://github.com/videojs/video.js
var dom = document.getElementById('example_video_1');
videojs(dom, {}, function(){
this.on('loadedmetadata', function(){
this.currentTime(500);
});
});
this is work, the API to bind event is player.on('event', fn);
videojs is cool~

Stop the back history, juste close panel [duplicate]

I have a jQuery mobile panel which slides in from the side, it works great.
But lets say you have a login page, that redirects to a main page with a panel. Now if the user opens the panel, and then clicks the back button, he expects the panel to close. But instead the browser navigates back to the login page.
I´ve tried adding something to the url:
window.location.hash = "panelOpen";
But that just messes up the jQuery mobile history state pattern. I´ve also tried to listen to the navigate event, and prevent it if a panel is open:
$(window).on('navigate', function (e, hans) {
var panels = $('[data-role="panel"].ui-panel-open');
if (panels&&panels.length>0) {
e.preventDefault();
e.stopPropagation();
$('#' + panels[0].id).panel('close');
return false;
}
});
This kind of works, except that the url is changed, and I cannot grab the event that changes the url. Furthermore, it also messes up the jQuery mobile history pattern.
So how does people achieve this expected 'app-like' behaviour with a jQuery mobile panel; open panel > history back > close panel. And thats it.
Thanks alot!
Updated
Instead of retrieving current URL from jQuery Mobile's history, It is safer to retrieve it from hashchange event event.originalEvent.newURL and then pass it to popstate event to be replaceState() with that URL.
Instead of listening to navigate, listen to popstate which fires before. The trick here is manipulate both browser's history and jQuery Mobile's history by replaceState() and reload same page without transition.
var newUrl;
$(window).on("hashchange", function (e) {
/* retrieve URL */
newUrl = e.originalEvent.newURL;
}).on("popstate", function (e) {
var direction = e.historyState.direction == "back" ? true : false,
activePanel = $(".ui-panel-open").length > 0 ? true : false,
url = newUrl,
title = document.title;
if (direction && activePanel) {
$(".ui-panel-open").panel("close");
$(".ui-header .ui-btn-active").removeClass("ui-btn-active");
/* reload same page to maintain jQM's history */
$.mobile.pageContainer.pagecontainer("change", url, {
allowSamePageTransition: true
});
/* replace state to maintain browsers history */
window.history.replaceState({}, title, url);
/* prevent navigating into history */
return false;
}
});
This part is meant to maintain same transition used previously as transition is set to none when reloading same page.
$(document).on("pagebeforechange", function (e, data) {
if (data.options && data.options.allowSamePageTransition) {
data.options.transition = "none";
} else {
data.options.transition = $.mobile.defaultPageTransition;
}
});
Demo - Code
I am a little bit late on the party, but i had recently the same requirements and i would like to share how i did it. So, i extended the requirement in the original question to Panels, Popups and Pages:
...an expected 'app-like' behaviour, history back > close
whaterver is open. And thats it.
In .on("panelopen"), .on("popupafteropen") and .on("pagecontainershow") i simply add another entry to the window history, by using the HTML5 API (https://developer.mozilla.org/en-US/docs/Web/API/History_API) (I believe there is no need to use the JQM navigate browser quirks for that):
window.history.pushState({}, window.document.title, window.location.href);
After that, i'm using more or less Omar's function to intercept the popstate event:
$(window).on("popstate", function (e) {
var pageId = $(":mobile-pagecontainer").pagecontainer("getActivePage").prop("id");
var pageOpen = (pageId != "page-home");
var panelOpen = $(".ui-panel-open").length > 0;
var popupOpen = $(".ui-popup-active").length > 0;
if(pageOpen) {
$.mobile.pageContainer.pagecontainer("change", "#page-home", {reverse: true});
return false;
}
if(panelOpen) {
$(".ui-panel-open").panel("close");
return false;
}
if(popupOpen) {
$(".ui-popup-active .ui-popup").popup("close")
return false;
}
});
As you see, the is just only one level to the home-page, but this can be easily extended by using JQM history implementation to get the previous page:
var activeId = $.mobile.navigate.history.activeIndex;
var jqmHistory = $.mobile.navigate.history.stack; // array of pages
and use pagecontainer to change to the active entry - 1.
As last note, this works well also by completely disabling the built-in JQM Ajax navigation system:
/* Completely disable navigation for mobile app */
$.mobile.ajaxEnabled = false;
$.mobile.loadingMessage = false;
$.mobile.pushStateEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.changePage.defaults.changeHash = false;
$.mobile.popup.prototype.options.history = false;
(Tested in Browser, on real Android and iOS devices)

ajaxStart() on forge.request.ajax

For showing a loading screen, we listen to the jQuery ajaxStart() event the following way:
$(document).ajaxStart(function() {
//show loading screen
});
However, this event doesn't get fired sending forge.reques.ajax() requests (at least is seems so).
Is there already a solution like that for forge or do I have to write that event by hand?
This doesn't currently exist in forge, but its pretty easy to implement:
var onAjaxStart = function () {
// show loading screen
}
var onAjaxEnd = function () {
// hide loading screen
}
var myAjax = function (params) {
onAjaxStart();
var success = params.success;
params.success = function () {
onAjaxEnd();
success && success();
};
forge.request.ajax(params);
}
Then us myAjax(...) instead of forge.request.ajax(...).

Resources