1

This is not for use in my project, Only for learning purposes.

In jQuery,
When we call $('h1'). it simply returns all the h1 elements from the document. Again when we make some action on an element like $('h1').hide(), it simply hides all the elements(cool ah?)


I want to learn this similar functionality, for example:

function app(elm){
  const x = (typeof elm !== 'object') ? document.querySelectorAll(elm) : elm

   return {
      hide : function(){
         x.forEach( target =>{
             target.style.display = 'none';
         });
      }
   }
}

This is a simple code here. So, If I call it like app('h1').hide(); it will hide all the h1 elements from the document. But if I call it like app('h1') it returns the object what I return that's normal.
In here I need all h1 elements from the document like jQuery. I mean It should work like this,

$('h1') === app('h1') //JQuery is equal to myCFunction (problem)
$('h1').hide === app('h1').hide() //jQuery is equal to myCFunction (solved)

[NOTE] Here is an article that is similar to my question but it's not my question answer. Article Link

Pedro Coelho
  • 1,411
  • 3
  • 18
  • 31
  • Your code works for `app(...)` just like jquery. But why do you think it is not? normally jquery uses `#name` or `.name` for elm, and I have try both are working. So are you looking for returning as a list of HTML element for `app(...) or a single element that could start to use like HTML DOM in js? – DeadlYBlinder Feb 26 '20 at 06:41

3 Answers3

0

You can return x instead of a custom object, but before returning inject the hide function into x object's prototype like x.prototype.hide = function(){/*...*/}.

Infant Ajay
  • 9
  • 1
  • 1
  • But every time I need to create this prototype when I will create a new function. Is jquery done same thing? –  Feb 26 '20 at 16:07
  • Yes, JQuery uses prototype inheritance extensively, [take a peek](https://github.com/jquery/jquery/blob/721744a9fab5b597febea64e466272eabfdb9463/src/core.js#L31). – Infant Ajay Feb 28 '20 at 06:21
0

I think $("h1") does not return selected elements. It stores the selected elements. Instead we can have new function(getElement) to get select elements.Hope this code helps.

var App = function() {
        var x ;
        this.app = function (elem) {
            x = document.querySelectorAll(elem);
            return this;
        }
        this.hide = function(){
            x.forEach(target => {
                target.style.display = 'none';
            });
            return;
        }
        this.getElement = function(){
            return x;
        }

    }
    var $ = new App();
    $.app("h1").hide();
    console.log($.app("h1").getElement());
sachin
  • 777
  • 5
  • 10
0

I've got a mostly working solution, but you still have to fix one small but annoying problem (see caveat 3). It's mostly done so I'll put it here anyway.

I think this is what you are looking for:

function app(selector) {    
    const retArr = document.querySelectorAll(selector); // The array to return

    // Add proxies for all prototype methods of all elements
    for (let e of retArr) {
        let methods = getProtoMethods(e);
        for (let mKey in methods) {
            // Skip if the proxy method already exists in retArr
            if (retArr[mKey] !== undefined) continue;

            // Otherwise set proxy method
            Object.defineProperty(retArr, mKey, {
                value: function(...args) {
                    // Loop through all elements in selection
                    retArr.forEach(el => {
                        // Call method if it exists 
                        if (el[mKey] !== undefined) el[mKey](...args);
                    });
                }
            });
        }
    }

    return retArr;

    // Gets all prototype methods for one object
    function getProtoMethods(obj) {
        let methods = {};
        // Loop through all prototype properties of obj and add all functions
        for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
            // Skip properties that aren't functions and constructor
            if (pKey !== "constructor" && typeof obj[pKey] === "function") {
                methods[pKey] = obj[pKey];
            }
        }
        return methods;
    }
}

The idea is to put all the selected objects in an array, then define additional methods on the array. It should have all the method names of the selected objects, but those methods are actually proxies of those original methods. When one of these proxy methods is called, it calls the original method on all (see caveat 1) the selected objects in the array. But otherwise the returned object can just be used as a normal array (or more accurately, NodeList in this case).


However it's worth mentioning that there are several caveats with this particular implementation.

  1. The list of proxy methods created is the union of the methods of all selected objects, not intersection. Suppose you selected two elements - A and B. A has method doA() and B has method doB(). Then the array returned by app() will have both doA() and doB() proxy methods. However when you call doA() for example, only A.doA() will be called because obviously B does not have a doA() method.
  2. If the selected objects do not have the same definition for the same method name, the proxy method will use their individual definitions. This is usually desired behaviour in polymorphism but still it's something to bear in mind.
  3. This implementation does not traverse the prototype chain, which is actually a major problem. It only looks at the prototypes of the selected elements, but not the prototypes of prototypes. Therefore this implementation does not work well with any inheritance. I did try to get this to work by making getProtoMethods() recursive, and it does work with normal JS objects, but doing that with DOM elements throws weird errors (TypeError: Illegal Invocation) (see here). If you can somehow fix this problem then this would be a fully working solution.

This is the problematic recursive code:

// Recursively gets all nested prototype methods for one object
function getProtoMethods(obj) {
    let methods = {};
    // Loop through all prototype properties of obj and add all functions
    for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
        // Skip properties that aren't functions and constructor
        // obj[pKey] throws error when obj is already a prototype object
        if (pKey !== "constructor" && typeof obj[pKey] === "function") {
            methods[pKey] = obj[pKey];
        }
    }
    // If obj's prototype has its own prototype then recurse.
    if (Object.getPrototypeOf(Object.getPrototypeOf(obj)) == null) {
        return methods;
    } else {
        return {...methods, ...getProtoMethods(Object.getPrototypeOf(obj))};
    }
}

Sorry I cannot solve your problem 100%, but hopefully this at least somewhat helpful.

cyqsimon
  • 2,752
  • 2
  • 17
  • 38