Possible Duplicates:
What does this mean in jquery $('#id', javascript_object);
What does $(''class for the same element', element) mean?
What does the second argument to $() mean?
Anyone know what are:
$('element', $$).function(){...};
(seen here)
And
$('element', this).function(){...};
(seen here)
?
$('.pblabel', this).text(newVal + '%');
Is the same thing as
$(this).find('.pblabel').text(newVal + '%');
In fact, that is the way it is rewritten and run internally. It is called the "context selector".
From the jQuery source:
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
It uses this or $$ as the context, i.e. all elements returned must be its descendants. The default is document.
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;
}
}
I'm noticing that this references something else inside a function that I added as event listener. I read this informative resource and a few questions on stackoverflow but I don't know how to apply it to my case (I'm quite new to the "oop" and the module pattern in javascript so I'm a bit lost).
Here is my little module:
var myModule = myModule || ( function() {
// Adds event listener for all browsers
// see http://stackoverflow.com/a/6348597
function addEvent( element, event, listener ) {
// IE < 9 has only attachElement
// IE >= 9 has addEventListener
if ( element.addEventListener ) {
return element.addEventListener( event, listener, false );
} else if ( element.attachElement ) {
return element.attachElement( "on" + event, listener );
}
}
return {
init: function() {
// Add event listeners
addEvent(
document.getElementById( "myElementId" ),
"click",
this.processMyElement
);
addEvent(
document.getElementById( "myOtherElementId" ),
"click",
this.processMyOtherElement
);
},
hideElementById: function( elementId ) {
document.getElementById( elementId ).style.display = "none";
},
showElementById: function( elementId ) {
document.getElementById( elementId ).style.display = "block";
},
processMyElement: function() {
this.hideElementById( "myElementId" );
this.showElementById( "myOtherElementId" );
},
processMyOtherElement: function() {
// Do something else...
}
};
}() );
The thing is that this which I use to call hideElementById in processMyElement is referencing to the element I added an eventListener to, and not to the current object.
I tried a few things without success:
removing the return in addEvent,
using var that = this as a private property of the module (placed in the module before the addEvent definition) and using that in processMyElement
using apply in the init method but it (obviously) calls processMyElement when adding the listener to the element
Could anyone help me with this? I tried a few things but I cannot see how to do it better...
PS: I try to build testable code, that's why I had those hideElementById and showElementById methods, in order to separate various functionalities (that may be quite clumsy actually but that's where I am ATM...).
There are (more than) a couple of common ways to get the correct this binding. For example, you can use a closure:
var that = this;
addEvent(
document.getElementById( "myOtherElementId" ),
"click",
function () {
that.processMyOtherElement();
}
);
Or you could use bind:
addEvent(
document.getElementById( "myOtherElementId" ),
"click",
this.processMyOtherElement.bind(this)
);
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
Which one you use would depend on other factors.
I am in process of learning more about javascript plugins and found one that is of interest to me. I am willing to get my feet dirty and see how this thing can be modified...
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
console.log('still working..' );
/*
Do more setup stuff here
*/
$(this).data('tooltip', {
target : $this,
tooltip : tooltip
});
}
});
},
show : function( ) {
console.log('this is the show');
},
hide : function( ) {
// GOOD
},
update : function( content ) {
console.log('this is the update');
// !!!
}
};
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
ok I have 4 questions...
1.how do you initialize this plugin? i keep getting 'still working..' with my console log when i try to run a random div element, $('#mtest').tooltip();.
2 the init: is inside the var method, which is private, meaning I can't access init: from outside of this plugin? right? where would i put initializing logic at since it appears to be returning options...?
3.
I am confused about this part of the code...
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
I know its returning all the methods, but...
3a. why write methods[method]// is looks like [method] is an array, and that looks confusing to me because I don't see an array, its a bunch of methods...
3b. what is the else checking for? or why would an error occur?
thanks for any advice on helping me fully understand this plugin!
I don't know what your getting at with the first question. But the other questions can be solved pretty easily.
First, lets go over 3.
The code you have, and what jQuery provides in their docs, is merely a sort of "getter" between you and your methods. Instead of clustering up a namespace with all of your methods, you put your methods into an object title methods (which is instantiated on the second line of your first block of code.
If you look at the jQuery provided code you are asking about, its not returning methods as you've thought. Its calling the method of the key in your methods object. The first if statement says that if you call your plugin (in your case, tooltip) with a string variable, it will look up that index in the methods object and Call the function.
The second else if block says that if you pass a object as a parameter OR no parameter, it will call your init method. This is sort of like a custom built getter/initializer for your plugin.
So now, to answer your second question, the init method can be accessed by either calling your tooltip plugin with..
1) no parameters
2) a object parameter (usually options such as {"someOption":true,"anotherOption":400})
3) the string 'init' as in $('#id').tooltip('init')
This way you can also access your show and hide methods with...
$('#id).tooltip('hide') ... and so forth.
You can read up on this in the jQuery docs for much more detail. This is me merely putting it into layman's terms.
How do you manage namespace for a custom JavaScript library depends on jQuery?
Do you create your own namespace, say foo and add your objects there? e.g. foo.myClass, foo.myFunction
Or do you add your objects to jQuery's namespace? e.g. jQuery.myClass, jQuery.myFunction
Which is the more common practice and why?
This article discusses writing jQuery plugins/libraries in excruciating detail.
What NOT to do:
(function( $ ){
$.fn.tooltip = function( options ) { // THIS };
$.fn.tooltipShow = function( ) { // IS };
$.fn.tooltipHide = function( ) { // BAD };
$.fn.tooltipUpdate = function( content ) { // !!! };
})( jQuery );
What to do:
(function( $ ){
var methods = {
init : function( options ) { // THIS },
show : function( ) { // IS },
hide : function( ) { // GOOD },
update : function( content ) { // !!! }
};
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
I also wrote a blog post last year about various methods for namespacing in JavaScript (non-jQuery related).
It would depend on what the library does.
If you're extending the functionality of instances of jQuery objects, you'd use jQuery.fn as was described very nicely by #David Titarenco in his answer.
If you're creating utilities that are meant to be seen as additions to those provided in window.jQuery, then I don't see a problem with using that namespace (as long as you're careful with naming).
If it is really its own separate library that is not meant to be seen as an extension of jQuery, yet relies on functionality from jQuery, then definitely use your own namespace.
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