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', {});
Related
I need to attach an Event called render to a panel element, that does nothing but being dispatched to warn all the listeners whenever panel is rendering.
Following the The old-fashioned way section of this link, I came up with this code:
/**
* **Static** Re-draw the layer panel to represent the current state of the layers.
* #param {Element} panel The DOM Element into which the layer tree will be rendered
*/
static renderPanel(panel) {
// Create the event.
var render_event = document.createEvent('Event');
// Define that the event name is 'render'.
render_event.initEvent('render', true, true);
// Listen for the event.
panel.addEventListener('render', function (e) {
// e.target matches panel
}, false);
panel.dispatchEvent(render_event);
This seems to have worked but as this is my first time doing this, I am not quite sure how to check the correctness of this method.
Looking inside the console I can see my panel element dispatching the render Event, but I'd like to ask if there's something I am missing or to be worried about before moving on.
To debug the result, I tried add an event listener to the document element like document.addEventListener("render",console.log("ciao")), which in turn printed ciao once in the console, but only just once.
I thought I would be able to see as many "ciao" in the console as the times the render Event was triggered, but this does not seem the case.
If you're trying to check everytime your event is fired, the second argument of addEventListener (taking into account what you're willing to achieve) should be a function callback using an event object as argument, like this for example:
document.addEventListener("render", function(e) { console.log("ciao"); });
In your example you're executing console.log("ciao"), not passing a function reference (anonymous or not), this is why it executes only one time: when the page loads/evaluates your script.
mdn guide on creating and dispatching custom events (same as your link)
The old fashioned method seems to still be working fine when I tried it, I saw the document event listener console log each time I triggered the event.
The updated way is:
panel.dispatchEvent(new CustomEvent('render'));
let div = document.querySelector('div');
div.addEventListener('old-event', () => {console.log('Old-fashinoed event caught')});
div.addEventListener('new-event', () => {console.log('New-fashioned event caught')});
let oldEvent = document.createEvent('Event');
oldEvent.initEvent('old-event', true, true);
let newEvent = new CustomEvent('new-event');
setInterval(() => {
div.dispatchEvent(oldEvent);
div.dispatchEvent(newEvent);
}, 1000);
<div>I emit an old-fashioned and a new-fashioned event every 1 second</div>
I have two simple event handling Observables that I set up on mousedown.
Mouseup triggers several actions in the application, depending on whether or not the element is moved on mousemove, so i need to prevent it on simple click.
My impression was, that this code should start firing mouseup only after 500 mousemoves have been skipped and one emitted, however, 'mouseup' is logged even without single 'mousemove' being logged, and immediately (even with skip() set to even more ridiculous numbers).
What am I missing?
var mouseupObservable = Observable.fromEvent(this.element, 'mouseup');
var mouseMoveObservable = Observable.fromEvent(window, 'mousemove');
mouseupObservable
.skipUntil(mouseMoveObservable)
.subscribe(()=>console.error('mouseup'));
mouseMoveObservable
.takeUntil(mouseupObservable)
.skip(500)
.subscribe(()=>console.error('mousemove'));
The thing is that you use the "raw" streams inside the skip, try this:
var mouseupObservable = Observable.fromEvent(this.element, 'mouseup');
var mouseMoveObservable = Observable.fromEvent(window, 'mousemove');
let mouseUp$ = mouseupObservable
.skipUntil(mouseMoveObservable.skip(500));
mouseUp$.subscribe(()=>console.error('mouseup'));
let mouseMove$ = mouseMoveObservable
.takeUntil(mouseUp$);
mouseMove$.subscribe....
But please keep in mind that the "takeUntil(...)" will complete your stream whe fulfilled....this means that this will work only for a single "move-click-cycle" - not sure if that is your intention.
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
I am developing an HTML5 application with EaselJS that uses a canvas element covering the entire window to display its interface. I'm using the following function to fix canvas bounds in case user resizes the window:
function resizeCanvas(event) {
if (window.onresize == null) window.onresize = resizeCanvas;
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
stage.update();
}
However, when canvas bounds change, so must some of its elements (children of the createjs.Stage() object). Ideally, I'd like resizeCanvas() to simply dispatch an event and have the appropriate stage.children() objects respond. I tried adding the following the line in the above function: stage.dispatchEvent(event) and creating the shape objects like this:
var grid = new createjs.Shape(drawGrid());
grid.addEventListener('resize', function(evt) { alert(evt); }, false);
stage.addChild(grid);
This, however does not work and it seems I grossly misunderstand Events.
The addEventListener() will listen for events dispatched by the object itself, not received by an object, so your line:
grid.addEventListener('resize', function(evt) { alert(evt); }, false);
will alert whenever the grid dispatches a resize-event, not when it receives one, so you would have to use grid.dispatchEvent('resize') in order to get the alert, in which case.. you already have the object, why dispatch the evnt, if you can call the size-method right away - it's like a chicken-egg issue in this case, it would make sense though if the grid had children listening to the event.
If you really want to work with events here, you'd have to add a listener to a central object(probably the stage) for each element that you want to resize, like this:
stage.addEventListener('resize', function(evt) { grid.resize(); });
// the grid-object ofc needs a resize-method that handles the event
// also: anonymous functions make this way a complete hassle, but
// you will very likely need the scope of the 'grid' here...
and finally you will need to have the stage dispatch the resize-event, so right befor you do that stage.update() in your resizeCanvas() you add:
stage.dispatchEvent('resize');
// optionally you can dispatch with an object that contains additional data
What I would do (and I couldn't tell if this way is better or worse, it's just how I would do it):
I would write a recursive method to call a resize-method on every child and their children, started by the stage instead of using events.
I have Javascript that people are including in their page. In my Javascript I have a version of jQuery (1.8 for sake of easy reference) that is sectioned off into its own namespace, and referenced via a global variable (but not one of the two default vars of "$" or "jQuery"). This allows users to have jQuery in their page and have it not interfere with the stuff I'm doing internally in my functions.
So we have one page that has jQuery already (1.4), and everything works fine, except that the user and my code are both listening to "click" events on elements, and theirs is going first, so on the few events they do that return false, jQuery stops propagation and my event never gets triggered. I need my event to go first. The user is expecting my onClick functionality to still work.
Now I know that jQuery keeps its own order of events internally through the _data() object, and through this it is possible to unbind existing events, bind my event, then rebind the existing events, but that only applies to objects bound through that instance of jQuery. I'd rather not just blindly look for the jQuery object in hopes that the conflict was introduced by a user's own version of jQuery. After all what happens when a user binds the event not through jQuery? Trying to manipulate the existing jQuery object in the page isn't a good solution.
I know that, depending on browser, they are using addEventListener/removeEventListener or attachEvent/detachEvent. If only I could get a listing of the already added events, I could rebind them in the order I wanted, but I can't find out how. Looking through the DOM via Chrome inspect I don't see onclick bound anywhere (not on the object, not on window or document either).
I'm having the darndest time trying to figure out just exactly where jQuery binds its listening. To be able to control the order of its own events, jQuery must blanketly listen somewhere and then fire off its own functions right? If I could figure out where that's done I might get some insight into how to ensure my event is always first. Or maybe there's some Javascript API I haven't been able to find on Google.
Any suggestions?
We solved this by just adding a little jQuery extension that inserts events at the head of the event chain:
$.fn.bindFirst = function(name, fn) {
var elem, handlers, i, _len;
this.bind(name, fn);
for (i = 0, _len = this.length; i < _len; i++) {
elem = this[i];
handlers = jQuery._data(elem).events[name.split('.')[0]];
handlers.unshift(handlers.pop());
}
};
Then, to bind your event:
$(".foo").bindFirst("click", function() { /* Your handler */ });
Easy peasy!
As Bergi and Chris Heald said in the comments, it turns out there's no way to get at the existing events from the DOM, and no method to insert events "first". They are fired in the order they were inserted by design, and hidden by design. As a few posters mentioned you have access to the ones added through the same instance of jQuery that you're using via jQuery's data, but that's it.
There is one other case where you can run before an event that was bound before your code ran, and that's if they used the "onclick" HTML attribute. In that case you can write a wrapper function, as nothingisnecessary pointed out in a rather over-the-top toned comment below. While this wouldn't help in the instance of the original question I asked, and it's now very rare for events to be bound this way (most people and frameworks use addEvent or attachEventListener underneath now), it is one scenario in which you can solve the issue of "running first", and since a lot of people visit this question looking for answers now, I thought I'd make sure the answer is complete.
I encounter an opposite situation where I was asked to include a library, which uses event.stopImmediatePropagation() on an element, to our website. So some of my event handlers are skipped. Here is what I do (as answered here):
<span onclick="yourEventHandler(event)">Button</span>
Warning: this is not the recommended way to bind events, other developers may murder you for this.
Its not a proper solution, but ... You can add event handler to parent node in capture phase. Not on target element itself!
<div>
<div id="target"></div>
</div>
target.parentNode.addEventListener('click',()=>{console.log('parent capture phase handler')},true)
Third argument in addEventListener means:
true - capture phase
false - bubble phase
Helpful links:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
https://javascript.info/bubbling-and-capturing
Found it easiest to add addListener and removeListener methods to document (as that's only where I need them - I suppose you can use Element.prototype and this instead). Only one "real" listener is added per type, and it's just a func to call the actual listeners in order. The eventListeners dictionary is added to document (so can mess with the handler or order).
[edit]
I think the correct answer for most cases is to use the 3rd argument of addEventListener: https://stackoverflow.com/a/29923421. The answer below ignores the argument (on purpose).
[edit] Updated code to only add one extra property: document.eventHandlers + modified naming.
// Storage.
document.eventListeners = {}; // { type: [ handlerFunc, listenerFuncs ] }
// Add event listener - returns index.
document.addListener = (type, listener, atIndex) => {
// Get info.
const listening = document.eventListeners[type];
// Add to existing.
if (listening) {
// Clean up.
atIndex = atIndex || 0;
const listeners = listening[1]; // Array of funcs.
// Already has.
const iExists = listeners.indexOf(listener);
if (iExists !== -1) {
// Nothing to do.
if (iExists === atIndex)
return atIndex;
// Remove from old position.
listeners.splice(atIndex, 1);
}
// Add (supporting one cycle of negatives).
const nListeners = listeners.length;
if (atIndex > nListeners)
atIndex = nListeners;
else if (atIndex < 0)
atIndex = Math.max(0, atIndex + nListeners + 1);
listeners.splice(atIndex, 0, listener);
}
// New one.
else {
// Handler func.
const handler = (...args) => {
const listening = document.eventListeners[type];
if (listening) {
const listeners = listening[1]; // Array of funcs.
for (const listener of listeners)
listener(...args);
}
};
// Update dictionary.
document.eventListeners[type] = [ handler, [ listener ] ];
// Add listener.
document.addEventListener(type, handler);
// First one.
atIndex = 0;
}
// Return index.
return atIndex;
};
// Remove event listener - returns index (-1 if not found).
document.removeListener = (type, listener) => {
// Get info.
const listening = document.eventListeners[type];
if (!listening)
return -1;
// Check if exists.
const listeners = listening[1];
const iExists = listeners.indexOf(listener);
if (iExists !== -1) {
// Remove listener.
listeners.splice(iExists, 1);
// If last one.
if (!listeners.length) {
// Remove listener.
const handlerFunc = listening[0];
document.removeEventListener(type, handlerFunc);
// Update dictionary.
delete document.eventListeners[type];
}
}
// Return index.
return iExists;
}
Aliaksei Pavlenkos suggestion about useCapture can be used. His allegation that it must be attached to the parent node is wrong: MDN
Event listeners in the “capturing” phase are called before event listeners in any non-capturing phases
target.addEventListener(type, listener, useCapture);
Just so it's said, I think this might be possible if you override the native implementations of these functions. This is BAD practice - very bad practice when developing a library to alter native implementations, because it can easily conflict with other libraries.
However, for completeness, here's one possibility (completely untested, just demonstrating the general concept):
// override createElement()
var temp = document.createElement;
document.createElement = function() {
// create element
var el = document.createElement.original.apply(document, arguments);
// override addEventListener()
el.addEventListenerOriginal = el.addEventListener;
el._my_stored_events = [];
// add custom functions
el.addEventListener = addEventListenerCustom;
el.addEventListenerFirst = addEventListenerFirst;
// ...
};
document.createElement.original = temp;
// define main event listeners
function myMainEventListeners(type) {
if (myMainEventListeners.all[type] === undefined) {
myMainEventListeners.all[type] = function() {
for (var i = 0; i < this._my_stored_events.length; i++) {
var event = this._my_stored_events[i];
if (event.type == type) {
event.listener.apply(this, arguments);
}
}
}
}
return myMainEventListeners.all[type];
}
myMainEventListeners.all = {};
// define functions to mess with the event list
function addEventListenerCustom(type, listener, useCapture, wantsUntrusted) {
// register handler in personal storage list
this._my_stored_events.push({
'type' : type,
'listener' : listener
});
// register custom event handler
if (this.type === undefined) {
this.type = myMainEventListeners(type);
}
}
function addEventListenerFirst(type, listener) {
// register handler in personal storage list
this._my_stored_events.push({
'type' : type,
'listener' : listener
});
// register custom event handler
if (this.type === undefined) {
this.type = myMainEventListeners(type);
}
}
// ...
A lot more work would need to be done in this regard to truly lock this down, and again, it's best not to modify native libraries. But it's a useful mental exercise that helps to demonstrate the flexibility JavaScript provides in solving problems like this.