Extending HTMLElement - javascript

using jQuery it is possible to do something like this: $("div")[5].animate()
This seems to me like the developers in a way extended the HTMLElement using prototype.
My question is now: How did they do this? Since HTMLElement.prototype is not working in IE for example I wonder if there is a cross browser method to prototype HTML elements.
Thanks!

You can't extend those things in IE; IE just does not implement the DOM interface that way. That's why Prototype forces you to "wrap" elements that you want to manipulate with those additional methods.

the jQuery factory function (jQuery() or $()) does not return a DOM node.
The jQuery factory function returns a new jQuery.init instance which acts very similar to an array. Instead of extending any DOM node's prototype, more functions are simply added to jQuery.fn
If a function is chained on a jQuery selector, it typically applies to all the elements contained within the jQuery.init instance.
I highly recommend reading through the commented jQuery source so that you can see exactly what's going on behind-the-scenes.

Note that $('#book') !== document.getElementById('book').
The first one is a jQuery object which refers to a dom element and can be extended, while the second one is actually a dom element.

Related

is there any way to create a javascript function like element.function(options)? [duplicate]

For example for this.parentNode I would like to just write this.p or instead of
document.getElementById('someid') just write document.g('someid'). Of course that are simple examples, I just want to know what is the correct way to do it.
(I know I can use jQuery or Prototype, but I'd like to learn how it is really done in JS)
Although you can prototype on the HTMLElement in many browsers - Internet Explorer (6,7,8) is NOT one of them. AFAIK, IE9 does support this (though I haven't tested it).
For browsers that do handle it, you can do:
HTMLElement.prototype.doHello = function(thing){
console.log(this + ' says: ' + thing)
}
document.body.doHello('hello')
I would strongly suggest not attempting to do this, for a few reasons:
Browser compatibility. While it is possible in several browsers, it isn't possible in IE <= 8.
DOM elements are host objects. Host objects (i.e. those provided by the environment that aren't native JavaScript objects) have no obligation to play by the same rules as native JavaScript objects and other than specified DOM behaviour can essentially do what they like. So, even if some browsers provide an HTMLElement prototype and allow you to augment it, there's no guarantee that it will work as you expect.
Compatibility with other code in your page. If any other code in your page (such as Prototype) messes with the HTMLElement prototype, you risk naming collisions and hard-to-detect bugs.
Instead, I would suggest creating wrapper objects around DOM nodes as jQuery, YUI and other libraries do.
Kangax has written a good article on DOM extensibility, covering all these points and more.
In a word, don't. It is best not to modify objects you don't own.
This is particularly true for HTMLElement, which you cannot modify in some browsers.
This article from perfectionkills.com will probably give you some insight into how it's done, and why you shouldn't do it.
(By the way, jQuery doesn't extend DOM elements. They use DOM wrappers instead.)
This might not be what you are looking for if you want to wrap a global object like document, but you can get a similar effect with custom-elements [1] [2] to create your own HTMLElement-like nodes.
create custom-element
add method to custom-element class
you can call the method
export class CustomElementInput extends HTMLElement {
log(){
alert("log")
}
// you can even overwrite methods like so
remove(){
alert("removing this node")
super.remove()
}
}
customElements.define("custom-element-input", CustomElementInput)
// somewhere else...
// in your HTML something like:
// <custom-element-input></custom-element-input>
const el = document.querySelector("custom-element-input")
el.log() // creates alert()

Is there any reason to have nested calls to $ (jQuery)?

I was poking around the source code of a website when I came across some code like this:
$($('#newForm_step1')).hide("slide", { direction: "left" }, 0);
and this:
$($($('.breadcrumbs')[0]).children().last()).html("...");
I've never seen the $ (jQuery) function used this way, and I was wondering if there'd be any practical reason to do something like this? To the best of my knowledge, wrapping a jQuery object with a call to $ simply returns a jQuery object for the same selector, and methods on jQuery objects return this, so I don't see why one would need nested calls to $.
No, there is no reason to do this.
In the first example, $($(...)) is redundant. There is absolutely no effect in immediately wrapping a jQuery object in another jQuery object.
The line should read
$('#newForm_step1').hide("slide", { direction: "left" }, 0);
In the second example, $(...)[0] returns a raw DOM element, so it's wrapped again before having jQuery's .children().last() invoked on it. The result of that is already a jQuery object, so there is no need to re-wrap it, and the "unwrapping" could have been avoided by calling .first() instead of [0].
The line should read
$('.breadcrumbs').first().children().last().html("...");
There's no necessary to wrap jQuery object with jQuery. That will just result the same but over-coding and redundant.
But when you have DOM Object then you need to wrap with jQuery so that it will be jQuery object.
Taking your example:
$('.breadcrumbs')[0];//[0] converts to JavaScript Object
Now, if you want to work with jQuery again, you may then wrap with jQuery:
$($('.breadcrumbs')[0]);//is now a jQuery Object
But with this example is unnecessary because you can do just like this:
$('.breadcrumbs:eq(0)');//selects first .breadcrumbs element
However, if you have some DOM Object rather than jQuery object then you need jQuery wrapper to work with jQuery methods.
You must remember this:
JavaScript Object needs JavaScript methods to be chained:
javascriptObject.javascriptMethods
Ex-
$('.breadcrumbs')[0].addEventListener();//javascript addEventListener method
jQuery Object needs jQuery methods to be chained:
jQueryObject.jQueryMethods
Ex-
$('.breadcrumbs').on();//jQuery on method

Prototype equivalent to jquery function without dollar

I have a site with both jQuery 1.8 and Prototype 2, using $ (dollar sign) seems to call jQuery but I want to invoke Prototype (from console). Specifically to do something like this: How to find event listeners on a DOM node when debugging or from the JavaScript code?
Ise there an Equivalent in Prototype like the jQuery() function?
Prototype is not a single object like jQuery, so you cannot invoke it like that.
Jquery
var jq = $('#id')
At this point you have a jQuery object that is referencing a DOM. If you try to use it like an actual DOM object, however, it won't work.
// This won't work
jq.className = "";
// This works because it's referencing the function inside jQuery
jq.removeClass();
Please note that jQuery can give you the actual DOM element if you need it, but this is not the default behavior.
Prototype
You need to think of Prototype as making base Javascript better as opposed to an object. A great example is Object.isUndefined. The Object object already exists in Javascript, Prototype is simply extending it with another function. When you see Prototype behaving like jQuery, it's almost always because Prototype extended what was already there
// This is a DOM reference
var pro = $('id'); // equivalent to document.getElementById('id');
pro.className = "";
// But there's no base Prototype object so this fails
pro.isUndefined();
// This is correct
Object.isUndefined(pro);

jQuery Like Inline Syntax Without Using jQuery?

In jQuery you can call a function like this for example:
$("id").someFunction();
Now after looking at the codebase of jQuery it looks like the object being created by using $ has its protoype modified to return the function which was added via .fn, in my application I would like the same syntax only without requiring jQuery.
Another example of this kind of behavior is some of Javascript's in-built methods such as: .replace, .toLowerCase, .split, .toString, etc. I understand some of those listed methods are on the String.prototype object and extending in-built objects is bad practice (so I hear).
How am I able to add a function to the prototype of every "String" or "Object" that gets assigned. The reason I am doing this is I am trying to create a cross-browser implementation of attaching events without having to do if statements all of the time.
So instead of needing to go: if (el.addEventListener) or if (el.attachEvent) I would like to be able to go el.bindEvent which behind the scenes would be a prototype method behind the scenes that would do all of the checking for event binding, etc.
My advanced JS knowledge when it comes to assigning prototype methods and whatnot isn't that great, so your help in understanding and correcting anything I've said is appreciated.
Extending the prototype of DOM elements is an even worse idea than extending built-in objects †. Please have a look at the article "What's wrong with extending the DOM".
What jQuery provides is a simple wrapper around the DOM API, which is ok. The problem with extending the DOM is that
they are host objects, and might behave differently than how objects are defined in the ECMAScript spec and
older IE versions don't even expose the prototype of DOM nodes.
The reason I am doing this is I am trying to create a cross-browser implementation of attaching events without having to do if statements all of the time.
This alone is no reason to extend the DOM. You can define your own function which binds event handlers and you even have to test whether addEventListener or attachEvent exists only once on page load. Such a function could look like this:
var bindEvent = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
element.addEventListener(event, handler, false);
};
}
else {
return function(element, event, handler) {
element.attachEvent("on" + event, function() {
handler.call(element, window.event);
}
};
}
}());
You can put a lot of normalization into such a function. For example, when using attachEvent, this does normally not refer to the element the handler is bound to (it refers to window), unlike with addEventListener. Also, as you might know, the event object is not passed as argument to the handler in IE. Both of these issues have been solved through the line:
handler.call(element, window.event);
(the disadvantage is that you cannot simply remove the handler, since you didn't bind handler directly, but these problems can be solved as well).
You can find more information about these browser differences and more in the excellent articles at quirksmode.org.
†: Since browsers provide Object.defineProperty and hence the possibility to mark properties as non-enumerable, extending built-in objects is not as bad anymore, but if libraries start using this, the chance of name collisions, method overriding and incompatibilities gets higher.
For your own code it should be ok. DOM objects should still be a tabu.
If I understood correctly, you want to know how to add methods to the built in javascript objects, so:
myObject.prototype.myMethod = function() {
// whatever
}
so, by example:
String.prototype.splice = function() {
// splice function for every string
}

How to add my own methods to HTMLElement object?

For example for this.parentNode I would like to just write this.p or instead of
document.getElementById('someid') just write document.g('someid'). Of course that are simple examples, I just want to know what is the correct way to do it.
(I know I can use jQuery or Prototype, but I'd like to learn how it is really done in JS)
Although you can prototype on the HTMLElement in many browsers - Internet Explorer (6,7,8) is NOT one of them. AFAIK, IE9 does support this (though I haven't tested it).
For browsers that do handle it, you can do:
HTMLElement.prototype.doHello = function(thing){
console.log(this + ' says: ' + thing)
}
document.body.doHello('hello')
I would strongly suggest not attempting to do this, for a few reasons:
Browser compatibility. While it is possible in several browsers, it isn't possible in IE <= 8.
DOM elements are host objects. Host objects (i.e. those provided by the environment that aren't native JavaScript objects) have no obligation to play by the same rules as native JavaScript objects and other than specified DOM behaviour can essentially do what they like. So, even if some browsers provide an HTMLElement prototype and allow you to augment it, there's no guarantee that it will work as you expect.
Compatibility with other code in your page. If any other code in your page (such as Prototype) messes with the HTMLElement prototype, you risk naming collisions and hard-to-detect bugs.
Instead, I would suggest creating wrapper objects around DOM nodes as jQuery, YUI and other libraries do.
Kangax has written a good article on DOM extensibility, covering all these points and more.
In a word, don't. It is best not to modify objects you don't own.
This is particularly true for HTMLElement, which you cannot modify in some browsers.
This article from perfectionkills.com will probably give you some insight into how it's done, and why you shouldn't do it.
(By the way, jQuery doesn't extend DOM elements. They use DOM wrappers instead.)
This might not be what you are looking for if you want to wrap a global object like document, but you can get a similar effect with custom-elements [1] [2] to create your own HTMLElement-like nodes.
create custom-element
add method to custom-element class
you can call the method
export class CustomElementInput extends HTMLElement {
log(){
alert("log")
}
// you can even overwrite methods like so
remove(){
alert("removing this node")
super.remove()
}
}
customElements.define("custom-element-input", CustomElementInput)
// somewhere else...
// in your HTML something like:
// <custom-element-input></custom-element-input>
const el = document.querySelector("custom-element-input")
el.log() // creates alert()

Categories

Resources