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.
Related
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?
What I am trying to achieve is to clone an item on the canvas when I click on it and drag the clone without releasing the mouse.
menuItem.onMouseDown = function(event){
var clone = this.clone();
clone.onMouseDrag = function(event){
this.position+=event.delta;
console.log(event);
}
var ev = new MouseEvent('mousedrag',
event.event);
ev.event.type="mousemove";
ev.delta={x:0,y:0};
ev.target=clone;
ev.point=event.point;
clone.emit('mousedrag',ev);
};
I tried this, I believe I need something like this. So when I click the menuItem I clone it, and set up the event for the clone, and then emit an event on it. But the emitted event needs to be set up, and that is where my idea falls apart. Any thoughts on this one?
I would do things a bit differently; I wouldn't try to swap the handlers in the middle of the selection/dragging but would just logically swap the items temporarily:
menuItem = new Path.Circle(view.bounds.center, 25);
menuItem.fillColor = 'red';
var oldMenuItem = null;
menuItem.onMouseDown = function(e) {
oldMenuItem = this.clone();
oldMenuItem.fillColor = 'green';
}
menuItem.onMouseDrag = function(e) {
this.position += e.delta;
}
menuItem.onMouseUp = function(e) {
// swap menuItem and oldMenuItem to keep mouse handling on the
// original item?
var t = this.position;
this.position = oldMenuItem.position;
oldMenuItem.position = t;
}
Here's a sketch that implements something similar (I think) to what you're looking for. Just pretend the red circle is the menu item.
It is possible to construct a ToolEvent or MouseEvent (depending on the handler) but that is currently undocumented and would require a trip to the source code on github. Edit: I got curious and took a trip to github.
The MouseEvent constructor code is in the events directory but the constructor is used and the .emit function is called from the CanvasView.js module (search for new MouseEvent). But in order to simulate what you want to you'll need to simulate the entire chain of events in order to keep the internal state consistent. So you'd need to 1) emit a mouseup event on the original item, 2) emit a mousedown on the new item, and 3) emit mousemoves on the new item, and 3) detach the handler from the original item and attach a handler to the new item in between 1 & 2. (You might not need to emit the original mouseup if you detach the handler before.) If you've created a Tool you will need to create a ToolEvent rather than a MouseEvent.
Anyway, I can see why it's not documented - there is a lot to keep in mind. I wanted to update this answer to reflect your original question even though the answer is still that it is probably best to find another way to perform this action.
In case someone really wants or needs to do this:
Mouse events are constructed via the code in MouseEvent.js
The constructor is used, and the event emitted, in CanvasView.js
Event processing is driven in View.js
Tool events are constructed via code in ToolEvent.js
OTOH, emitting a frame event is easy:
view.emit('frame', {});
I'm having an issue where I have a phantom game object floating around in my JavaScript program that's still attached to my keyboard input handler (keymaster.js).
In the code below, I run startGame in demo mode when the page first loads, in order to display a simulated game while the user is reading the splash screen.
Then when the user presses the "Start" button, I rerun startGame. However, at this point, whenever the user presses a keystroke, two bullets are fired instead of one. I've discovered that there are two separate 'game' objects responding to the keystrokes, and each is firing a bullet.
How can I completely reset my game state between games and kill all associated objects in memory?
var startGame = function(demoMode) {
this.game = new TypingFrenzy.Game({
"ctx": ctx,
"ctx_kbd": ctx_kbd,
"demoMode": demoMode
});
this.currentGameView = new TypingFrenzy.GameView(this.game, ctx);
this.currentGameView.start();
}
startGame(true)
Below is the code where I bind my event handlers, in response to meager's comment:
GameView.prototype.bindKeyHandlers = function () {
var ship = this.ship;
for (i = 33; i <= 64; i++) {
var chr = String.fromCharCode(i);
key(chr, function (event, handler) { ship.processKeystroke(event, handler) });
}
};
Unbinding my key listeners from my game object allowed my game object to be deleted. I used the following command:
key.unbind('a');
Thanks meager!
This solved the problem. See my edits to the original question.
key.unbind('a');
I'm in need of a function that figures out if the user is clicking, double-clicking or dragging the mouse.
Since it's all happening in the same canvas using the normal events doesn't work. I found this through google:
It is inadvisable to bind handlers to both the click and dblclick
events for the same element. The sequence of events triggered varies
from browser to browser, with some receiving two click events and
others only one. If an interface that reacts differently to single-
and double-clicks cannot be avoided, then the dblclick event should be
simulated within the click handler. We can achieve this by saving a
timestamp in the handler, and then comparing the current time to the
saved timestamp on subsequent clicks. If the difference is small
enough, we can treat the click as a double-click.
How could I achieve this in a good way?
First see if the click is pressed (for drag)? When it is released treat it as a click? and then if clicked again doubleclick?
How can I translate this to code? Help appreciated.
Something like the following should get you started, I'm using jQuery just to make it easier to show, this can be implemented with straight JS, it's just a lot of noise
$(function() {
var doubleClickThreshold = 50; //ms
var lastClick = 0;
var isDragging = false;
var isDoubleClick = false;
$node = ("#mycanvas");
$node.click(function(){
var thisClick = new Date().getTime();
var isDoubleClick = thisClick - lastClick < doubleClickThreshold;
lastClick = thisClick;
});
$node.mousedown(function() {
mouseIsDown = true;
});
$node.mouseUp(function() {
isDragging = false;
mouseIsDown = false;
});
// Using document so you can drag outside of the canvas, use $node
// if you cannot drag outside of the canvas
$(document).mousemove(function() {
if (mouseIsDown) {
isDragging = true;
}
});
});
I would probably work around this by putting a timer in the click event to check for another click.
time = 0
if(new Date().getTime() < time + 400) {
// Double click
throw new Error("Stop execution");
}
if(!!time)
throw new Error("Stop execution");
time = new Date().getTime();
// Single click
Something like that. Untested, and I can't think right now for some odd reason.
This is sufficiently important to me that I'm opening the question up to a bounty with a simple goal: Can anyone successfully open a new, full-screen NativeWindow in AJAX Air and, from that window, detect key strokes?
Hopefully I'm just overlooking something really, really simple, but if JS is not capable of listening for keyboard events, maybe a flash widget/helper might be able to relay keyboard events to JS. That's the only thing I can think of, but there may be other ways. I just don't know. Hopefully someone out there knows the right answer!
Update
Many thanks to #mwilcox for the answer. I don't know what the difference is between the method I was using (from the O'Reilly Cookbook) and createRootWindow(), but whatever it is, it did solve my problems. The code I ended up using is this:
var objWindowOptions = new air.NativeWindowInitOptions();
objWindowOptions.transparent = false;
objWindowOptions.systemChrome = air.NativeWindowSystemChrome.STANDARD;
objWindowOptions.type= air.NativeWindowType.NORMAL;
var linkScreenToMainWindow = function() {
wWindow.removeEventListener(air.Event.COMPLETE,linkScreenToMainWindow);
objScreen.setWindowReference(wWindow.stage.getChildAt(0).window);
// At this point your windows are connected and you can fire commands into
// the window using objScreen as a proxy. For example:
alert(objScreen.document.body.innerHTML);
objScreen.myfunction();
};
var fhFilePath = air.File.applicationDirectory.resolvePath('childwindow.html');
wWindow = air.HTMLLoader.createRootWindow(true, objWindowOptions, true);
wWindow.stage.displayState = window.runtime.flash.display.StageDisplayState.FULL_SCREEN_INTERACTIVE;
wWindow.addEventListener(air.Event.COMPLETE,linkScreenToMainWindow);
wWindow.load(new air.URLRequest(fhFilePath.url));
I've created a new window in Adobe Air (JS) and I need to capture any key-presses (or keydowns if it's easier). I have no problem adding an event listener to the main window, but any child window doesn't seem to recognize any of the common hook techniques.
Part of the problem, I think, is that the first parameter of addEventListener() is the name of the event, and all of the documented event names fail to raise any events. Any idea what how I'm supposed to do this?
Main window
// Keyboard handler and event listener subscription:
var watcher = function() {
alert("Working");
};
window.addEventListener("keypress",watcher,false); // WORKS!
// Create child window:
var wWindow = new air.NativeWindow(objWindowOptions);
wWindow.activate();
wWindow.stage.displayState = window.runtime.flash.display.StageDisplayState.FULL_SCREEN_INTERACTIVE;
wWindow.stage.scaleMode = "noScale";
wWindow.stage.addChild( htmlView );
htmlView.load( new air.URLRequest("newpage.html") );
Child window: newpage.html
// Keyboard handler and event listener subscription
var handler = function() {
alert('success!');
};
var strEventName = KeyboardEvent.KEY_DOWN; // Fails -- is undefined
//var strEventName = KeyboardEvent.KEYDOWN; // Fails -- is undefined
//var strEventName = 'keydown'; // Fails
// var strEventName = 1024; // Fails
window.nativeWindow.stage.addEventListener(strEventName,handler,false); // Fails
nativeApplication.addEventListener(strEventName,handler,false); // Fails
window.addEventListener(strEventName,handler,false); // Fails
I may be mistaken, but I think I've tried every permutation of the above and none of them work.
I think you're missing a step. Try:
var newWin = air.HTMLLoader.createRootWindow(...options);
var container = newWin.window.nativeWindow;
Otherwise, are you sure AIR is loaded in that child window?