Angel Politis
Angel Politis

Reputation: 11313

How to replicate jQuery's $(this)?

I am creating a mini-library, sort of trying to reconstruct, at least partly, the way jQuery works for learning purposes and to understand better how object-oriented programming works.

I have recreated the jQuery methods click and addClass, but when I call them like:

$(".class1").click(function() {
    $(".class1").addClass("class2"); // Works, but it adds class2 to all elements
    this.addClass("class2"); // Doesn't work
});

I get an Uncaught Error saying this.addClass is not a function, which is normal, since I shouldn't be able to access another object's methods.

How is $(this) made in jQuery to mean the DOM element that triggered an event, so that in my case I can use it to add class2 only to the element clicked and not all elements that have the class class1?

P.S: I tried reading the jQuery file, but I feel like these waters are currently too deep for me.


Edit:

  1. I always appreciate all the answers and the help I get on Stack Overflow, but telling me to use $(this) instead of this doesn't solve my issue, because $(this) doesn't exist in my code. I'm trying to learn how to create something like jQuery's $(this) & what's the logic behind it.

  2. The click method is defined as follows:

    $.prototype.click = function(callback) {
       for (var i = 0; i < this.length; i++) {
          this[i].onclick = function(event) {
             callback.call(this, event);
          }
       }
    };

Upvotes: 0

Views: 755

Answers (5)

Rob M.
Rob M.

Reputation: 36511

You need to use call, apply, bind or some combination of those to set the callback's context to the DOM Node. Here is a contrived example of jquery's each method that sets the context of the callback using call:

var $ = {
   each: function(selector, callback) {
      var collection = Array.from(document.querySelectorAll(selector));
      collection.forEach(function(element, index) {
         // the magic...
         callback.call(element, index, element);
      });
   }
}

$.each('.foo', function(idx, el) {
   console.log(this.textContent);
});

Upvotes: 2

Angel Politis
Angel Politis

Reputation: 11313

With an extra 1.5 years of experience, this question becomes rather easy.

  1. Alter $, so that, except string selectors, it can accept HTML elements.
  2. Create a new instance of the object containing the HTML element given.
  3. Call addClass with that as the context.

Code:

;(function() {
  /* The object constructor. */
  function ElementList(arg) {
    /* Cache the context. */
    var that = this;
    
    /* Save the length of the object. */
    this.length = 0;

    /* Check whether the argument is a string. */
    if (typeof arg == "string") {
      /* Fetch the elements matching the selector and inject them in 'this'. */
      [].forEach.call(document.querySelectorAll(arg), function(element, index) {
        that[index] = element;
        that.length++;
      });
    }

    /* Check whether the argument is an HTML element and inject it into 'this'. */
    else if (arg instanceof Element) {
      this[0] = arg;
      this.length = 1;
    }
  }

  /* The 'click' method of the prototype. */
  ElementList.prototype.click = function(callback) {
    /* Iterate over every element and set the 'click' event. */
    [].forEach.call(this, function(element) {
      element.addEventListener("click", function(event) {
        callback.call(this, event);
      });
    });
  }

  /* The 'addClass' method of the prototype. */
  ElementList.prototype.addClass = function(className) {
    /* Iterate over every element. */
    [].forEach.call(this, function(element) {
      /* Cache the classList of the element. */
      var list = element.classList;

      /* Add the specified className, if it doesn't already exist. */
      if (!list.contains(className)) list.add(className);
    });
  }

  /* The global callable. */
  window.$ = function(arg) {
    return new ElementList(arg);
  }
})();

/* Example */
$("#b1").click(function() {
  $(this).addClass("clicked");
  console.log(this);
});
<button id="b1">Click</button>

Upvotes: 2

Leonid Zakharov
Leonid Zakharov

Reputation: 970

Ok, I understand now your question. Let me try to help you again.

jQuery doesn't knows what DOM element do you use when you give it to selector. It doesn't parsing it or something else. Just save it to the internal property.

Very simplified code to understand:

$ = function(e) {

    // finding object. For example "this" is object
    if (typeof e !== 'object' || typeof e.className === 'undefined') {
        if (typeof e == 'string') {
            if (e[0] == '#') {
                e = document.getElementById(e.substring(1));
            } else if (e[0] == '.') {
                e = document.getElementsByClassName(e.substring(1))[0];
            } else {
                // ... etc
            }
        }
        // ... etc
    }

    var manager = {
        elem: e,
        addClass: function(newClass) {
            manager.elem.className = manager.elem.className + ' ' + newClass;
            return manager;
        },
        click: function(callback) {
            // here is just simple way without queues
            manager.elem.onclick = function(event) {
                callback.call(manager, event);
            }
        }
    }

    return manager;

}

Upvotes: 0

Ismail RBOUH
Ismail RBOUH

Reputation: 10460

One possible way (only selectors are accepted):

$ = function(selector) {
    this.elements = '';//Select the element(s) based on your selector

    this.addClass = function(klass) {
        //apply your klass to you element(s)
        return this;
    };

    this.click= function(handler) {
        //Attach click event to your element(s)            
        return this;
    };

    return this;

};

Please keep in mind it's just an example.

Edit 1:

In your click method you are calling the handler in the wrong scope (the anonymous function scope). You need to use the outer scope:

$.prototype = {
    click: function(callback) {
        console.log(this.length);
        var _self = this;
        for (var i = 0; i < this.length; i++) {
            this[i].onclick = function(event) {
                //this here presents the anonymous function scope
                //You need to call the handler in the outer scope
                callback.call(_self, event);
                //If you want to call the handler in the Element scope:
                //callback.call(_self[i], event); 
            }
        }
    }
}

Note: In your example, this.addClass("class2"); doesn't work because jQuery calls the click handler in the Element scope not jQuery scope. Therefore, this presents the Element which dosen't have the addClass method;

Upvotes: 0

Travis J
Travis J

Reputation: 82287

this is the native JavaScript element and only exposes the native API. You need to pass it to the jQuery constructor in order to access jQuery's API

$(this).addClass("class2"); // This will work

Upvotes: 1

Related Questions