8

I am writing a few functions to simplify my interaction with Javascript Nodes, here is the source-code so far:

Node.prototype.getClasses = function() {
    return this.className ? this.className.split(" ") : "";
};

Node.prototype.hasClass = function(c) {
    return this.getClasses().indexOf(c) >= 0;
};

Node.prototype.addClass = function(c) {
    if (!this.hasClass(c)) {
        this.className += " " + c;
    }
    return this;
};

Node.prototype.removeClass = function(c) {
    if (this.hasClass(c)) {
        var classes = this.getClasses();
        var newClasses = [];
        for (var index = 0; index < classes.length; index++) {
            if (classes[index] !== c) {
                newClasses.push(classes[index]);
            }
        }
        this.className = newClasses.join(" ");
    }
    return this;
};

function NodeCollection(nodes) {
    this.nodes = nodes;
    this.addClass = (c) => {
        for (var nodeIndex = 0; nodeIndex < this.nodes.length; nodeIndex++) {
            this.nodes[nodeIndex].addClass(c);
        }
        return this.nodes;
    };
    this.removeClass = (c) => {
        for (var nodeIndex = 0; nodeIndex < this.nodes.length; nodeIndex++) {
            this.nodes[nodeIndex].removeClass(c);
        }
        return this.nodes;
    };
    this.getHTML = () => {
        var output = "";
        for (var nodeIndex = 0; nodeIndex < this.nodes.length; nodeIndex++) {
            output += this.nodes[nodeIndex].outerHTML;
        }
        return output;
    };
    this.each = (f) => {
        for (var nodeIndex = 0; nodeIndex < this.nodes.length; nodeIndex++) {
            f(this.nodes[nodeIndex]);
        }
        return this.nodes;
    };
}

Node.prototype.query = function(s) {
    return new NodeCollection(this.querySelectorAll(s));
};

Node.prototype.siblings = function(s) {
    var rawSiblings = this.parentNode.querySelectorAll(s);
    var output = [];
    for (var siblingIndex = 0; siblingIndex < rawSiblings.length; siblingIndex++) {
        if ((rawSiblings[siblingIndex].parentNode === this.parentNode) && (rawSiblings[siblingIndex] !== this)) {
            output.push(rawSiblings[siblingIndex]);
        }
    }
    return new NodeCollection(output);
};

Everything is working great and I am quite content with these functions, I have managed to prevent a lot of headaches without the usage of Javascript frameworks (a hobby project).

Now, I would like to be able to write a query function for NodeCollection as well, however, I am not quite aware of how should I concatenate the nodes members of the NodeCollection objects, which are instances of NodeList. I would like to write something like this as a member function of NodeCollection:

this.query = (s) => {
    //create an empty NodeList
    for (var index = 0; index < this.nodes.length; index++) {
        //concat this[nodes][index] to the node list created outside the 
        //cycle avoiding possible duplicates
    }
    //return the concatenated NodeList
};

How can I achieve this?

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • You want to query the existing nodes inside the NodeCollection or add the results of query to DOM to the existing nodes? – gurvinder372 Dec 21 '17 at 14:03
  • Why not turn the nodeList to a proper array, and then you can freely concat. this.nodes = Array.from(nodes); – Orr Siloni Dec 21 '17 at 14:05
  • Using jQuery, you solve it. – Miquel Al. Vicens Dec 21 '17 at 14:05
  • @gurvinder372 I would like to query the existing nodes inside the NodeCollection and return a concatenated result without duplicates. – Lajos Arpad Dec 21 '17 at 14:11
  • @LajosArpad concatenate the result with which array? the same nodelist which you have just queried? – gurvinder372 Dec 21 '17 at 14:12
  • @OrrSiloni the reason is that querySelectorAll returns a NodeList and I would like to be consistent with that. Of course your suggestion is an alternative, which, if there is no solution to concatenate NodeList objects might turn out to be the road to go on, but I intended to have a little bit more consistency, hence the question. – Lajos Arpad Dec 21 '17 at 14:13
  • @MiquelAl.Vicens Using the solution already implemented at jquery will not answer the question. – Lajos Arpad Dec 21 '17 at 14:14
  • @gurvinder372 yes, I have called query(), returning some items and called each for those to perform some actions. Then, I would like to query them to find some descendants and call an each for them. – Lajos Arpad Dec 21 '17 at 14:16
  • @LajosArpad, I know. It was a advice. – Miquel Al. Vicens Dec 21 '17 at 14:16
  • @MiquelAl.Vicens I have been using jquery since 2009. I know it has a solution. – Lajos Arpad Dec 21 '17 at 14:17
  • 2
    @LajosArpad So, you are looking for a method to concatenate two nodelist while avoiding duplicates? – gurvinder372 Dec 21 '17 at 14:24
  • When you recollect a HTML element, compare with strict equal (`===`). – Miquel Al. Vicens Dec 21 '17 at 14:30
  • @gurvinder372 That is exactly what I am looking for. – Lajos Arpad Dec 21 '17 at 14:33
  • Uh, don't you know about [`classList`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)? Also keep these methods on the `Element` interface, not on the `Node` interface. – Bergi Dec 21 '17 at 16:23

1 Answers1

4

How to concatenate two NodeList objects into one, avoiding duplicates

Use isSameNode and Array.from

Array.from( nodeList1 ).forEach( function( ele, index ){
  var isDuplicate = Array.from( nodeList2 ).some( ( ele2 ) => ele.isSameNode(ele2) );
  if ( !isDuplicate )
  {  
      nodeList2[ nodeList2.length ] = ele;
  }
})

Now nodeList2 has all the nodes from nodeList1 which are not duplicates.

Demo

var nodeList1 = Array.from( document.querySelectorAll(".a") );
var nodeList2 = Array.from( document.querySelectorAll(".b") );

console.log( "original length " + nodeList1.length, nodeList2.length );

nodeList1.forEach(function(ele, index) {
  var isDuplicate = nodeList2.some( ele2 => ele.isSameNode(ele2));
  //console.log( ele, isDuplicate );
  if (!isDuplicate) {
    nodeList2.push( ele );
  }
});

console.log( "Final length " + nodeList1.length , nodeList2.length );
<div class="a b"></div>
<div class="a"></div>
<div class="b"></div>
<div class="a b"></div>
gurvinder372
  • 66,980
  • 10
  • 72
  • 94
  • Very interesting, I have upvoted it, but at first glance this seems to result in an array. If it is impossible to have a NodeList resulting from the concatenation, then I will accept this answer. – Lajos Arpad Dec 21 '17 at 14:40
  • @LajosArpad Nodelist is a static collection. I don't think I can mutate it – gurvinder372 Dec 21 '17 at 14:45
  • I am trying share a demo on this. – gurvinder372 Dec 21 '17 at 14:45
  • The creation of a new NodeList srpings to mind as a possible alternative. – Lajos Arpad Dec 21 '17 at 14:46
  • @LajosArpad If you try to invoke `NodeList()` or `new NodeList()`, it throws an error *Uncaught TypeError: Illegal constructor* – gurvinder372 Dec 21 '17 at 14:48
  • "*why can't I use the `NodeList` constructor to create a `NodeList`?*" - Because the [DOM specification for the `NodeList` interface](https://dom.spec.whatwg.org/#interface-nodelist) does not specify the [WebIDL \[Constructor\] attribute](https://www.w3.org/TR/WebIDL/#Constructor), so it cannot be created directly in user scripts. Quoted from: https://stackoverflow.com/a/38441943/82548 – David Thomas Dec 21 '17 at 14:50
  • Okay, it seems that what I initially wanted is impossible and the second best after that is your elegant solution. Very nice, thanks. – Lajos Arpad Dec 21 '17 at 14:52
  • @DavidThomas `toNodeList` given in the link you have shared is quite creative :) – gurvinder372 Dec 21 '17 at 14:52
  • @LajosArpad You can try the toNodeList method in the link shared by DavidThomas – gurvinder372 Dec 21 '17 at 14:53