Reputation: 11313
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:
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.
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
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
Reputation: 11313
With an extra 1.5 years of experience, this question becomes rather easy.
$
, so that, except string selectors, it can accept HTML
elements.HTML
element given.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
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
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
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