views:

44

answers:

4

How to create a pure JavaScript(without using any library) plugin which looks like:

document.getElementById('id').myPlugin();

like jquery ?

A: 

Why use that format? I think

myPlugin(document.getElementById('id'))

would probably be clearer to anyone maintaining the code. I'm not a big fan of monkey patching.

spender
+1  A: 

I agree with spender. You should really not mess around with standard javascript elements. It's better to do a wrapper for it (ie $ as jQuery uses). If you want to make a short hand for get elements do a wrapper method and extend that one instead. This will make the code easier to maintain. Since if you modify the standard functionality in Javascript it make interfere with standard behavior. This could lead to some really hard debugging. It's better to make a wrapper function, do what ever you like with it.

But in despite of this, you want to mess around with the standard behavior you can always bind new methods to the standard objects using prototypeing. For example:

Object.prototype.myMethod = function () {}

..fredrik

fredrik
+2  A: 

I hesitate to say you can't. After all, Prototype.js did.

To be able to call getElementById('id').someNewMethod() you would have to ‘extend’ the prototype of the type of object getElementById returns, which is an Element. In some browsers you can indeed do that:

Element.prototype.someNewMethod= function() {
    alert('hello from a '+this.tagName);
};

document.body.someNewMethod(); // hello from a BODY

However, this is quite questionable. Prototyping onto the native JavaScript objects like Object or String is potentially dodgy enough, what with name clashes between browsers and libraries, but Element and all the other DOM objects may be ‘host’ objects, which don't permit prototyping.

Even if DOM objects are implemented with a full native JavaScript interface, no specification says that Element must expose a prototype/constructor-function under a window property/global variable called Element.

In reality this does work in Firefox, Webkit and Opera, and IE from version 8 onwards. However it's unstandardised, not all browsers make all the same interfaces available under all the same names, and it won't work in IE6, IE7 or many smaller browsers (eg. mobile browsers).

Prototype had a fallback for these browsers: it would try to augment every instance of an Element with the new members when it first saw each element. But this had extremely messy side-effects and is considered by many to be a mistake; Prototype 2.0 won't try to extend DOM interfaces any more.

Maybe in the future somebody will tie this down and make it a reliable part of the browser environment. But today that's not the case, so you should continue to use other, clunkier methods such as wrapper functions.

bobince
Good answer. But just out of curiosity, shouldn't one be able to fake it using some real dodgy javascript code. First save the original getElements and the overwrite it? Real scary solution but should work. :)
fredrik
Yes, you could: you'd essentially be wrapping the entire DOM in your own reimplementation. The problem with that is (a) it'd be slow, and (b) a lot of the DOM relies of properties rather than methods: eg. `document.body.firstChild` makes no method calls at all. You couldn't define your own property getter/setters in JavaScript before ECMAScript Fifth Edition, so you can't have dynamic properties that work cross-browser and even *with* properties you can't emulate a NodeList/array-like very well.
bobince
So either you'd have to fill in all the linked `firstChild`​s and `nextSibling`​s and `parentNode`​s and every other property for the entire DOM at start-up time and on every document modification (eek!), or you have to change the interface, eg. by requiring users to write `document.getBody().getFirstChild()`. But if you're changing the interface you lose the advantage of the native DOM anyway, so you might as well just come up with your own interface (like jQuery et al).
bobince
Well I don't think it was a great idea to start with. :) but great response. The tricky part now is, how do you write a DOM reimplementation that's faster then the native one !
fredrik
+1  A: 

You can create your own wrapper (similar to jQuery), and doing this will allow you to circumvent all of the discussed problems with extending the DOM directly.

myWrapper = (function(){

    function NodeList(elems) {

        this.length = 0;
        this.merge(this, elems.nodeType ? [elems] : elems);

    }

    function myWrapper(elems) {
        return new NodeList(elems);
    }

    myWrapper.NodeList = NodeList;

    NodeList.prototype = {
        merge: function(first, second) {

            var i = first.length, j = 0;

            for (var l = second.length; j < l; ++j) {
                first[i++] = second[j];
            }

            first.length = i;

            return first;

        },
        each: function(fn) {

            for (var i = -1, l = this.length; ++i < l;) {
                fn.call(this[i], this[i], i, l, this);
            }

            return this;

        }
    };

    return myWrapper;

})();

And you can add your own methods like so:

myWrapper.NodeList.prototype.myPlugin = function() {
    return this.each(function(){
        // Do something with 'this' (the element)
    });
};

Usage:

myWrapper(document.getElementById('id')).myPlugin();
J-P