I have seen many JQuery functions using parameter function parameters. Even though I use them, It feels rather incomplete without knowing how this work in the back end.
as an example lets use the click method :
$("p").click(function(event){
event.preventdefault();
alert("testing 123");
});
In this code, if I used, "this" inside the method, it will give me the "p" element itself.
However, I cannot figure out the "event" parameter get assigned with something.
shouldn't there be some place with a code bit like
var event = blah blah;
so that the event parameter has some values?
I have clicked the actual JQuery file by pressing f12 and it goes to a function like this
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
I cannot see any place of that filling or assigning something to the parameter named "event"
I have the same problem with $.ajax requests as well,
$.ajax({
type: "POST",
url: url,
async: false,
success: function (data) { }
});
It is visible that there is someplace loading the "data" in the "data" parameter, How and where does the actual data get filled up in? when do we load actual data.
I have seen a bit of similar problem.
The declaration happens in the function parameters themselves.
Declaring data, event, or whatever you want to call it inside the function parameters (any word will work), is effectively the var data = ... statement.
In the instance of an event handler, event is passed by the browser to any function latching on to that event. In the case of the ajax call, as #Alec said, that is the data returning from the server.
jQuery is basically a wrapper that returns an object with many methods. Most of them are not that straightforward, if you want to understand more deeply, you don't have many choices except using the console and going through the source code which you can find here: https://code.jquery.com/jquery-1.12.4.js. Ideally, use uncompressed version. For some methods, it can be quite long to get to the bottom of it. The way the Click callback works is hidden pretty deep.
You can find it this way:
In the console, enter $("p").click. You'll get:
ƒ ( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
}
Which comes from here in the source code:
jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu" ).split( " " ),
function( i, name ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
} );
So because you have at least an argument, it runs: this.on( name, null, data, fn ), where this is your jQuery object $('p'), name is 'click', and data is your function(event). So onto this.on():
console.log($('p').on);
ƒ ( types, selector, data, fn ) {
return on( this, types, selector, data, fn );
}
Here, function on isn't global, so it's in jQuery's closure, so back to the source code where you can find:
function on( elem, types, selector, data, fn, one ) {
...
where elem is your jQuery object $('p'), types is 'click', selector is null, data is your function(e) and fn is null. This leads to:
elem.each( function() {
jQuery.event.add( this, types, fn, data, selector );
} );
So you can find:
jQuery.event = {
global: {},
add: function( elem, types, handler, data, selector ) {
...
Where you can find an addEventListener :
elem.addEventListener( type, eventHandle, false );
On addEventListener, the callback has the event parameter, which is native javascript. In jQuery, the callback is eventHandle, so let's find this one:
eventHandle = elemData.handle = 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;
};
So it returns the function dispatch, so now the callback is this :
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) , where arguments is e (the original addEventListener Event). So find dispatch:
dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
event = jQuery.event.fix( event );
...
So what is this event.fix:
fix: function( event ) {
if ( event[ jQuery.expando ] ) {
return event;
}
// Create a writable copy of the event object and normalize some properties
var i, prop, copy,
type = event.type,
originalEvent = event,
fixHook = this.fixHooks[ type ];
In here you find
event = new jQuery.Event( originalEvent );
jQuery.Event = function( src, props ) {
...
Where the event that is passed as a parameter of click is defined. You can test it by adding properties on jQuery.Event.prototype. Like this for example:
jQuery.Event.prototype.prop = 'newProp';
So, to sum up, the event in function(event), is an instance of jQuery.Event.
See
console.log($('p').click);
console.log($('p').on);
console.log(jQuery.Event)
jQuery.Event.prototype.prop = 'test';
$('p').click(function(event){console.log(event.prop)});
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<p>test</p>
For Ajax, it's probably a bit more straightforward, but again, if you want to know exactly, not much else you can do but go through the source code.
In reference to your first question, about the event parameter, the event is your click. It will never be explicitly declared like a normal variable. It is just a parameter, and in your example the click is the event.
In reference to your ajax question, the data parameter is what is coming back from your backend after a successful post. For example, I use ajax calls to send some information from my frontend. My backend then uses that information to send back data to frontend inside that success: function(data), like JSON. JSON would be the data parameter.
Related
Does anyone know the magic required to get jQuery .trigger() to trigger a custom event that's handled by a (not jQuery) native JavaScript event handler?
test = document.querySelectorAll('.test')[0];
test.addEventListener('click', function() {
console.log('click')
});
test.addEventListener('custom', function(ev) {
console.log('custom', ev.detail)
});
// Custom Native -> Native works as expected
test.dispatchEvent(new CustomEvent('custom', {detail: 'detail'})); // -> "custom" "detail"
// Standard jQuery -> Native works as expected
$(test).trigger('click'); // -> "click"
// Custom jQuery -> Native does not work
$(test).trigger('custom'); // -> No log?
$(test).trigger({type: 'custom'}); // -> No log?
codepen.io live example
Edited to add:
A bit more details on my use case. I'm developing a library that relies on custom events but doesn't itself use jQuery. However, I'd like to make the library convenient for those applications that do have jQuery.
Well, after stepping through the jQuery source in a debugger, it looks like there is a solution. Not elegant, but workable. The trick is to add an onxxxx property to the element, where xxxx is the event name. The addition to the code in the question would be:
test.oncustom = function(ev, data) {
// ev is the jQuery Event object
// data is data passed to jQuery `.trigger()`
}
Note that jQuery does not add custom data to, for example, ev.detail, as would be the case for a standard event. Instead it passes custom data as an additional parameter.
My idea is to create a plugin which will serve as a wrapper around trigger function in jquery:
(function($) {
$.fn.extend({
trigger: function(type, data) {
return this.each(function() {
if (typeof type == "string" && type.startsWith("test:")) {
this.dispatchEvent(new window.CustomEvent(type, data));
}else{
jQuery.event.trigger(type, data, this)
}
});
}
});
})(jQuery);
It is slightly modified code from: https://github.com/jquery/jquery/blob/master/src/event/trigger.js#L185
Assuming that you add handler as follows:
test.addEventListener('test:custom', function(ev) {
console.log('test:custom', ev.detail)
});
You can dispatch it by:
$(test).trigger('test:custom', { detail: 'jquery'});
The downside is that you need to prefix all your custom events with some kind of namespace.
JSFiddle
https://learn.jquery.com/events/introduction-to-custom-events/
At the end of the webpage see:
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
$( document ).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" ] );
It's not a magic. The problem located in jQuery's resolving procedure on elem[type]. Your test element doesen't have custom handler but instead has a native click handler.
So, your dirty-fix might look such as:
**test.custom = function () {console.log('custom fixed')};**
Please have a look at a code-snippet from jquery-1.7.2.js below:
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
// IE<9 dies on focus/blur to hidden element (#1486)
if (ontype && elem[type] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow(elem)) {
// Don't re-trigger an onFOO event when we call its FOO() method
old = elem[ontype];
if (old) {
elem[ontype] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
elem[type]();
jQuery.event.triggered = undefined;
if (old) {
elem[ontype] = old;
}
}
Does anyone know the magic required to get jQuery .trigger() to trigger a custom event that's handled by a (not jQuery) native JavaScript event handler?
test = document.querySelectorAll('.test')[0];
test.addEventListener('click', function() {
console.log('click')
});
test.addEventListener('custom', function(ev) {
console.log('custom', ev.detail)
});
// Custom Native -> Native works as expected
test.dispatchEvent(new CustomEvent('custom', {detail: 'detail'})); // -> "custom" "detail"
// Standard jQuery -> Native works as expected
$(test).trigger('click'); // -> "click"
// Custom jQuery -> Native does not work
$(test).trigger('custom'); // -> No log?
$(test).trigger({type: 'custom'}); // -> No log?
codepen.io live example
Edited to add:
A bit more details on my use case. I'm developing a library that relies on custom events but doesn't itself use jQuery. However, I'd like to make the library convenient for those applications that do have jQuery.
Well, after stepping through the jQuery source in a debugger, it looks like there is a solution. Not elegant, but workable. The trick is to add an onxxxx property to the element, where xxxx is the event name. The addition to the code in the question would be:
test.oncustom = function(ev, data) {
// ev is the jQuery Event object
// data is data passed to jQuery `.trigger()`
}
Note that jQuery does not add custom data to, for example, ev.detail, as would be the case for a standard event. Instead it passes custom data as an additional parameter.
My idea is to create a plugin which will serve as a wrapper around trigger function in jquery:
(function($) {
$.fn.extend({
trigger: function(type, data) {
return this.each(function() {
if (typeof type == "string" && type.startsWith("test:")) {
this.dispatchEvent(new window.CustomEvent(type, data));
}else{
jQuery.event.trigger(type, data, this)
}
});
}
});
})(jQuery);
It is slightly modified code from: https://github.com/jquery/jquery/blob/master/src/event/trigger.js#L185
Assuming that you add handler as follows:
test.addEventListener('test:custom', function(ev) {
console.log('test:custom', ev.detail)
});
You can dispatch it by:
$(test).trigger('test:custom', { detail: 'jquery'});
The downside is that you need to prefix all your custom events with some kind of namespace.
JSFiddle
https://learn.jquery.com/events/introduction-to-custom-events/
At the end of the webpage see:
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
$( document ).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" ] );
It's not a magic. The problem located in jQuery's resolving procedure on elem[type]. Your test element doesen't have custom handler but instead has a native click handler.
So, your dirty-fix might look such as:
**test.custom = function () {console.log('custom fixed')};**
Please have a look at a code-snippet from jquery-1.7.2.js below:
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
// IE<9 dies on focus/blur to hidden element (#1486)
if (ontype && elem[type] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow(elem)) {
// Don't re-trigger an onFOO event when we call its FOO() method
old = elem[ontype];
if (old) {
elem[ontype] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
elem[type]();
jQuery.event.triggered = undefined;
if (old) {
elem[ontype] = old;
}
}
I am trying to hook css get/set. However after installing a simple pass thru hook it seems to be not called: (no error messages in Chrome console)
$.cssHooks['padding-left'] = {
get: function( elem, computed, extra ) {
return $.css( elem, $['padding-left'] );
},
set: function( elem, value) {
// Setting breakpoint here in Chrome, this line is not called:
elem.style[ $.support['padding-left'] ] = value;
}
};
// Proof of concept: Should not this statement initiate the hooked setter call?
// This statement is called, confirmed via breakpoint
$('#et-top-navigation').css('padding-left', '0px');
What am I missing?
Use the camel-case javascript property name rather than the hyphenated css style.
$.cssHooks['paddingLeft'] = { ...
You need wrap all of that in a document ready call, because jQuery writes cssHooks at this time and will get rid of your functions if they exist.
Take a look at the skeleton template in the API
https://api.jquery.com/jQuery.cssHooks/
(function( $ ) {
// First, check to see if cssHooks are supported
if ( !$.cssHooks ) {
// If not, output an error message
throw( new Error( "jQuery 1.4.3 or above is required for this plugin to work" ) );
}
// Wrap in a document ready call, because jQuery writes
// cssHooks at this time and will blow away your functions
// if they exist.
$(function () {
$.cssHooks[ "someCSSProp" ] = {
get: function( elem, computed, extra ) {
// Handle getting the CSS property
},
set: function( elem, value ) {
// Handle setting the CSS value
}
};
});
})( jQuery );
Can somebody tell how to "unbind" an anonymous function?
In jQuery it's capable to do that, but how can I implement this Functionality in my own script.
This is the scenario:
The following code attach a onclick event to the Div which have someDivId as ID, now when you click the DIV, it's showing 'clicked!'.
var a = document.getElementById('someDivId');
bindEvent(a,'click',function(){alert('clicked!');});
That's all great, the problem is how to "un-attach" the Function to the DIV if the function is anonymous or how to "un-attach" all attached events to the 'a' Element?
unBind(a,'click'); //Not necessarily the given params, it's just an example.
This is the code for bindEvent Method:
function bindEvent (el,evtType,fn){
if ( el.attachEvent ) {
el['e'+evtType+fn] = fn;
el[evtType+fn] = function(){
fn.call(el,window.event);
}
el.attachEvent( 'on'+evtType, el[evtType+fn] );
} else {
el.addEventListener( evtType, fn, false );
}
}
Finally, and after hours of Test&Errors i have found a solution, maybe it's not the best or most efficient but... IT WORKS! (Tested on IE9, Firefox 12, Chrome 18)
First all I'v create two cross-browser and auxiliary addEvent() and removeEvent() methods. (Idea taken from Jquery's source code!)
HELPERS.removeEvent = document.removeEventListener ?
function( type, handle,el ) {
if ( el.removeEventListener ) {
//W3C Standard
el.removeEventListener( type, handle, true );
}
} :
function( type, handle,el ) {
if ( el.detachEvent ) {
//The IE way
el.detachEvent( 'on'+type, el[type+handle] );
el[type+handle] = null;
}
};
HELPERS.addEvent = document.addEventListener ?
function( type, handle,el ) {
if ( el.addEventListener ) {
//W3C Standard
el.addEventListener( type, handle, true );
}
} :
function( type, handle,el ) {
if ( el.attachEvent ) {
//The IE way
el['e'+type+handle] = handle;
el[type+handle] = function(){
handle.call(el,window.event);
};
el.attachEvent( 'on'+type, el[type+handle] );
}
}
Also we need some kind of 'container' to store the attached events to elements, like this:
HELPERS.EVTS = {};
And finally the two callable and exposed to the users Methods:
The next one to add an Event(event) and associate this Event to a Method (handler) for a specific Element (el).
function bindEvent(event, handler,el) {
if(!(el in HELPERS.EVT)) {
// HELPERS.EVT stores references to nodes
HELPERS.EVT[el] = {};
}
if(!(event in HELPERS.EVT[el])) {
// each entry contains another entry for each event type
HELPERS.EVT[el][event] = [];
}
// capture reference
HELPERS.EVT[el][event].push([handler, true]);
//Finally call the aux. Method
HELPERS.addEvent(event,handler,el);
return;
}
Lastly the method that un-attach every pre-attached events (event) for an specific Element (el)
function removeAllEvent(event,el) {
if(el in HELPERS.EVT) {
var handlers = HELPERS.EVT[el];
if(event in handlers) {
var eventHandlers = handlers[event];
for(var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
HELPERS.removeEvent(event,handler[0],el);
}
}
}
return;
}
By the way, to call this methods you must do the following:
Capture a DOM Node
var a = document.getElementById('some_id');
Call the method 'bindEvent()' with the corresponding parameters.
bindEvent('click',function(){alert('say hi');},a);
And to de-attach it:
removeAllEvent('click',a);
That's all, hope will be useful for somebody one day.
Personally (and I know this isn't the "best" way, as it does require me to think about what I'm doing), I like to just use the on* event properties of the element I'm working with.
This has the convenient upside of being able to quickly and easily detach events.
var a = document.getElementById('someDivId');
a.onclick = function() {alert("Clicked!");};
// later...
a.onclick = null;
However, you do have to be careful with this because if you try to add a second event handler it will overwrite the first. Keep that in mind and you should be all fine.
I'm not sure if you can unbind an anonymous function attached via javascript. If possible you can simple remove the element from the DOM and recreate it. This will get rid of any event handlers previously attached.
JavaScript provides no list of event listeners attached to a node.
You can remove all event listeners of a node but using the Node.cloneNode method, see here: https://developer.mozilla.org/En/DOM/Node.cloneNode
This clones the node (obviously) but it does not clone the event listeners attached to it.
You could also just bind empty functions as event listeners:
function noop() {}
bindEvent(myElement, "click", noop);
This is from jquery's source:
jQuery.removeEvent = document.removeEventListener ?
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
function( elem, type, handle ) {
if ( elem.detachEvent ) {
elem.detachEvent( "on" + type, handle );
}
};
i am using jquery and doing something like this
DOM:
<div id="parent"></div>
JS:
var _doSomeThing = function()
{
//some codes
}
$(function()
{
// appending div and binding methods to span
$('#parent').append('<span>1</span>');
$('#parent').append('<span>2</span>');
$('#parent span').bind('click', _doSomeThing);
});
function _clearDiv()
{
//clear div
$('#parent').html('');
}
//sometime in future, call clear div
_clearDiv();
Now my question is, do binding events to DOM and later just removing the elements from DOM leads to memory leakage?
If yes, how to solve this problem?
the jQuery html method attempts to prevent memory leaks by removing event handlers for any elements that are deleted as a result of calling .html('') on a jQuery object.
From the 1.4.2 source
html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;
}
// See if we can take a shortcut and just use innerHTML
// THE RELEVANT PART
else if ( typeof value === "string" && !rnocache.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
value = value.replace(rxhtmlTag, fcloseTag);
try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}
// If using innerHTML throws an exception, use the fallback method
}
catch(e) {
this.empty().append( value );
}
}
else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery(this), old = self.html();
self.empty().append(function(){
return value.call( this, i, old );
});
});
}
else {
this.empty().append( value );
}
return this;
}
We can see that the jQuery.cleanData() function is called. Here is the source for that
cleanData: function( elems ) {
var data, id, cache = jQuery.cache,
special = jQuery.event.special,
deleteExpando = jQuery.support.deleteExpando;
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
id = elem[ jQuery.expando ];
if ( id ) {
data = cache[ id ];
if ( data.events ) {
for ( var type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
} else {
removeEvent( elem, type, data.handle );
}
}
}
if ( deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando );
}
delete cache[ id ];
}
}
}
This looks in the jQuery.cache object for any event type properties on the events object property of the data object relating to each element that will be deleted when calling .html('') and removes them.
To basically explain how the standard event binding works, when a function is bound as a handler to an event raised on an element using jQuery, a data object is added as a property to the jQuery.cache object. This data object contains an events property object that will have a property created on it with a name matching the event type to which you wish to bind the event handler function. this property will contain an array of functions that should be called when the event is raised on the element, so the event handler function is added to this array. If this is the first event handler function for the event type and element in question, the jQuery.event.handle function with a call to apply (using the element as the context such that this in the function execution context will refer to the element) is registered with the browser using addEventListener/attachEvent.
When an event is raised, the jQuery.event.handle function will call all of the functions in the array on the property of the events property object of the data object matching the event type and the element on which the event was raised.
So in summary, html('') shouldn't cause memory leaks as a number of defensive measures are in place to prevent them.
Yes, because jQuery maintains a list of the attached event handlers to make unhooking them easier and in order to explicitly unhook them for you when the page is unloaded (which works around a more serious memory leak in IE). (So does Prototype, can't speak for other libs.) The solution is to unhook them before removing the elements (either directly, or via empty).
Can't comment on the leakage problem but you could simply use .empty() instead of .html(''). That way you'd clean the innerHTML and remove any bound event handlers.
You can always use $('#parent span').unbind(); just to be sure
Since you're constantly referring to $('#parent'), you should create a reference to that object in the global scope so that jQuery isn't constantly looking for the object on each request. Doing this, you're essentially caching the reference to the object, which will cut down on memory usage tremendously.
_parent = $('#parent');
...
function(){ _parent.append('<span>1</span>'); }
Edit: I picked up this tip from this article on jQuery Performance Rules