Yeah so I've been messing around with javascript a while but only recently got into stuff like object orientation, prototyping and using objects for all functions and vars.
But, many frameworks like jQuery or extJS have something I have yet to grasp, you can define an object by using a built in function for searching the dom, example:
var a = $('#mydiv');
and then you can do a.click(function);
So my question is, how can I create a "framework" of sorts where I can write code in that style, like example:
var mydiv = document.querySelector('mydiv');
mydiv.neph(args,args);
So I have my object defined, in this case it's a dom element or whatever, now I pass it to my function "neph" with arguments, I want to create code that allows me to do this. But since mydiv does not have any function, it only has in this case the dom element right so mydiv.neph does not exist, but in jquery you can define whatever var and .click or .mouseover or whatever does exists within the object as functions? Confusion ensues! :D
Ok sorry if this is a retarded question btw =P
jQuery and other libraries define a function called $ that takes several optional parameters. The object returned by calling $ is not a DOM element, but a jQuery object wrapping a DOM element up with a set of convenient functions.
You can do something similar yourself:
<html>
<body>
<input id="derp" type="text"/>
<script type="text/javascript">
function $(id)
{
return new myLibrary(id);
};
function myLibrary(id)
{
this.el = document.getElementById(id);
};
myLibrary.prototype.help = function()
{
alert(this.el.id);
return this;
};
// Woah! My own fake jquery!
$("derp").help();
</script>
</body>
</html>
jQuery is far more sophisticated, of course. For example, it will use apply and call to set this correctly in calls like jQuery.each.
You need to create a Prototype in javascript. This is what allows you to add a function to an object that's already defined (i.e. the .click() function that you gave as an example).
You can have a look at the jQuery code, it's open source. It's not the simplest code, but you can still see how it works and how they do it.
Mike's comment is a good answer: Look at jquery or Ext-Core's sources.
Maybe what you're missing is that, in jquery, for instance $() returns a jquery object, which wraps the plain vanilla DOM node, providing extended functionality.
In jQuery, $ is just an alias to the jQuery object. So when you call $('#mydiv'); you're really calling a function like jQuery('#mydiv'); So part of what makes jQuery so cool is that every the $() function returns this, which means when you call the $() you're getting a handle to the jQuery object and all of the methods it has on it. That is what allows you to do something like this:
var a = $('#mydiv');
a.click(function() { // etc etc etc });
So to pull off your example:
var mydiv = document.querySelector('mydiv');
mydiv.neph(args,args);
You'd have to create an object that has a function called neph on it and return that object in the context of mydiv when you call querySelector.
var myFramework = function() {
this.context = undefined;
this.neph = function(arg, args) {
console.log(arg, args);
}
};
var myCuteFunction = function(element) {
var f = new myFramework();
f.context = element;
return f;
}
// ...
var x = myCuteFunction('#mydiv');
x.neph(arg, args);
Since nobody has really answered about Ext, you can easily extend the element wrapper prototype:
Ext.override(Ext.Element, {
myMethod: function(a, b){
console.log(a, b, this);
}
});
"this" will refer to the Ext.Element object.
Related
I am trying to figure out how jQuery is both a function which accepts an argument and returns a value (selector -> array of elements), and also an object that can be extended with new methods:
$.fn.myMethod = function() {//do stuff}
As a company we are moving away from jQuery, because vanilla JS is so useable. However we have some jQuery plugins I wrote that we would like to keep using. I am re-writing them, but would like to keep the syntax for using them similar to how it was in JQ. I don't want to extend the Element object (element.myPlugin()) for obvious reasons, so I was thinking I would create my own wrapper like JQ. Ideally I could define the base/default function that returns an array of DOM elements like so:
const __ = function(element) {
if (typeof element === 'string') {
return document.querySelectorAll(element)
}
return [element]
}
But then later, this function could be extended with new methods:
__.myNewPlugin = function(text) {
this.forEach(el => el.innerText = text)
}
Where this is the array of DOM elements returned by the base/default function, so that later the rest of my team could use the new method like so:
__(document.querySelector('.thing')).myNewPlugin('Hi SO')
-or-
__('.thing').myNewPlugin('Hi SO')
If for some reason you think this is a bad idea, I'm happy to hear your reasoning, but please also post an example of how this is achieved. It's obviously possible (because JQ does it), so even if I decide not to go this route, I'd still like to learn how something like this could be implemented.
I couldn't find the syntax something like this anywhere:
var mz = jQuery.noConflict();
mz('#zoom01, .cloud-zoom-gallery').CloudZoom();
This means: jQuery.noConflict()('#zoom01, .cloud-zoom-gallery').CloudZoom();
And something like this:
$(window)[this.off?'off':'on']("scroll", fixDiv );
So, I'm wondering about the syntax something like of these:
jQuery.noConflict()(syntax) and $(window)[syntax](syntax) and I also think there might be something like this $(selector){syntax}
Can anyone elaborate of those syntax?
The best place to start is the documentation
$.noConflict()
Many JavaScript libraries use $ as a function or variable name, just
as jQuery does. In jQuery's case, $ is just an alias for jQuery, so
all functionality is available without using $. If you need to use
another JavaScript library alongside jQuery, return control of $ back
to the other library with a call to $.noConflict(). Old references of
$ are saved during jQuery initialization; noConflict() simply restores
them.
In other words, noConflict() sets a variable to equal jQuery, so this
var mz = jQuery.noConflict();
mz('#zoom01, .cloud-zoom-gallery').CloudZoom();
is the same as
$('#zoom01, .cloud-zoom-gallery').CloudZoom();
or
jQuery('#zoom01, .cloud-zoom-gallery').CloudZoom();
noConflict() does not directly take selectors, it's just a function that sets jQuery in a certain scope to a variable so you can have multiple versions of jQuery (which you shouldn't) or use other libraries that also uses $ for something, it does not mirror the selector engine or anything else, even if it might seem so at first glance, it simply returns an instance of jQuery
In javascript there is dot notation and bracket notation, so an object can be accessed as
object.propertyName
or
object['propertyName']
as everything in javascript is an object, even jQuery methods, they can be accessed as
$('#element').fadeIn(200);
or
$('#element')['fadeIn'](200);
it's the same thing, so doing
$(window)['on']("scroll", fixDiv );
is the same as
$(window).on("scroll", fixDiv );
the advantage of using brackets is that they can contain any string, even variables, or in this case ternary statements, or the returned result of a function
var event = 'on';
$(window)[event]("scroll", fixDiv );
or
var event = this.off ? 'off' : 'on';
$(window)[event]("scroll", fixDiv );
that one also uses this, which in the global scope would be window, and it's the same as
$(window)[this.off ? 'off' : 'on']("scroll", fixDiv );
The ternary statement itself is just a fancy condition, and this
var event;
if (this.off) {
event = 'off';
} else {
event = 'on';
}
is exactly the same as
var event = this.off ? 'off' : 'on';
Added for the edited question :
jQuery() or $() is a function, something we can tell from the parenthesis, so it's something like
function jQuery(arguments) {
// do something
}
which can be called as
jQuery(some_arguments);
and as var $ = jQuery one can also do $();
Now that we know it's a function, it makes sense that we can do
$('#element_id')
and internally jQuery checks what kind of argument we passed, it sees that is's a string, and it's starting with #, so it's an ID, and then jQuery can do document.getElementById() and get that DOM element, and at the same it wraps that element in a new array-like object, usually referred to as a jQuery object.
We can also pass in a DOM node, array, object or anything else, and jQuery tries to figure out what it is, and wrap in that jQuery object for us to use with other jQuery methods, so this :
$({x:10, y:20})
is the same as
var obj = {x:10, y:20};
$(obj)
and its turned into one of those jQuery objects with the properties x and y. Passing in an object like this means we can chain on methods, and those properties are available in the methods.
$({x:10, y:20}).animate({x:50}, 1000);
And that's basically how it works, simplified a lot.
As for passing objects to methods, that's a very common way to pass arguments.
To see how it works, it's easiest to create a method:
$.fn.doStuff = function(argument) {
this.css(argument);
}
inside a jQuery plugin, this is the jQuery object, and we can now use the mothod above that does nothing more than pass the arguments to jQuery's css().
We know we can pass an object to css() like this :
$('#element').css({left: '10px', top: '20px'});
so using our plugin we can do the same
var obj = {left: '10px', top: '20px'};
$('#element').doStuff(obj);
and it ends up doing exactly the same thing. Of course, we could do anything with the object :
$.fn.doStuff = function(args) {
if ( typeof args == 'string' ) {
alert(args); // if it's a string, just alert it
} else if ( typeof args == 'object' ) {
for ( var key in args ) { // if it's an object, iterate
this[0].style[key] = args[key]; // and do something
}
}
}
foo['bar'] syntax is to get the property bar from object foo.
foo() is to execute the function foo.
And you can combine these as you wish.
jQuery.noConflict() returns a function so you could execute the result by jQuery.noConflict()(syntax).
$(window) returns an object so you could get a property from it by $(window)[syntax], and if the property is a function, then you could execute it by $(window)[syntax](syntax).
This is just javascript syntax.
person.name is exactly the same as person["name"]
The same happens with methods
$(window).on(...) is exactly the same as $(window)["on"](...)
One cool thing about the second way is that you can make the member name variable, for example:
So doing:
var windowMethod = "on";
$(window)[windowMethod](...)
is the same as
$(window)["on"](...)
And you can have an expression inside the brackets, so this:
$(window)[this.off ? 'off' : 'on']("scroll", fixDiv );
would be exactly the same as doing this:
if(this.off)
$(window).off("scroll", fixDiv);
else
$(window).on("scroll", fixDiv);
But the former is shorter.
Hope this helps. Cheers
PS: The jQuery.noConflict()(syntax) is straightforward, .noConflict() just returns a function and then we append some other parens to call it just as any other function.
I was trying to use $.fn.show (and other jQuery functions) within higher-order functions.
What I originally wanted to have was a function that applies a given function to all elements returned by a collection of other functions applied to a given element. Something that would look like this:
function mapOn( func, genratingFunc, element ){
$(generatingFuncs).each(function(){
var buf = $(element);
while(buf.length){ // run as long as elements are returned
func(buf);
buf = this(buf);
}
});
}
I needed such a function to apply some functions to a couple of DOM nodes and their parents and/or children in a handy, expressive way. Let's say we want to hide the node with the ID hideMyFamily and its children. I don't know any handy way to do this with jQuery so I'd run hide() on $("#hideMyFamily").children() and on $("#hideMyFamily").children().children() and so on until the length of the collection was 0 (and on $("#hideMyFamily") itself of course).
Thing is, running mapOn( $.fn.show, [$.fn.children], $("#hideMyFamily") ) won't do the job since you apparently cannot just apply $.fn.show to an element/collection.
So what I came up with is this:
For each of the jQuery's functions that I need to specify another function (within global scope) that looks like this:
function _show(e){ $(e).show(); }
For each of the jQuery's "generating" functions I specify another "work-around function":
function _id(e){ return $(e); }
function _children(e){ return $(e).children(); }
And then I can specify my "multiMap" function which looks like this:
function multiMap(func, generators, elem){
$(generators).each(function(){
var buf = $(elem);
var buf2 = [];
while (buf.length && buf[0] !== buf2[0]) {
func(buf);
buf2 = buf;
buf = this(buf);
}
});
}
Now I can run my handy function multiMap(_show, [_id, _children], "hideMyFamily") to hide the element itself and all of its children.
Now, to get to the point, my question is: Is there any more elegant way to achieve the desired behaviour? Is there any jQuery magic I didn't take into account?
tl;dr Is there a handy way to use jQuery's functions like show() and hide() on nodes/collections in a way like $.fn.show( $("someElements") )?
Yes, it is possible.
You can do $.fn.show.call($("some-elements")).
I haven't fully gone through your more elaborate example, but that seems to be what you're looking for. And for your final example, you could write things as:
multiMap($.fn.show, [_id, _children], "hideMyFamily")
and then in mutliMap do f.call or something like that and I believe that would work.
I have one function:
function myFunction(){
var id = $(this).attr('id');
}
Now sometimes, myFunction gets called with $(this) as the context but sometimes the context is just 'this'.
In one line how can I do something similar to this:
if(this == $(this)){
var e = $(this);
}
Basically a test to see if 'this' is a jQuery 'this' or a JS 'this'.
Possible?
if (this.jquery) { // refers to jQuery version
// jQuery object
}
Alternatively:
if (this instanceof jQuery) { // prototype chain
// jQuery object
}
However, as others have said, it doesn't really matter, $(this) will work whether or not this is already a jQuery object or a DOM element.
You can also just do var e = $(this) when this is a jQuery object or if it's not.
One way to deal with it would just be to always wrap it in $(...). Wrapping a jQuery object like that creates a clone (see the jQuery docs), so this HTML:
Test Link
with this JS:
var link = $('#test-link');
var doubleWrappedLink = $(link);
alert(doubleWrappedLink.attr('href'));
will correctly pop up "test".
You can use the following snippet:
if (obj instanceof jQuery || 'jquery' in Object(obj)) { }
Take a look at: Check if object is a jQuery object
I'm playing around with the jQuery $.data function, and I'm running in to some trouble. If I do like this:
(function($){
$.fn.testData = function() {
var obj = $(this);
obj.text($.data(obj,'test'));
}
})(jQuery);
var element = $("#test");
$.data(element,'test','hej');
element.testData();
this comes out as undefined. Why?
EDIT:
It works just fine if I use the elem.data(key) function, like this:
(function($){
$.fn.testData = function() {
var obj = $(this);
obj.text(obj.data('test'));
}
})(jQuery);
var element = $("#test");
element.data('test','hej');
element.testData();
but I just saw an slideshow by Paul Irish, which claims that elem.data is 10x slower than $.data(elem):
http://paulirish.com/2009/perf/
(function($){
$.fn.testData = function() {
var obj = $(this);
obj.text($.data(obj[0],'test'));
}
var element = $("#test");
$.data(element[0],'test','hej');
element.testData();
})(jQuery);
Here is the jsFiddle: http://jsfiddle.net/TdJHq/
jQuery.data (doc jQuery.data) works on DOM elements, not on jQuery elements. So you must extract the real element under your jQuery selector.
obj.text($.data(obj[0],'test'));
This is what #alexl explained.
But there is also a .data() method, that works on a jQuery selector, so you could use:
// getter
obj.text($obj.data('test'));
// setter
obj.text($obj.data('test','toto'));
And you may have mixed both.
The problem is that you are creating different objects for attaching and retrieving the data. Whenever you call $(selector), you are creating a new jQuery object and this it will be different than the one you attached the data to.
Here is a simple example:
$.data($('#test'),'test','hej');
alert($.data($('#test'),'test'));
It will give you undefined.
That is why $.data expects a DOM element and not a jQuery object. No matter how you retrieve a certain DOM element, the reference is always the same.
So either pass the DOM element ($('#test')[0]) or better, use $('#test').data().