Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a jQuery like "$" object

My end goal is being able to do something like this:

MyVar(parameter).functionToPerform();

Silly enough, even after reading up on how variables are declared, looking at the jQuery code, ... I still can't get my head around it.

This is what I've tried so far, but it fails:

var MyClass = function(context) {
this.print = function(){
    console.log("Printing");
}

this.move = function(){
    console.log(context);
}
};

var test = new MyClass();
test.print(); // Works
console.log('Moving: ' + test('azerty').move() ); // Type property error
like image 411
skerit Avatar asked Dec 29 '10 17:12

skerit


2 Answers

As I write this, Squeegy's answer has the highest number of votes: 7. Yet it is wrong because __proto__ is non-standard and is not supported by Internet Explorer (even version 8). However, getting rid of __proto__ does not get it working either in IE 6.

This (somewhat simplified) is the way jQuery actually does it (even try it on IE 6), and it also includes examples of static methods and method chaining. For all the details of how jQuery does it, of course, you will have to check the jQuery source code yourself.

var MyClass = function(context) {
    // Call the constructor
    return new MyClass.init(context);
};

// Static methods
MyClass.init = function(context) {
    // Save the context
    this.context = context;
};
MyClass.messageBox = function(str) {
    alert(str);
};


// Instance methods
MyClass.init.prototype.print = function() {
    return "Printing";
};
MyClass.init.prototype.move = function() {
    return this.context;
};

// Method chaining example
MyClass.init.prototype.flash = function() {
    document.body.style.backgroundColor = '#ffc';
    setInterval(function() {
        document.body.style.backgroundColor = '';
    }, 5000);
    return this;
};


$('#output').append('<li>print(): '+ MyClass().print() +'</li>');
$('#output').append('<li>flash().move():'+ MyClass('azerty').flash().move() +'</li>');
$('#output').append('<li>context: '+ MyClass('azerty').context +'</li>');
MyClass.messageBox('Hello, world!');

Note that if you need "private" data, you will have to put instance methods inside MyClass.init (with a variable declared just inside that function) as this.print = function() { ... }; instead of using MyClass.init.prototype.

like image 96
PleaseStand Avatar answered Oct 13 '22 13:10

PleaseStand


[Probably] The Most Elegant Solution

First off, jQuery uses a pattern which is closer to a Monad, a Factory, or a combination of both. Nevertheless, here's what I've been using in my projects because the pattern, itself, is so loosely coupled to whatever class you'd like to utilize:

;(function (undefined) {
    if (undefined) return;
    var ENV = this;

    var Class = function Class() {
        var thus = this;

        function find(data) {
            console.log('@find #data', data);
            return this;
        }

        function show(data) {
            console.log('@show #data', data);
            return this;
        }

        // export precepts
        this.find = find;
        this.show = show;

        return this;
    };

    var Namespace = ENV['N'] = new (function Namespace(Class) {
        var thus = this;

        var Ns = Class.apply(function Ns(data) {

            if (this instanceof N) {
              return new Namespace(Class);
            }

            return Ns.find.apply(Ns, arguments);
        });


        return Ns;
    })(Class);

}).call(window || new function Scope() {});

var n = N('#id').show(450);
var m = new N();

m('#id')('.curried').show('slow');

console.log(n !== m);  // >> true

Basically, you can use it as a function, an object, and use the new keyword to construct another unique object/function. You can use this to enforce an arbiter method (default, like the find method above), or use different methods based upon what parameters are input. For instance, you can do something like:

var elementsList = N('#id1')('#id2')('#otherSpecialElement').each(fn);

-- OR --

var general = N('.things');
var specific = general('.specific')('[data-more-specific]').show();

The above would, for instance, accumulate a nodelist of multiple elements (1st expression), or drill down to one specific element (2nd).

Hope this helps

like image 39
Cody Avatar answered Oct 13 '22 13:10

Cody