I have these bits of code that are supposed to bug two functions and fire events for them. For some reason, however, the event does not seem to trigger. They log to the console as expected, but the event never gets fired.
//Show backstage event
(function( $, oldRem ){
backstage.show = function(){
console.log("yup, I'm the right one");
var resp = oldRem.apply( this, arguments );
$("#backstageArea").trigger("showBackstage");
return(resp);
};
})( jQuery, backstage.show );
//Hide backstage event
(function( $, oldRem ){
backstage.hide = function(){
console.log("And so am I.");
var resp = oldRem.apply( this, arguments );
if(anim && config.chkAnimate) {setTimeout( 'jQuery("#backstageArea").trigger("hideBackstage")', config.animDuration);}
else {$("#backstageArea").trigger("hideBackstage");}
return(resp);
};
})( jQuery, backstage.hide );
//Resize never logs its console message.
jQuery.bind("hideBackstage", function(){topbar.resize();});
jQuery.bind("showBackstage", function(){topbar.resize();});
You need to specify what element(s) to bind to, so try:
jQuery("#backstageArea").bind(...
instead of
jQuery.bind(...
(And if you're using jQuery version 1.7+ you may like to switch to the .on() method - will have the same effect for this purpose, but is the recommended way of doing things from v1.7 on.)
Related
I'm currently attempting to test some code that uses drag-and-drop. I found some other questions that were kinda related to this, but they were way too specific to help me, or not related enough.
This being a test, I'm struggling on trying to automatically execute code inside a .on('drop',function(e){....} event. The main issue is not that I can't run the code inside, but it's that I can't transfer the dataTransfer property, and I can't seem to fake it because it's read-only. Is there anyway to fake the dataTransfer property or otherwise get around it?
I came up with this JSFiddle that serves as a template of what I'm trying to do: https://jsfiddle.net/gnq50hsp/53/
Essentially if you are able to explain to me (if this is at all possible) how I can possibly fake the dataTransfer property, I should be all set.
Side notes:
I'm totally open to other ways of somehow getting inside that code, like for example, maybe its possible to trigger the event and pass in a fake event object with a fake dataTransfer object.
To see the drag-drop behavior, change the JavaScript load type from no-wrap head to on-Load, then you should see what I'm trying to simulate.
Important to note that I cannot modify any of the code inside the event handlers, only inside the outside function
Using Karma/Jasmine so use of those tools are also possible like spies
Also, I'm using Chrome.
Thanks in advance, and let me know for any questions/clarifications!
You should be able to override pretty much everything you want using Object.defineProperty. Depending on what you want to test it can be very simple or very complex. Faking the dataTransfer can be a bit tricky, since there's a lot of restrictions and behaviors linked to it, but if you simply want to test the drop function, it's fairly easy.
Here's a way, this should give you some ideas as to how to fake some events and data:
//Event stuff
var target = $('#target');
var test = $('#test');
test.on('dragstart', function(e) {
e.originalEvent.dataTransfer.setData("text/plain", "test");
});
target.on('dragover', function(e) {
//e.dataTransfer.setData('test');
e.preventDefault();
e.stopPropagation();
});
target.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
});
//What I want to simulate:
target.on('drop', function(e) {
console.log(e)
//Issue is that I can't properly override the dataTransfer property, since its read-only
document.getElementById('dataTransferDisplay').innerHTML = e.originalEvent.dataTransfer.getData("text");
});
function simulateDrop() {
// You'll need the original event
var fakeOriginalEvent = new DragEvent('drop');
// Using defineProperty you can override dataTransfer property.
// The original property works with a getter and a setter,
// so assigning it won't work. You need Object.defineProperty.
Object.defineProperty(fakeOriginalEvent.constructor.prototype, 'dataTransfer', {
value: {}
});
// Once dataTransfer is overridden, you can define getData.
fakeOriginalEvent.dataTransfer.getData = function() {
return 'test'
};
// TO have the same behavior, you need a jquery Event with an original event
var fakeJqueryEvent = $.Event('drop', {
originalEvent: fakeOriginalEvent
});
target.trigger(fakeJqueryEvent)
}
https://jsfiddle.net/0tbp4wmk/1/
As per jsfiddel link you want to achieve drag and drop feature. jQuery Draggable UI already provides this feature why you can not use that?
For create custom event on your way you have to follow two alternative ways
$('your selector').on( "myCustomEvent", {
foo: "bar"
}, function( event, arg1, arg2 ) {
console.log( event.data.foo ); // "bar"
console.log( arg1 ); // "bim"
console.log( arg2 ); // "baz"
});
$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] );
On above example
In the world of custom events, there are two important jQuery methods: .on() and .trigger(). In the Events chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things:
.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
OR
jQuery.event.special.multiclick = {
delegateType: "click",
bindType: "click",
handle: function( event ) {
var handleObj = event.handleObj;
var targetData = jQuery.data( event.target );
var ret = null;
// If a multiple of the click count, run the handler
targetData.clicks = ( targetData.clicks || 0 ) + 1;
if ( targetData.clicks % event.data.clicks === 0 ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = handleObj.type;
return ret;
}
}
};
// Sample usage
$( "p" ).on( "multiclick", {
clicks: 3
}, function( event ) {
alert( "clicked 3 times" );
});
On above example
This multiclick special event maps itself into a standard click event, but uses a handle hook so that it can monitor the event and only deliver it when the user clicks on the element a multiple of the number of times specified during event binding.
The hook stores the current click count in the data object, so multiclick handlers on different elements don't interfere with each other. It changes the event type to the original multiclick type before calling the handler and restores it to the mapped "click" type before returning:
I'm trying to capture an event from a plugin:
$(document).ready(function( ) {
CKEDITOR.replaceAll( 'js-ckeditor' );
CKEDITOR.editor.on('simpleuploads.startUpload', function (ev) {
console.log(ev.data)
});
});
...but when I run it, I see the error TypeError: CKEDITOR.editor.on is not a function. What am I doing wrong here? According to the documentation editor should have a method "on" (which I can see when I inspect the object). By the way, both the textarea and plugin function well so replaceAll seems to work fine.
CKEDITOR.editor.on is a method of a working editor instance, i.e. returned by CKEDITOR.replace, while CKEDITOR.editor is just editor constructor.
Instead attach CKEDITOR.instanceReady event before CKEDITOR.replaceAll and use event data to attach more events to particular editor instances:
CKEDITOR.on( 'instanceReady', function( evt ) {
evt.editor.on( 'simpleuploads.startUpload', function( evt ) {
...
} );
} );
CKEDITOR.replaceAll( 'js-ckeditor' );
I'm sure we've all seen the site for vanilla-js (the fastest framework for JavaScript) ;D and I was just curious, exactly how much faster plain JavaScript was than jQuery at adding an event handler for a click. So I headed on over to jsPerf to test it out and I was quite surprised by the results.
jQuery outperformed plain JavaScript by over 2500%.
My test code:
//jQuery
$('#test').click(function(){
console.log('hi');
});
//Plain JavaScript
document.getElementById('test').addEventListener('click', function(){
console.log('hi');
});
I just can't understand how this would happen because it seems that eventually jQuery would end up having to use the exact same function that plain JavaScript uses. Can someone please explain why this happens to me?
As you can see in this snippet from jQuery.event.add it does only create the eventHandle once.
See more: http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.event.add
// Init the element's event structure and main handler, if this is the first
events = elemData.events;
if (!events) {
elemData.events = events = {};
}
eventHandle = elemData.handle;
if (!eventHandle) {
elemData.handle = eventHandle = function (e) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) : undefined;
};
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}
And here we have the addEventListener:
// Init the event handler queue if we're the first
handlers = events[type];
if (!handlers) {
handlers = events[type] = [];
handlers.delegateCount = 0;
// Only use addEventListener/attachEvent if the special events handler returns false
if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
// Bind the global event handler to the element
if (elem.addEventListener) {
elem.addEventListener(type, eventHandle, false);
} else if (elem.attachEvent) {
elem.attachEvent("on" + type, eventHandle);
}
}
}
I think it's because internally jQuery really only has to call addEventListener() once, for its own internal handler. Once that's set up, it just has to add your callback to a simple list. Thus most of the calls to .click() just do some bookkeeping and a .push() (or something like that).
Consider a basic addEventListener as
window.onload=function(){
document.getElementById("alert")
.addEventListener('click', function(){
alert("OK");
}, false);
}
where <div id="alert">ALERT</div> does not exist in the original document and we call it from an external source by AJAX. How we can force addEventListener to work for newly added elements to the documents (after initial scan of DOM elements by window.onload)?
In jQuery, we do this by live() or delegate(); but how we can do this with addEventListener in pure Javascript? As a matter of fact, I am looking for the equivalent to delegate(), as live() attaches the event to the root document; I wish to make a fresh event listening at the level of parent.
Overly simplified and is very far away from jQuery's event system but the basic idea is there.
http://jsfiddle.net/fJzBL/
var div = document.createElement("div"),
prefix = ["moz","webkit","ms","o"].filter(function(prefix){
return prefix+"MatchesSelector" in div;
})[0] + "MatchesSelector";
Element.prototype.addDelegateListener = function( type, selector, fn ) {
this.addEventListener( type, function(e){
var target = e.target;
while( target && target !== this && !target[prefix](selector) ) {
target = target.parentNode;
}
if( target && target !== this ) {
return fn.call( target, e );
}
}, false );
};
What you are missing on with this:
Performance optimizations, every delegate listener will run a full loop so if you add many on a single element, you will run all these loops
Writable event object. So you cannot fix e.currentTarget which is very important since this is usually used as a reference to some instance
There is no data store implementation so there is no good way to remove the handlers unless you make the functions manually everytime
Only bubbling events are supported, so no "change" or "submit" etc which you took for granted with jQuery
Many others which I'm simply forgetting about for now
document.addEventListener("DOMNodeInserted", evtNewElement, false);
function evtNewElement(e) {
try {
switch(e.target.id) {
case 'alert': /* addEventListener stuff */ ; break;
default: /**/
}
} catch(ex) {}
}
Note: according to the comment of #hemlock, it seems this family of events is deprecated. We have to head towards mutation observers instead.
I want to decorate or wrapper a specific onclick callback such that my code is executed after the original installed callback is called.
I started with this in order to do it in a semi-python'ic way using function generator with closure:
var original_onclick = node.onclick;
function wrapper_onclick() {
original_onclick.apply(this,arguments);
my_code();
}
node.onclick = wrapper_onclick;
However, when I use the above code, the original callbacks aren't working properly. Am I passing in the wrong first parameter (context parameter?) this. Do I need to tweak the arguments? Is this already in list arguments?
This is exactly why lots of people worked to find a proper solution for cross browser event listening. Some browsers don't fire the function in the right context, some browsers don't pass the event argument and so on. It's a lot easier to use a framework, but if you insist on using no frameworks...
First if you have really horrible code like:
<a href="javascript:someFunction( this , evaledVariable );otherFn( ohDearVariable )">
Then sorry, no one can help you.
Otherwise...
In any case, you can't attach events directly to a node by simply setting the .onclick attribute and expect it to behave equally cross browser or even work properly in the browser you're developing with. You must use both addEventListener and attachEvent methods.
The most simplest way of forking for cross browser events is:
var addListener = function( node , event , listener ) {
if ( node.addEventListener ) {
node.addEventListener( event , listener , false );
} else if (node.attachEvent ) {
node.attachEvent( "on" + event , listener );
}
}
Same for removing listeners:
var removeListener = function( node , event , listener ) {
if ( node.addEventListener ) {
node.removeEventListener( event , listener , false );
} else if (node.attachEvent ) {
node.detachEvent( "on" + event , listener );
}
}
And use it thus:
var nodeListener = function( e ) {
alert(e.target);
// run once for demo
removeListener( node , "click" , nodeListener );
}
// notice we omit the "on" part.
addListener( node , "click" , nodeListener );
So if you want to wrap listeners appended to node the WRONG onclick way:
var wrongOnClick = node.onclick;
var listener = function( e ) {
before();
wrongOnClick.call( this , e );
after();
};
addListener( node , "click" , listener );
This will serve most modern browsers including IE, though actually a lot more optimalisation and memory leak prevention code is needed for a good implementation, but instead of writing this code yourself or having others do it for you, use a framework/tookit! Because by the time you have a good implementation running that works cross browser, you will have more code bloat then when you actually used a framework...
** Addendum **
As a hack, I still suggest to use the addEventListener method and keep references to your listeners.
var aListener = function( e ) { }
node.addEventListener( "click" , aListener , false );
var wrappedListener = function ( e ) {
before( e );
aListener( e );
after( e );
}
node.removeEventListener( "click" , aListener , false );
node.addEventListener( "click" , wrappedListener , false );
This obviously only works with your listeners, if you want it to wrap existing listeners in a page (listeners from others), then there's simply no way of knowing how people appended their listeners to nodes. If they used addEventListener/attachEvent libraries or the dirty example in the beginning of this post, you simply cannot reliably wrap them without manual coding and looking up the references or manually exposing them.
Based on help from Matt and BGerrissen, the following code works great for chrome:
var original_onclick = node.onclick;
function wrapper_onclick(e ) {
var ret = original_onclick(e);
remove_story_onclicks();
return ret;
}
node.onclick = wrapper_onclick;
Still not exactly sure how to do a bulletproof wrapper like the following in python:
def wrappee(foo,bar,baz):
print foo,bar,baz
def wrapper(*args,**kwargs):
print 'before'
wrappee(*args,**kwargs)
print 'after'
wrapper('a','b','c')
which prints
before
a b c
after