0

I am writing a micro-library instead of using jQuery. I need only 3-4 methods ( for DOM traversal, Adding Eventlisteners etc). So I decided to write them myself instead of bloating the site with jQuery.

Here is the snippet from the code:

lib.js


window.twentyFourJS = (function() {

    let elements;

    const Constructor = function(selector) {
        elements = document.querySelectorAll(selector);
        this.elements = elements;
    };

    Constructor.prototype.addClass = function(className) {
        elements.forEach( item => item.classList.add(className));
        return this;
    };

    Constructor.prototype.on = function(event, callback, useCapture = false){
        elements.forEach((element) => {
            element.addEventListener(event, callback, useCapture);
        });
        return this;
    }

  const initFunction = function(selector){
        return new Constructor(selector);
    }
    
    return initFunction;
    
})(twentyFourJS);

script.js

(function($){

  $('.tab-menu li a').on('click', function(event) {

    console.log('I am clicked'); // This works
    
    this.addClass('MyClass'); // This does NOT work (as expected)

    // I want to be able to do this
    $(this).addClass('MyClass');

    event.preventDefault();

  });
})(twentyFourJS);

Basically I want to be able to use $(this) like we use it in jQuery.

this.addClass('MyClass') and $(this).addClass('MyClass') won't work and this is the expected behaviour.

As per my understanding this is referring to the plain HTML element. So it does not have access to any Constructor methods. it won't work.

And I have not written any code that will wrap element in Constructor object in $(this). I will have to do some changes to my Constructor so that I can access the Constructor functions using $(this). What are those changes/addition to it?

Kindly recommend only Vanilla JS ways instead of libraries.

Sandesh Yadav
  • 489
  • 5
  • 20
  • so in your constructor you need to see if it is a string or if it is an object.... – epascarello Feb 02 '22 at 14:45
  • You already use `jquery` (Under `script.js`) - So your question is a bit confusing (Jquery already have this ideas). In general start here: https://stackoverflow.com/questions/1051782/whats-the-difference-between-this-and-this – Ezra Siton Feb 02 '22 at 14:46
  • @EzraSiton I am not using jQuery. The code style just looks like it. – Sandesh Yadav Feb 02 '22 at 14:49
  • @epascarello Basically I want to be able to use `$(this)` like we use it in jQuery. – Sandesh Yadav Feb 02 '22 at 14:52
  • I understand that is what I said in my comment and in my answer. – epascarello Feb 02 '22 at 14:53
  • 1
    Note that your current code has a huge bug. (Even without `on`.) Because the `elements` variable is shared between different instances of `twentyFourJS` The following does not what you expect: `const $a = $(".a")` then `const $b = $(".b")` then `$a.addClass("c")`. This does **not** add "c" to the elements with the class "a", but adds it to the elements with class "b". Because the second initialisation will reassign the `elements` variable. Remove the variable and only use `this.elements` which is unique for each instance. – 3limin4t0r Feb 02 '22 at 15:21
  • @3limin4t0r Thanks for pointing out. That prevents from a big debugging in future. – Sandesh Yadav Feb 02 '22 at 15:47

3 Answers3

2

in your constructor you need to see what you have and handle it in different ways.

const Constructor = function(selector) {
    if (typeof selector === 'string') {
        elements = document.querySelectorAll(selector);
    } else {
      // need some sort of check to see if collection or single element
      // This could be improved since it could fail when someone would add a length property/attribute
      elements = selector.length ? selector : [selector]; 
    }
    this.elements = elements;
};
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • Alternative you could also use `"forEach" in selector` instead of `selector.length`. `forEach` seems to be the only method used. In some scenarios `length` is not present while `forEach` is. An example would be if you pass a Set instance, which uses `size` instead of `length`. - This also solves empty array passing. Which in this solution would nest the array. If `selector` is `[]`, then `elements` will be `[[]]` (because `0` is falsy). Resulting in errors if you then call `.addClass("a")`. – 3limin4t0r Feb 02 '22 at 15:31
1

All you really need to do is make sure your Constructor argument can distinguish between a string selector being passed in, and an object.

const Constructor = function(selector) {
  if(typeof selector == "string"){
    elements = document.querySelectorAll(selector);
    this.elements = elements;
  }
  else{
    this.elements = selector;
  }
};

You can go further than this, but at a very minimum for the example given that works.

Live example below:

window.twentyFourJS = (function() {

    let elements;

    const Constructor = function(selector) {
      if(typeof selector == "string"){
        elements = document.querySelectorAll(selector);
        this.elements = elements;
      }
      else{
        this.elements = selector;
      }
    };

    Constructor.prototype.addClass = function(className) {
        elements.forEach( item => item.classList.add(className));
        return this;
    };

    Constructor.prototype.on = function(event, callback, useCapture = false){
        elements.forEach((element) => {
            element.addEventListener(event, callback, useCapture);
        });
        return this;
    }

  const initFunction = function(selector){
        return new Constructor(selector);
    }
    
    return initFunction;
    
})();


(function($){

  $('.btn').on('click', function(event) {

    console.log('I am clicked'); // This works
    
    // I want to be able to do this
    $(this).addClass('MyClass');

    event.preventDefault();

  });
})(twentyFourJS);
.MyClass{
  background-color:red
}
<button class="btn">Click me</btn>
Jamiec
  • 133,658
  • 13
  • 134
  • 193
1
  1. first You Need to Check for a string case 1. $("div")
  2. Then You need to Check for it's NodeType and for a window

case 1. var elm = document .getElementById("ID") $(elm) case 2. $(this) -- window

function $(selector){
var element; 
 if (typeof selector === 'string') {
element = document.querySelectorAll(selector)
}
if (element.nodeType || element=== window) element= [selector];

return element ; 
}