Capturing Adobe Air keyboard events in new window - javascript

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?

Related

Automation script is not working?

This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.

Utilizing Firefox's default/built-in Event Listeners

I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined

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.

Inject custom function to onclick event

What I'm trying to do is to record all user activity on a given web page, so I'm using socket.io to send the events registered on the page, to the server with something like this:
$(document).on('click.mynamespace', function(event){
var elem = event.target;
socket.emit('my-message', { 'element' : elem };
}
The problem I'm facing is when the target element is a link of this kind My link. Whatever function is called and the page unloads (disconnecting my socket) before the socket.emit statement is executed properly.
I want to make this as universal as possible since this will be working as a plugin, and would like it to adjust to any kind of environment over which I will have no control.
So, is there a way to "highjack" all click events, send them first with my custom function, and then continue with the "normal" execution, whatever it may be?
EDIT
It seems only FF (tested on 14.0.1) causes the socket.emit event not to finish. Chrome 21.0.x seems to be working but not sure if this is by "chance".
EDIT 2
The function someFunctionThatRedirects actually redirects in this way window.location.ref = clickedurl
Events bubble upwards, so clicked element gets it's event fired before your socket.emit, you can change the way the functions work to make them do their actions in the order you want as follows
function someFunctionThatRedirects(){
window.redirectTo = 'myNewPage';
}
$(document).on('click.mynamespace', function(event){
var elem = $(event.target)[0];
socket.emit('my-message', { 'element' : elem };
if(window.redirectTo !== undefined) window.location.href = window.redirectTo;
}

MooTools events not firing in IE8

I have a Mootools asset created like so:
// Create a new asset
var asset = new Asset.image(path, {
title: this.language.download,
events: {click: this.download.bind(this, link)},
});
I have a method of a MooTools object defined as such:
download: function(e) {
// The path to download
console.log('download: ' + e);
},
In Firefox the console.log print shows up. In IE8, however, I have no luck. Am I missing something?
Any tips or advice would be greatly appreciated. TIA!
I have tried to implement this using the two methods described. Neither seem to work in IE8 or IE8 with compatibility mode.
var path = this.options.assetBasePath + 'disk.png';
var _this = this;
var icon = new Asset.image(path, {
/*onload: function() {
this.set({
title: _this.language.download,
events: {click: function() {
alert('test');
}
}
}).inject(el, 'top');
}*/
});
icon.set({title: _this.language.download,
events: {click: function() {
alert('test');
}
}
});
icon.inject(el, 'top');
icon.addClass('browser-icon');
icons.push(icon);
Both of these methods display an alert() just fine in FF but fail in IE.
In view of further communication via email... and the fact that the issue is caused by the use of filemanager mootools plugin by cpojer (of the mootools core team), I am updating my reply to the advice given via email as well.
source code in question: http://github.com/cpojer/mootools-filemanager/raw/master/Source/FileManager.js - line 408 is the problem
The reason why the original code may fail (in IE8) but i'd consider it unsafe as is anyway - is that events in mootools are UID driven per element. I.E. - if you inject an element or pass it through a selector and possibly create it via new Element() class (tbc), it assigns it a UID against which element storage is enabled. It is possible that the chaining used in the original form results in the addEvent running before the element exists which would cause it to fail. The issue here is WHY would it fail to attach the events when all tests I have conducted seem to work. Basically, the Asset.image code goes:
var image = new Image();
var element = document.id(image) || new Element('img');
element.store("foo", "bar");
alert(element.retrieve("foo") || "failed");
this DOES assign a UID and makes it available for events even before it's a member of the DOM. tescase: http://fragged.org/dev/ie8-new-image-bug.php - does that output 'bar' for you? if so, there is no reason to suspect foul play on the order of assignment of events vs DOM insertion due to storage anyway, it could be a total manipulation issue / onclick problem.
Either way, you can try replacing the code in the class with this, аlthough I have not tested it:
var _this = this;
icons.push(new Asset.image(this.options.assetBasePath + 'disk.png', {
title: this.language.download
"class": 'browser-icon',
onload: function() {
// once the src has been set and image loaded
// inject into DOM (hence open to manilulations) and then add the event
this.inject(el, 'top').addEvent('click', function(e) {
_this.tips.hide();
e.stop();
window.open(_this.options.baseURL + _this.normalize(_this.Directory + '/' + file.name));
});
}
}));
also to consider: IE has - in the past - been known to have issues with cached images being loaded up failing to trigger events if the assignment of the event is wrong way around:
// wrong:
var image = new Image();
image.src = 'image.jpg';
image.onload = function() { // evil, won't work if image already cached, it won't trigger
...
};
// right:
var image = new Image();
image.onload = function() { // always fires the event.
...
};
image.src = 'image.jpg';
this is just as a clue, mootools ensures the correct order of insertion vs event assignment anyway - but that does not mean that IE8 has no other similar issues to do with events.

Categories