How do I stop from creating an endless sequence of events? - javascript

I have a set of user preferences with a listener on change.
A few of the preferences need to interact. For instance, if I change preference A to "off" then Preference D should also be shown as "off."
I realize what I have is a simple implementation - which worked when all preferences were independent. But if the user changes Preference A, and my code checks for their choice then changes Preference D for them, then I get endless triggering of events. How do I gracefully design around this trap I've created?
$('.updateUserDetails').on('change', function (e) {
console.log('Detected change in user prefs...');
var chromeLog = $("#loggingModeAccount").is(':checked') ? 1 : 0;
var historyPosition = parseInt($("#historyPosDD").val(), 10);
if (chromeLog === 0){
console.log('Turning history OFF in Chrome...');
$("#historyPosDD").val("127");
historyPosition = 127; // Set to default
} else {
console.log('Chrome logging ON...');
$("#historyPosDD").val("3");
historyPosition = 3; // Set to default
});
Both #loggingModeAccount and #historyPosDD have the class . updateUserDetails. So when my code updates $("#historyPosDD").val based on the change to #loggingModeAccount I create an endless loop.
I've tried including e.stopPropagation(); in various places with no luck...
How do I design my way out of this? Any help/direction appreciated.

Related

How to only click a page element once in a for-loop that checks for a new HTML event?

I'm writing a userscript for a website where occasionally a coin drop will appear on-screen and only a limited number of people on the site can claim it. My script detects when a new coin drop appears based on the length of the page element "coindrop-status", and when a new drop is detected it auto-clicks the prompt to open the initial drop splash screen, then auto-clicks the actual grab button within that splash screen.
The problem is that because the first auto-click is within a for-loop, it continuously spam-clicks to open the splash screen until the drop has been fully claimed and the loop breaks, preventing stage 2 of the auto-click function from clicking the actual button to grab the drop within the splash screen.
I've tried to solve this problem many times now but because coin drops are so infrequent, it's a massive pain to debug - how can I change my script so that when a drop is detected, the splash screen is only clicked once (so that it stays open) before clicking the grab button within it repeatedly?
var newDrop = false;
function dropCheck() {
clearInterval(scanFreq);
var coinLength = document.getElementsByClassName("coindrop-status").length - 1;
for(var i = coinLength; i >= 0; i--) {
if(document.getElementsByClassName("coindrop-status")[i].innerText == "Grab") {
newDrop = true;
document.getElementsByClassName("coindrop-status")[i].click();
setTimeout(function() {document.elementFromPoint(1250, 840).click()},1000);
setTimeout(function() {document.elementFromPoint(1250, 840).click()},1000);
}
}
if(newDrop) {
newDrop = false;
setTimeout(dropCheck,800);
} else {
setTimeout(dropCheck,100);
}
}
var scanFreq = setInterval(dropCheck,800);
Admittedly, clicking the grab button multiple times is probably overkill, but I figure at least it guarantees that the coin drop actually gets grabbed.
Forgive any bad coding practice I may have integrated into this script; I'm still learning to program. I'm sure there are much, much more elegant ways to accomplish the goal of this userscript, so if you have any other suggestions please feel free to give some constructive criticism as well.
First, you don't need to loop like this. Like at all. There is a class called MutationObserver which will call a callback only when elements in the DOM changed. My approach when waiting for a specific element to be added is basically this:
/**
* Waits for a node to start to appear in DOM. Requires a CSS query to fetch it.
* #param {string} query CSS query
* #returns {Promise<HTMLElement>}
*/
function awaitElement(query) {
return new Promise((resolve, reject)=>{
const targetNode = document.body;
const testNow = targetNode.querySelector(query);
if(testNow != null) {
resolve(testNow);
return;
}
const observer = new MutationObserver((mutationList, observer) => {
const result = targetNode.querySelector(query);
if(result != null) {
observer.disconnect();
resolve(result)
}
});
observer.observe(targetNode, { attributes: false, childList: true, subtree: true });
});
}
Used as:
(async () => {
while(true) {
let myElm = await awaitElement(".coindrop-status");
// handle the drop here. Note that you must remove the element somehow, otherwise you will get an endless loop
}
})();
You will probably need to adjust my function a little, so that you can make it ignore coin drops you already handled. One way to handle this without any modification is add a custom class name to the handled coin divs, and then use the not selector when searching for more.
I also do not think it's really wise to use element from point. Doesn't the popup for claiming the coin have some selector as well?

find if camera has attached or detached keyboard control to canvas in Babylonjs

I have a program in BabylonJS that has multiple cameras (free camera, scene, follow, and sometimes an arc camera). I set the active camera(s) and attach control (keyboard based movement) using the following:
scene.activeCameras[0] = camera;
camera.attachControl(canvas);
There are times I bring up html forms in my program and allow the user to type in the form (or temporarily turn the controls over to custom keydown / keyup controls) so I use the following to detach the control (so that the typed keys are not intercepted by the canvas scene):
for (var i = 0;i < scene.activeCameras.length;i++) {
scene.activeCameras[i].detachControl(canvas);
}
Then I attach again when done. This works great, but there are times when I need to test if the control is attached or not.
Currently, I set an outside variable (but I have to place it in many locations in my code) but I was wondering if there is a function in BabylonJS that tells you the attached control camera name or boolean true / false if camera control is currently attached?
you can test scene.activeCameras[i].inputs.attachedElement
Using #DavidCatuhe 's answer I was able to write this function others might find useful. Thanks!
function iscamaraattached() {
var attached = false;
if (scene.activeCameras != null) {
for (var i=0;i < scene.activeCameras.length;i++) {
if (scene.activeCameras[i].inputs.attachedElement != null) {
console.log("i=" + i + " - " + scene.activeCameras[i].inputs.attachedElement.id);
attached = true;
}
}
}
return attached;
}

Cross-Frame events on IE Edge break HammerJS (v2)

I'm facing a strange problem that I think leaves HammerJS internal event loop with a stuck event that ruins subsequent detections.
This only happens on Internet Explorer Edge on a Touch Device with PointerEvents.
Basically, when using HammerJS for a PAN event (panstart -> panmove -> panend), and you cross the current frame boundary (for example, into an IFRAME, or just outside the browser window) AND you release your finger there, then HammerJS never receives the CANCEL event and the session kind of stays stuck.
From then on, all gestures are reported incorrectly, with one more finger ('pointer') than you're using: For example, it will report a PINCH or ROTATE (2 pointers) just tapping (1 pointer) and so on.
I haven't found a way to reset the Hammer Manager once it enters this ghost state. This breaks my app.
I've prepared a Fiddle with a full working example. Please execute it under a Windows/Touch device !
https://jsfiddle.net/28cxrupv/5/
I'd like to know, either how to detect the out-of-bounds event, or just how could I manually reset the Hammer Manager instance if I am able to detect myself by other means that there are stuck events.
UPDATE
I've found in my investigations that the problem is at the lowest level in HammerJS: the PointerEvents handler has an array of detected pointers this.store and there's the stuck event with an old timestamp.
I've found a way to patch Hammer.JS so it can detect stuck pointers. I don't know if this is wrong, but apparently it works!
On HammerJS PointerEvents handler, there's an array this.store that keeps all current pointer events. It's there where, when we pan out of the window and release the touch, a stuck event is kept forever.
Clearing this array causes Hammer to go back to normal again.
I just added a condition where, if we are processing a Primary touch (start of a gesture?), and the store is not empty, it clears the store automatically.
How it works is, on the next interaction with the stuck hammer instance, the internal store gets reset and the gesture is interpreted properly.
On Hammer.js 2.0.6, around line 885
/**
* handle mouse events
* #param {Object} ev
*/
handler: function PEhandler(ev) {
var store = this.store;
var removePointer = false;
var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
var isTouch = (pointerType == INPUT_TYPE_TOUCH);
// get index of the event in the store
var storeIndex = inArray(store, ev.pointerId, 'pointerId');
// start and mouse must be down
if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
// NEW CONDITION: Check the store is empty on a new gesture
// http://stackoverflow.com/questions/35618107/cross-frame-events-on-ie-edge-break-hammerjs-v2
if (ev.isPrimary && store.length) {
window.console.warn ("Store should be 0 on a primary touch! Clearing Stuck Event!");
this.reset();
}
if (storeIndex < 0) {
store.push(ev);
storeIndex = store.length - 1;
}
} else if (eventType & (INPUT_END | INPUT_CANCEL)) {
removePointer = true;
}
// it not found, so the pointer hasn't been down (so it's probably a hover)
if (storeIndex < 0) {
return;
}
// update the event in the store
store[storeIndex] = ev;
this.callback(this.manager, eventType, {
pointers: store,
changedPointers: [ev],
pointerType: pointerType,
srcEvent: ev
});
if (removePointer) {
// remove from the store
store.splice(storeIndex, 1);
}
}
});
and also I define the function "reset":
/**
* Reset internal state
*/
reset: function() {
this.store = (this.manager.session.pointerEvents = []);
},

visibilitychange event is not triggered when switching program/window with ALT+TAB or clicking in taskbar

The problem is with the behaviour of the event "visibilitychange".
It's triggered:
- When I switch to a different tab inside the browser window.
When I click in minimize / restore buttons for the browser window.
(this is ok)
It's not triggered:
- When I switch to a different window/program using ALT+TAB.
When I switch to a different window/program clicking on taskbar.
(this SHOULD trigger, because, just like when minimizing, the window's visibility may change)
W3 Page Visibility API Documentation: http://www.w3.org/TR/page-visibility/
There is no definition of "page visibility" regarding ALT+TAB/program switching in the spec sheet. I'm guessing it has something to do in between the OS and the Browser.
TESTED IN
Browsers:
Chrome 40.0.2214.115 m / Firefox 36.0.1 / Internet Explorer 11.0.9600.17107
OS: Windows 8.1
Is there a workaround to fix this behaviour? The implementation is fairly simple, I listen to the "visibilitychange" event using jQuery, and then in its callback, I check for the value of "document.visibilityState", but the problem is that the event is not firing when expected.
$(document).on('visibilitychange', function() {
if(document.visibilityState == 'hidden') {
// page is hidden
} else {
// page is visible
}
});
This can be done without jQuery too, but the ALT+TAB and taskbar switch hide/show expected behaviour is still missing:
if(document.addEventListener){
document.addEventListener("visibilitychange", function() {
// check for page visibility
});
}
I've also tried the ifvisible.js module (https://github.com/serkanyersen/ifvisible.js) but the behaviour is the same.
ifvisible.on('blur', function() {
// page is hidden
});
ifvisible.on('focus', function() {
// page is visible
});
I haven't tested in other browsers because if I can't make it work in Chrome on Windows I really don't care about the other browsers yet.
Any help or suggestions?
UPDATE
I tried using different vendor prefixes for the event name (visibilitychange, webkitvisibilitychange, mozvisibilitychange, msvisibilitychange) but but still the event is not triggered when I switch to a different program in the taskbar or ALT+TAB, or even if I open the start menu thing in windows with the windows key, which covers the whole screen.
I can reproduce the exact same issue in Chrome, Firefox and Internet Explorer.
UPDATE #2
Here's a roundup post I wrote for this issue and a workaround in pure Javascript to solve the encountered problems.
UPDATE #3
Edited to include a copy of the sourced blog post. (see accepted answer)
Here's a roundup post I wrote for this issue and a workaround in pure JavaScript to solve the encountered problems.
Edited to include a copy of the sourced blog post:
In any kind of javascript application we develop there may be a
feature or any change in the application which reacts according to the
current user visibility state, this could be to pause a playing video
when the user ALT+TABs to a different window, tracking stats about how
the users interact with our application, how often does him switch to
a different tab, how long does it take him to return and a lot of
performance improvements that can benefit from this kind of API.
The Page Visibility API provides us with two top-level attributes:
document.hidden (boolean) and document.visibilityState (which could be
any of these strings: “hidden”, “visible”, “prerender”, “unloaded”).
This would not be not good enough without an event we could listen to
though, that’s why the API also provides the useful visibilitychange
event.
So, here’s a basic example on how we could take action on a visibility
change:
function handleVisibilityChange() {
if(document.hidden) {
// the page is hidden
} else {
// the page is visible
}
}
document.addEventListener("visibilitychange", handleVisibilityChange, false);
We could also check for document.visibilityState value.
Dealing with vendor issues George Berkeley by John Smibert
Some of the implementations on some browsers still need that the
attributes or even the event name is vendor-prefixed, this means we
may need to listen to the msvisibilitychange event or check for the
document.webkitHidden or the document.mozHidden attributes. In order
to do so, we should check if any vendor-prefixed attribute is set, and
once we know which one is the one used in the current browser (only if
there’s the need for a prefix), we can name the event and attributes
properly.
Here’s an example approach on how to handle these prefixes:
var browserPrefixes = ['moz', 'ms', 'o', 'webkit'];
// get the correct attribute name
function getHiddenPropertyName(prefix) {
return (prefix ? prefix + 'Hidden' : 'hidden');
}
// get the correct event name
function getVisibilityEvent(prefix) {
return (prefix ? prefix : '') + 'visibilitychange';
}
// get current browser vendor prefix
function getBrowserPrefix() {
for (var i = 0; i < browserPrefixes.length; i++) {
if(getHiddenPropertyName(browserPrefixes[i]) in document) {
// return vendor prefix
return browserPrefixes[i];
}
}
// no vendor prefix needed
return null;
}
// bind and handle events
var browserPrefix = getBrowserPrefix();
function handleVisibilityChange() {
if(document[getHiddenPropertyName(browserPrefix )]) {
// the page is hidden
console.log('hidden');
} else {
// the page is visible
console.log('visible');
}
}
document.addEventListener(getVisibilityEvent(browserPrefix), handleVisibilityChange, false);
Other issues There is a challenging issue around the “Page Visibility”
definition: how to determine if the application is visible or not if
the window focus is lost for another window, but not the actual
visibility on the screen? what about different kinds of visibility
lost, like ALT+TAB, WIN/MAC key (start menu / dash), taskbar/dock
actions, WIN+L (lock screen), window minimize, window close, tab
switching. What about the behaviour on mobile devices?
There’s lots of ways in which we may lose or gain visibility and a lot
of possible interactions between the browser and the OS, therefore I
don’t think there’s a proper and complete “visible page” definition in
the W3C spec. This is the definition we get for the document.hidden
attribute:
HIDDEN ATTRIBUTE On getting, the hidden attribute MUST return true if
the Document contained by the top level browsing context (root window
in the browser’s viewport) [HTML5] is not visible at all. The
attribute MUST return false if the Document contained by the top level
browsing context is at least partially visible on at least one screen.
If the defaultView of the Document is null, on getting, the hidden
attribute MUST return true.
To accommodate accessibility tools that are typically full screen but
still show a view of the page, when applicable, this attribute MAY
return false when the User Agent is not minimized but is fully
obscured by other applications.
I’ve found several inconsistencies on when the event is actually
fired, for example (Chrome 41.0.2272.101 m, on Windows 8.1) the event
is not fired when I ALT+TAB to a different window/program nor when I
ALT+TAB again to return, but it IS fired if I CTRL+TAB and then
CTRL+SHIFT+TAB to switch between browser tabs. It’s also fired when I
click on the minimize button, but it’s not fired if the window is not
maximized and I click my editor window which is behing the browser
window. So the behaviour of this API and it’s different
implementations are still obscure.
A workaround for this, is to compensate taking advantage of the better
implemented focus and blur events, and making a custom approach to the
whole “Page Visibility” issue using an internal flag to prevent
multiple executions, this is what I’ve come up with:
var browserPrefixes = ['moz', 'ms', 'o', 'webkit'],
isVisible = true; // internal flag, defaults to true
// get the correct attribute name
function getHiddenPropertyName(prefix) {
return (prefix ? prefix + 'Hidden' : 'hidden');
}
// get the correct event name
function getVisibilityEvent(prefix) {
return (prefix ? prefix : '') + 'visibilitychange';
}
// get current browser vendor prefix
function getBrowserPrefix() {
for (var i = 0; i < browserPrefixes.length; i++) {
if(getHiddenPropertyName(browserPrefixes[i]) in document) {
// return vendor prefix
return browserPrefixes[i];
}
}
// no vendor prefix needed
return null;
}
// bind and handle events
var browserPrefix = getBrowserPrefix(),
hiddenPropertyName = getHiddenPropertyName(browserPrefix),
visibilityEventName = getVisibilityEvent(browserPrefix);
function onVisible() {
// prevent double execution
if(isVisible) {
return;
}
// change flag value
isVisible = true;
console.log('visible}
function onHidden() {
// prevent double execution
if(!isVisible) {
return;
}
// change flag value
isVisible = false;
console.log('hidden}
function handleVisibilityChange(forcedFlag) {
// forcedFlag is a boolean when this event handler is triggered by a
// focus or blur eventotherwise it's an Event object
if(typeof forcedFlag === "boolean") {
if(forcedFlag) {
return onVisible();
}
return onHidden();
}
if(document[hiddenPropertyName]) {
return onHidden();
}
return onVisible();
}
document.addEventListener(visibilityEventName, handleVisibilityChange, false);
// extra event listeners for better behaviour
document.addEventListener('focus', function() {
handleVisibilityChange(true);
}, false);
document.addEventListener('blur', function() {
handleVisibilityChange(false);
}, false);
window.addEventListener('focus', function() {
handleVisibilityChange(true);
}, false);
window.addEventListener('blur', function() {
handleVisibilityChange(false);
}, false);
I welcome any feedback on this workaround. Some other great sources
for ideas on this subject:
Using the Page Visibility API Using PC Hardware more efficiently in
HTML5: New Web Performance APIs, Part 2 Introduction to the Page
Visibility API Conclusion The technologies of the web are continuously
evolving, we’re still recovering from a dark past where tables where
the markup king, where semantics didn’t mattered, and they weren’t any
standards around how a browser should render a page.
It’s important we push these new standards forward, but sometimes our
development requirements make us still need to adapt to these kind of
transitions, by handling vendor prefixes, testing in different
browsers and differents OSs or depend on third-party tools to properly
identify this differences.
We can only hope for a future where the W3C specifications are
strictly revised, strictly implemented by the browser developer teams,
and maybe one day we will have a common standard for all of us to work
with.
As for the Page Visibility API let’s just kinda cite George Berkeley
and say that:
“being visible” is being perceived.
A working solution is proposed described here: https://stackoverflow.com/a/9502074/698168. It uses a combination of the W3C Page Visibility API, blur/focus and mouse movements. Hidden HTML pages related to Alt+Tab are identified in a probabilistic way (i.e. you cannot determine if your page is hidden with 100% accuracy).
we can do like below when switching between tabs and switching between applications
var pageVisible = true;
function handleVisibilityChange() {
if (document.hidden) {
pageVisible = false;
} else {
pageVisible = true;
}
console.log("handleVisibilityChange")
console.log("pageVisible", pageVisible)
// some function call
}
document.addEventListener("visibilitychange", handleVisibilityChange, false);
window.addEventListener('focus', function() {
pageVisible = true;
// some function call
}, false);
window.addEventListener('blur', function() {
pageVisible = false;
// some function call
}, false);
There's a very simple solution to this I have come across.
You just need to pass false to the useCapture while attaching an event listener to the document. Works like a charm!
document.addEventListener('visibilitychange', function () {
// code goes here
}, false)

Dynamically creating a series of oscillators that play with keyup/down

The problem is this:
In the following example, http://jsfiddle.net/GmgGY/2/
when you click on the orange button it creates a new div. When you click on this div it plays an oscillator. If you push a key on the keyboard (keydown) it plays it as well. It then stops playing it when the keyboard character is lifted (keyup). This is good and what I want.
However, when you click the orange button multiple times and create multiple synths. When you push a key on the keyboard all of them play (which is what I want) but only the last created one seems to respond to the keyup event.I want all of them to respond to the keyup event.Not just the last one.
I am not sure how to fix this.
Each dynamically created div has a unique ID but also a class that is universal to all of them. I thought there might be a way to select the class ( synth.class) and launch a universal oscillator.disconnect() on keyup ???
Another thing I'm thinking is my problem might need some kind of iterating thread that compensates for whatever DOM issue is causing this (assuming it isn't just exclusively the programming thus far). But I am not sure.
The Javascript code is below. I tried to keep it as minimal as possible but I couldn't figure out how to make it any smaller than this and still have it be clear. I omitted the html and css elements but kept them in the JSfiddle example.
$(function(){
var SynthCreationModule = (function(){
context = new webkitAudioContext();
var orangeButton;
var applicationArea = document.getElementById("applicationArea"),
orangeButton = document.getElementById("orangeButton"),
counterSynth = 1;
counterPitchInput = 1;
orangeButton.addEventListener("click",createSynth, false);
function createSynth () {
var synth = document.createElement("div");
synth.className = "synth";
synth.id = "synthid" + (counterSynth++);
applicationArea.appendChild(synth);
var pitchInput = document.createElement('input');
pitchInput.type = "range";
pitchInput.className = "pitchInputClass";
pitchInput.id = "pitchInput" + (counterPitchInput++);
pitchInput.min = "0";
pitchInput.max="2000";
synth.appendChild(pitchInput);
synth.onmousedown= function () {
oscillator = context.createOscillator(),
oscillator.type = 2;
oscillator.frequency.value = pitchInput.value;
oscillator.connect(context.destination);
oscillator.noteOn(0);
};
synth.onmouseup = function () {
oscillator.disconnect();
};
// Keydown & keyup events to launch oscillator. ( These don't work properly if you create two or more synths. Playing a key down works, but keyup only works on the last created synth. The previous created synths will continue to create additional oscillators but the keydown will not work to stop them.
var keydown = false;
$('body').keydown(function() {
if(!keydown){
synth.onmousedown();
keydown = true;
}
});
$('body').keyup(function() {
synth.onmouseup();
keydown = false;
});
$(synth).draggable();
};
}());
});
Your problem is actually that you never explicitly declare and scope "oscillator" - so it's going into globals. Try putting "this." in front of each occurrence of "oscillator", and it will work.
This isn't ideal code, though, because you're attaching a whole extra body event handler for each synth - your code
$('body').keydown(function() {
if(!keydown){
synth.onmousedown();
keydown = true;
}
});
is creating a whole separate function call and calling attachEventHandler on the body under the hood, with "synth" bound to the new version; it might be better to track the list of synths (even getting them back from a body.getElementsBySelector()) and calling noteOn/Off on each one. Up to you, though.

Categories