This function is working correctly in Firefox but not IE 11. I believe the culprit is:
event = String(data.model.attributes.eventToRaise);
I know through a bunch of logging (which I removed for presentation here) that I'm getting into the first else branch correctly, but that the comparison to 'AAA' and 'BBB' aren't working correctly in IE. Logging the contents of 'event' in Firefox shows me a string but in IE console it's a pointer. I've tried a few different things (.toString(), "" + event), all of which worked in FF but not IE.
The context is Backbone with Marionette, this function is being called on click events from different places, and the code is supposed to determine which event originated the call.
showPanel: function (data) {
event = String(data.model.attributes.eventToRaise);
if (event === this.lastEvent) {
//do something
}
else {
var view = NaN;
if (event === 'AAA') {
//whatever
}
else if (event === 'BBB') {
//whatever else
}
this.lastEvent = event;
}
edit: 'var event = String(data.model.attributes.eventToRaise);' solved the issue
You could try the following:
showPanel: function (data) {
event = new String(data.model.attributes.eventToRaise);
if (event === this.lastEvent) {
//do something
}
else {
var view = NaN;
if (event.indexOf("AAA")) {
//whatever
}
else if (event.indexOf("BBB")) {
//whatever else
}
this.lastEvent = event;
}
var event = data.model.attributes.eventToRaise;
solved the issue.
Related
Given a publish-subscribe pattern using ES6 as follows (extracted from https://davidwalsh.name/pubsub-javascript):
class PubSub {
constructor() {
this.handlers = [];
}
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
{
if (this.getHandler(event, handler) == null) {
this.handlers.push({event: event, handler: handler.bind(context), key: Guid()});
}
}
}
unsubscribe(event, handler) {
let filteredHandler = this.getHandler(event, handler);
if (filteredHandler != null) {
let idx = this.handlers.indexOf(filteredHandler);
if (idx > -1) {
this.handlers.splice(idx, 1);
}
}
}
publish(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) {
topic.handler(args)
}
})
}
getHandler(event, handler) {
if (this.handlers == null || this.handlers.length < 1) {
return null;
}
let filtered = null;
this.handlers.forEach(topic => {
if (topic.event === event && topic.handler === handler) {
filtered = topic;
}
});
return filtered;
}
getNumOfSubsribers() {
if (this.handlers != null && this.handlers.length > 0) {
return this.handlers.length;
}
return 0;
}
}
The subscribe and publish methods work. However, the getHandler and unsubscribe method do not work as expected (getHandler seems returning null). I have tried to search around but could not get a satisfactory solution to this problem (not sure how a function bound to a given context can be filtered out from an array).
What have I done wrong in the code? Kindly advise me on getHandler and also unsubscribe part of the code.
Appreciate some kind help.
That code is odd in a couple of ways.
The reason getHandler doesn't work is that the handler property of the object pushed on handlers is not the function that was passed in; it's the result of calling bind on that function. Formatted properly, this is subscribe:
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
{
if (this.getHandler(event, handler) == null) {
this.handlers.push({
event: event,
handler: handler.bind(context), // ** NOTE **
key: Guid()
});
}
}
}
That value will never be equal to the original, by definition.
Instead, it should include the original handler as well so it can check for it later. Let's also get rid of the pointless standalone block:
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
if (this.getHandler(event, handler) == null) {
this.handlers.push({
event: event,
handler: handler.bind(context),
originalHandler: handler, // ***
key: Guid()
});
}
}
Now, getHandler can look for matches with originalHandler. While we're there, let's stop looping when we find the handler rather than keeping going, and use the semantically-appropriate Array#find:
getHandler(event, handler) {
if (this.handlers == null || this.handlers.length < 1) {
return null;
}
let filtered = this.handlers.find(topic => topic.event === event && topic.originalHandler === handler);
return filtered;
}
There are other issues with the code (like binding the handler to itself if no context is provided), but a full code review is out of scope; the above is why getHandler doesn't work and thus why unsubscribe doesn't work. With that fix, unsubscribe should also work (though it seems odd to search twice).
Using edge animate, I don't seem to have the control over orders of operations that I am looking for. I have a function that operates a switch and another that tests the conditions of one or many switches. The problem I am running into is edge keeps wanting to run the test before the switch.
I can have the switch launch the test but run into the issue that I load the objects in an array inside of edge. What I am thinking is I need a way of pre-loading variables that the function can use but they don't need to be global since they are only used in this one function.
Here is what I have so far.
Inside Edge Animate:
twoPhaseSwitch('.btn1','on'); //sets up switch 1
twoPhaseSwitch('.swtch1','off'); //sets up switch 2
conditionsArray(['.btn1','.swtch1']); // tells the test script what the buttons are
In JavaScript file:
function twoPhaseSwitch(object,state)
{
var obj = $(object);
stage.getSymbol(obj).stop(state);
obj.data('state',state);
obj.mousedown(function(e)
{
if(obj.state == 'off')
{
stage.getSymbol(obj).stop('on');
obj.state = 'on';
obj.data('state','on');
}else{
stage.getSymbol(obj).stop('off');
obj.state = 'off';
obj.data('state','off');
};
});
};
function conditionsArray(obj)
{
var answers = ['on','on'];
// compare lengths
if (obj.length != answers.length)
return 'Argument Miscount';
var challengResults = challangeArray();
if (challengResults == true)
{
lightOn('.LED1','on');
}else if(challengResults == false)
{
lightOn('.LED1','off');
};
console.log(challengResults);
function challangeArray()
{
for (var i = 0, l=obj.length; i < l; i++)
{
if ($(obj[i]).data('state') != answers[i]) {
return false;
}
}
return true;
};
};
function lightOn(lightOBJ,state)
{
lightOBJ = $(lightOBJ);
stage.getSymbol(lightOBJ).stop(state);
};
I use mousedown and mouseup currently to fake the order of operations but it brings some pretty unacceptable issues so I am trying to do this right.
did you try wrapping your code in
$( document ).ready(){
your code here
}
to prevent any javascript from running until the page loads?
Need to be able to arbitrarily create events (even those created during runtime) and then arbitrarily bind event listeners for such events to elements.
In the code below, everything works exactly as planned except that XBS.pullCurtain() is never called (but the event fires!). I've checked and rechecked the arguments, the sequence, etc.—no errors are thrown, everything appears to be what I'm expecting. But no sauce from the event listener. :S
// this func is used in the larger object below
function eCustom(eName, eProperties) {
var defaultProps = {"bubbles":true, "cancelable":false, "eventPhase":0, "type":eName};
if (typeof(eProperties) == "object") {
for (var prop in eProperties) {
if (eProperties.hasOwnProperty(prop) ) {
defaultProps[prop] = eProperties[prop];
}
}
}
return jQuery.Event(eName, defaultProps);
}
window.XBS = {
cfg: {
minLoadTime: 1000
},
evnt: {
wakeFromSleep: eCustom("wakeFromSleep"),
assetsLoaded: eCustom("assetsLoaded")
},
init: {
// obviously will be expanded haha
XBS.initImages();
},
stopwatch: {
__sw: new Date(),
reset:function() { XBS.stopwatch.startTime = -1},
start: function() {
var now = XBS.stopwatch.__sw.getTime();
XBS.stopwatch.startTime = now;
return now;
},
read: function() { return XBS.stopwatch.__sw.getTime() - XBS.stopwatch.start;},
now: function() { return XBS.stopwatch.__sw.getTime();},
elapsed: function(period) {
var diff = XBS.stopwatch.now() - XBS.stopwatch.startTime;
return (period !== undefined) ? diff > period : diff;
},
startTime:-1
},
sleep: function(period, wakeEvent, onWake, target) {
if (wakeEvent == undefined) wakeEvent = XBS.evnt.wakeFromSleep;
if (isFunction(onWake) ) {
// confirmed code reaches these lines and all vars are as expected
if (target == undefined) target = window;
$(target).on(wakeEvent, onWake);
}
setTimeout(function() {$(target).trigger(wakeEvent) }, period);
},
initImages: function() {
XBS.stopwatch.start();
// loadImages is a jQuery extension (link at bottom), but it works like a charm—
// problem isn't there, I'm 99% sure
$("#image-loading-queue").loadImages({
allLoadedClb:function() {
var success = null;
//minLoadTime because there will be presentational animations
if (XBS.stopwatch.elapsed(XBS.cfg.minLoadTime)) {
success = true;
} else {
var elapsed = XBS.cfg.minLoadTime - XBS.stopwatch.elapsed();
XBS.sleep(elapsed, XBS.evnt.wakeFromSleep, XBS.pullCurtain, "#curtain");
}
}
});
// this return is a placeholder for pending functionality, which is why it's not used
return success;
},
pullCurtain: function() {
// this will get more elaborate, but for now, I just want to call the darn thing!
$("#curtain").fadeToggle();
}
}
Borrowed Library:
http://www.jqueryscript.net/loading/Asynchronous-Image-Loading-with-jQuery-Image-Loader-Plugin.html
jsFiddle demo
You have an error in your jQuery on method call (which your JavaScript console is probably complaining about). The first parameter to on should only be the name of the event to handle (a string), not a jQuery Event object. Take a look at the documentation. It has two signatures: one which takes the method name (e.g. 'click') and one which takes a map of multiple method names to functions (e.g. {'click': fn1, 'mouseover': fn2}). It doesn't have a method signature for jQuery Event objects.
When you bind the handler, change the line to pass the event name only:
$(target).on(wakeEvent.type, onWake);
For context, I am writing a plugin for omniture (adobe) sitecatalyst for video tracking. Before I write the plugin in the sitecatalyst format, I want to confirm it is working. I have tested the same code but using jQuery, and it works fine with how jQuery handles variables/scope. But doing it with Javascript directly is proving to be a little more difficult. Here is where I am at:
var vsa = new Array();
var vp = new Array();
vsa = document.getElementsByTagName('video');
if(vsa.length>0){
for(var vvv=0;vvv<vsa.length;vvv++) {
vsa[vvv].addEventListener('seeked',function () { if(vp[vsa[vvv].id]) { s.Media.play(vsa[vvv].id,vsa[vvv].currentTime); }},false);
vsa[vvv].addEventListener('seeking',function () { if(vp[vsa[vvv].id]) { s.Media.play(vsa[vvv].id,vsa[vvv].currentTime); }},false);
vsa[vvv].addEventListener('play',function () {
if(!vp[vsa[vvv].id]) {
vp[vsa[vvv].id] = true;
s.Media.open(vsa[vvv].id,vsa[vvv].duration,s.Media.playerName);
s.Media.play(vsa[vvv].id,vsa[vvv].currentTime);
} else {
s.Media.play(vsa[vvv].id,vsa[vvv].currentTime);
}},false);
vsa[vvv].addEventListener('pause',function () { if(vp[vsa[vvv].id]) { s.Media.stop(vsa[vvv].id,vsa[vvv].currentTime); }},false);
vsa[vvv].addEventListener('ended',function () { vp[vsa[vvv].id] = false; s.Media.stop(vsa[vvv].id,vsa[vvv].currentTime); s.Media.close(vsa[vvv].id); },false);
if (typeof vsa[vvv].error != 'undefined' && vsa[vvv].error) {
var scvt_msg = 'Error Not Captured';
if(typeof vsa[vvv].error.code != 'undefined') {
switch (vsa[vvv].error.code) {
case MEDIA_ERR_ABORTED:
scvt_msg = 'vsa[vvv]eo stopped before load.';
break;
case MEDIA_ERR_NETWORK:
scvt_msg = 'Network error';
break;
case MEDIA_ERR_DECODE:
scvt_msg = 'vsa[vvv]eo is broken';
break;
case MEDIA_ERR_SRC_NOT_SUPPORTED:
scvt_msg = 'Codec is unsupported by this browser';
break;
}
}
s.tl(this,'o','video: ' + scvt_msg);
}
}
}
On load, there is no error (meaning the eventlisteners are attaching correctly). When I press play on the video, I get a "vsa[vvv] is undefined". on the line of code that starts with
if(!vp[vsa[vvv].id])
Any ideas how to get the "global" vars of vsa, vp, and s accessible from the event listener function?
Thank you!
The variables are accessible. The problem is that you fell into the (very common) closures inside for loops trap.
Basically, all the event listeners are sharing the same vvv variable. By the time the event listeners run, vvv has gone through all the loop and is set to vsa.length, making vsa[vvv] undefined. (Check this out by adding a console.log(vvv) call to your event listeners)
The usual fix for this is creating the event listeners in a function outside the loop, to make each event listener get its own reference to a videotag variable.
function addMyEvents(node){
//the inner functions here get their own separate version of "node"
// instead of sharing the vvv
node.addEventListener('seeked',function () { if(vp[node.id]) { s.Media.play(node.id, node.currentTime); }},false);
node.addEventListener('seeking',function () { if(vp[node.id]) { s.Media.play(node.id, node.currentTime); }},false);
//...
}
for(var vvv=0;vvv<vsa.length;vvv++) {
addMyEvents(vsa[vvv]);
}
By the way, you don't need the if(vsa.length) test in the beginning since the loop wont run if the length is zero anyway...
To aboid this error in the future, run your code through a linter like JSHint or JSLint. They give warnings if you create functions inside a for loop.
Here is my code:
function pauseSound() {
var pauseSound = document.getElementById("backgroundMusic");
pauseSound.pause();
}
I would like to add a keyboard shortcut to this code, how can I do this so that the function can also be executed when a button is clicked too?
Tried to add an else if statement but it doesn't work, any ideas?
function doc_keyUp(e) {
if (e.ctrlKey && e.keyCode == 88) {
pauseSound();
}
else if (e.ctrlKey && e.keyCode == 84) {
playSound();
}
}
An event handler for the document's keyup event seems like an appropriate solution.
Note: KeyboardEvent.keyCode was deprecated in favor of KeyboardEvent.key.
// define a handler
function doc_keyUp(e) {
// this would test for whichever key is 40 (down arrow) and the ctrl key at the same time
if (e.ctrlKey && e.key === 'ArrowDown') {
// call your function to do the thing
pauseSound();
}
}
// register the handler
document.addEventListener('keyup', doc_keyUp, false);
If you want to trigger an event after pressing a key, try:
In this example press ALT+a:
document.onkeyup = function () {
var e = e || window.event; // for IE to cover IEs window event-object
if(e.altKey && e.which == 65) {
alert('Keyboard shortcut working!');
return false;
}
}
Here is a fiddle: https://jsfiddle.net/dmtf6n27/38/
Please also note there is a difference for the keycode numbers, whether you are using onkeypress or onkeyup. W3 Schools' "KeyboardEvent keyCode" Property has more information.
//For single key: Short cut key for 'Z'
document.onkeypress = function (e) {
var evt = window.event || e;
switch (evt.keyCode) {
case 90:
// Call your method Here
break;
}
}
//For combine keys like Alt+P
document.onkeyup = function (e) {
var evt = window.event || e;
if (evt.keyCode == 80 && evt.altKey) {
// Call Your method here
}
}
}
//ensure if short cut keys are case sensitive.
// If its not case sensitive then
//check with the evt.keyCode values for both upper case and lower case. ......
Here's my solution:
HTMLElement.prototype.onshortcut = function(shortcut, handler) {
var currentKeys = []
function reset() {
currentKeys = []
}
function shortcutMatches() {
currentKeys.sort()
shortcut.sort()
return (
JSON.stringify(currentKeys) ==
JSON.stringify(shortcut)
)
}
this.onkeydown = function(ev) {
currentKeys.push(ev.key)
if (shortcutMatches()) {
ev.preventDefault()
reset()
handler(this)
}
}
this.onkeyup = reset
}
document.body.onshortcut(["Control", "Shift", "P"], el => {
alert("Hello!")
})
When you call my function, it will create an array called currentKeys; these are the keys that will are being held down at that moment.
Every time a key is pressed, sensed because of onkeydown, it is added to the currentKeys array.
When the keys are released, sensed because of onkeyup, the array is reset meaning that no keys are being pressed at that moment.
Each time it will check if the shortcut matches. If it does it will call the handler.
This worked for me
document.onkeyup=function(e){
var e = e || window.event;
if(e.which == 37) {
$("#prev").click()
}else if(e.which == 39){
$("#next").click()
}
}
Catch the key code and then call your function. This example catches the ESC key and calls your function:
function getKey(key) {
if ( key == null ) {
keycode = event.keyCode;
// To Mozilla
} else {
keycode = key.keyCode;
}
// Return the key in lower case form
if (keycode ==27){
//alert(keycode);
pauseSound();
return false;
}
//return String.fromCharCode(keycode).toLowerCase();
}
$(document).ready( function (){
$(document).keydown(function (eventObj){
//alert("Keydown: The key is: "+getKey(eventObj));
getKey(eventObj);
});
});
You'll need JQUERY for this example.
These appear to all be using the deprecated keyCode and which properties. Here is a non-deprecated version using jQuery to wire up the event:
$("body").on("keyup", function (e) {
if(e.ctrlKey && e.key == 'x')
pauseSound();
else if(e.ctrlKey && e.key == 't')
playSound();
})
Note: Ctrl+t may already be assigned to opening a new browser tab.
Here's some stuff to use if you want. You can register a bunch of keys and handler with it.
Comments are in the code, but in short it sets up a listener on the document and manages a hash with the key combinations for which you want to listen.
When you register a key (combination) to listen for, you submit the keycode (preferrably as a constant taken from the exported "key" property, to which you can add more constants for yourself), a handler function and possibly an options hash where you say if the Ctrl and/or Alt key are involved in your plans for this key.
When you de-register a key (combination) you just submit the key and the optional hash for Ctrl/Alt-ness.
window.npup = (function keypressListener() {
// Object to hold keyCode/handler mappings
var mappings = {};
// Default options for additional meta keys
var defaultOptions = {ctrl:false, alt:false};
// Flag for if we're running checks or not
var active = false;
// The function that gets called on keyup.
// Tries to find a handler to execute
function driver(event) {
var keyCode = event.keyCode, ctrl = !!event.ctrlKey, alt = !!event.altKey;
var key = buildKey(keyCode, ctrl, alt);
var handler = mappings[key];
if (handler) {handler(event);}
}
// Take the three props and make a string to use as key in the hash
function buildKey(keyCode, ctrl, alt) {return (keyCode+'_'+ctrl+'_'+alt);}
function listen(keyCode, handler, options) {
// Build default options if there are none submitted
options = options || defaultOptions;
if (typeof handler!=='function') {throw new Error('Submit a handler for keyCode #'+keyCode+'(ctrl:'+!!options.ctrl+', alt:'+options.alt+')');}
// Build a key and map handler for the key combination
var key = buildKey(keyCode, !!options.ctrl, !!options.alt);
mappings[key] = handler;
}
function unListen(keyCode, options) {
// Build default options if there are none submitted
options = options || defaultOptions;
// Build a key and map handler for the key combination
var key = buildKey(keyCode, !!options.ctrl, !!options.alt);
// Delete what was found
delete mappings[key];
}
// Rudimentary attempt att cross-browser-ness
var xb = {
addEventListener: function (element, eventName, handler) {
if (element.attachEvent) {element.attachEvent('on'+eventName, handler);}
else {element.addEventListener(eventName, handler, false);}
}
, removeEventListener: function (element, eventName, handler) {
if (element.attachEvent) {element.detachEvent('on'+eventName, handler);}
else {element.removeEventListener(eventName, handler, false);}
}
};
function setActive(activate) {
activate = (typeof activate==='undefined' || !!activate); // true is default
if (activate===active) {return;} // already in the desired state, do nothing
var addOrRemove = activate ? 'addEventListener' : 'removeEventListener';
xb[addOrRemove](document, 'keyup', driver);
active = activate;
}
// Activate on load
setActive();
// export API
return {
// Add/replace handler for a keycode.
// Submit keycode, handler function and an optional hash with booleans for properties 'ctrl' and 'alt'
listen: listen
// Remove handler for a keycode
// Submit keycode and an optional hash with booleans for properties 'ctrl' and 'alt'
, unListen: unListen
// Turn on or off the whole thing.
// Submit a boolean. No arg means true
, setActive: setActive
// Keycode constants, fill in your own here
, key : {
VK_F1 : 112
, VK_F2: 113
, VK_A: 65
, VK_B: 66
, VK_C: 67
}
};
})();
// Small demo of listen and unListen
// Usage:
// listen(key, handler [,options])
// unListen(key, [,options])
npup.listen(npup.key.VK_F1, function (event) {
console.log('F1, adding listener on \'B\'');
npup.listen(npup.key.VK_B, function (event) {
console.log('B');
});
});
npup.listen(npup.key.VK_F2, function (event) {
console.log('F2, removing listener on \'B\'');
npup.unListen(npup.key.VK_B);
});
npup.listen(npup.key.VK_A, function (event) {
console.log('ctrl-A');
}, {ctrl: true});
npup.listen(npup.key.VK_A, function (event) {
console.log('ctrl-alt-A');
}, {ctrl: true, alt: true});
npup.listen(npup.key.VK_C, function (event) {
console.log('ctrl-alt-C => It all ends!');
npup.setActive(false);
}, {ctrl: true, alt: true});
It is not terribly tested, but seemed to work OK.
Look at Javascript Char Codes (Key Codes) to find a lot of keyCodes to use,
Solution:
var activeKeys = [];
//determine operating system
var os = false;
window.addEventListener('load', function() {
var userAgent = navigator.appVersion;
if (userAgent.indexOf("Win") != -1) os = "windows";
if (userAgent.indexOf("Mac") != -1) os = "osx";
if (userAgent.indexOf("X11") != -1) os = "unix";
if (userAgent.indexOf("Linux") != -1) os = "linux";
});
window.addEventListener('keydown', function(e) {
if (activeKeys.indexOf(e.which) == -1) {
activeKeys.push(e.which);
}
if (os == 'osx') {
} else {
//use indexOf function to check for keys being pressed IE
if (activeKeys.indexOf(17) != -1 && activeKeys.indexOf(86) != -1) {
console.log('you are trying to paste with control+v keys');
}
/*
the control and v keys (for paste)
if(activeKeys.indexOf(17) != -1 && activeKeys.indexOf(86) != -1){
command and v keys are being pressed
}
*/
}
});
window.addEventListener('keyup', function(e) {
var result = activeKeys.indexOf(e.which);
if (result != -1) {
activeKeys.splice(result, 1);
}
});
Explanation:
I ran into this same problem and came up with my own solution. e.metaKey didn't seem to work with the keyup event in Chrome and Safari. However, I'm not sure if it was specific to my application since I had other algorithms blocking some key events and I may have mistakenly blocked the meta key.
This algorithm monitors for keys going down and then adds them to a list of keys that are currently being pressed. When released, the key is removed from the list. Check for simultaneous keys in the list by using indexOf to find key codes in the array.
Saving with ctrl+s in React
useEffect(() => {
document.onkeydown = function (e) {
if (e.ctrlKey == true && e.key == 's') {
e.preventDefault() // to override browser's default save page feature
alert('ctrl+s is working for save!') // invoke your API to save
}
}
}, [])
Many of these answers suggest forcibly overriding document.onkeypress. This is not a good practice because it only allows for a single event handler to be assigned. If any other handlers were previously set up by another script they will be replaced by your function. If you assign another handler later, it will replace the one you assigned here.
A much better approach is to use addEventListener to attach your keyboard shortcut. This allows you to attach as many handlers as necessary and will not interfere with any external libraries that may have attached their own.
Additionally, the UIEvent.which property was never standardized and should not be used. The same goes for KeyboardEvent.keyCode. The current standards compliant property you should use to check which key was pressed is KeyboardEvent.key. Find the key you want in the full list of available values.
For best performance, return early if your desired modifier key is not pressed. As well, rather than having multiple keypress event listeners, use a single one with a swtich/case statement to react appropriately to each key that you want to handle.
Also, do not forget to cancel the default behavior of the key with Event.preventDefault if necessary. Though, there are some shortcuts that you cannot override like ctrl+w.
document.addEventListener('keypress', event => {
if (!event.ctrlKey) { return; }
event.preventDefault();
switch (event.key) {
case 'x' : doSomething(); break
case 'z' : doSomethingElse(); break;
default : console.log('unhandled key was pressed');
}
});