Javascript - eliminate elements that are either children or parents from an array

There is a code to remove child or parent elements from a random array that may contain both child and parent elements. For instance:

<html>

    <body>
        <div id='1'>
            <div id='2'>
                <div id='3'>
                </div>
                <div id='4'>
                </div>
            </div>
        </div>
        <div id='5'>
            <div id='6'>
            </div>
        </div>
    </body>

</html>

arr = document.getElementsByTagName('div')
// arr: [<div#1>,<div#2>, <div#3>, <div#4>, <div#5>, <div#6>]

From this example, how can I extract the children:

// arr: [<div#3>, <div#4><div#6>]

Or extract the parents:

// arr: [<div#1>, <div#5>]

Currently, the code being used is:

function isDescendant(parent, child) {
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}

function filterArray(arr, parent=true){
    newArr = [];
    arr.forEach((a)=>{
        bool = true

        if (parent){
            arr.forEach((b)=>{
                if (isDescendant(a, b)){
                    bool = false
                };
            });
        }
        else{
            arr.forEach((b)=>{
                if (isDescendant(b, a)){
                    bool = false
                };
            });            
        }

        if(bool){
            newArr.push(a)
        }
    });
    return newArr
};

Is there a more efficient solution available?

Answer №1

When dealing with arrays in JavaScript, you can utilize the filter method to effectively filter through the elements. To determine if a specific node is a parent or child node of another node, the contains method can be used. Alternatively, the more general compareDocumentPosition method can also be applied.

const nodes = Array.from(document.body.querySelectorAll("div"));

//One approach is to identify parent nodes by eliminating nodes that are not contained by any other node in the array:
let parents = nodes.filter( n => !nodes.find( m => m !== n && m.contains(n) ));
//To find child nodes, the contains check can be inverted to find nodes that do not contain any other node in the array:
let children = nodes.filter( n => !nodes.find( m => m !== n && n.contains(m) ));
console.log("approach 1:\n", parents, "\n", children);

//The same approach using compareDocumentPosition instead of contains:
parents = nodes.filter( n => !nodes.find(m => m.compareDocumentPosition(n) & Node.DOCUMENT_POSITION_CONTAINED_BY) );
children = nodes.filter( n => !nodes.find(m => n.compareDocumentPosition(m) & Node.DOCUMENT_POSITION_CONTAINED_BY) )

console.log("approach 2:\n", parents, "\n", children);

//For a different perspective without considering elements in the array, you can compare nodes to topmost parent or check if they have any children:
const topElement = document.body;
parents = nodes.filter( n => n.parentElement === topElement );
children = nodes.filter( n => !n.childElementCount );
console.log("approach 3:\n", parents, "\n", children);
<div id='1'>
  <div id='2'>
    <div id='3'>
    </div>
    <div id='4'>
    </div>
  </div>
</div>
<div id='5'>
  <div id='6'>
  </div>
</div>

A performance comparison indicates that the last method is the fastest, followed by the contains approach. The slowest method is using compareDocumentPosition, although it is still quicker than using filterArray to obtain the child array.

Answer №2

When dealing with children, one can easily convert a collection to an array and then utilize the filter() method along with querySelector(). This approach ensures that no elements are returned in case the specified element is not a parent.

As for outer parents, one can make use of Array#some() and node#contains() within the filter() function.

const nodeList = Array.from(document.querySelectorAll('div'));

const innerChildren = nodeList.filter(elem => !elem.querySelector('*')); // You can also use the same selector as the main query

const outerParents = nodeList.filter(node => !nodeList.some(elem => elem !== node && elem.contains(node)));

console.log(innerChildren) // div#3, div#4 & div#6  
console.log(outerParents) //  div#1, div#5
<div id='1'>
  <div id='2'>
    <div id='3'></div>
    <div id='4'></div>
  </div>
</div>
<div id='5'>
  <div id='6'></div>
</div>

Answer №3

Give this a try. It selects the correct answers, although not in a generic way. Hopefully, it will work for your situation. It's also important to minimize data loops within the scene.

const elements = Array.from(document.body.querySelectorAll("div"));

const children = elements.filter( element => 0 === element.querySelectorAll('div').length );
const ancestors = elements.filter( element => 'DIV' !== element.parentNode.nodeName );

Check out this JsFiddle for a demonstration: https://jsfiddle.net/3nhzvat9/

Another solution that may offer even better performance:

const elements = document.body.querySelectorAll("div");
const children = [];
const ancestors = [];

elements.forEach( element => {
   if(0  === element.querySelectorAll('div').length) {
      children.push(element);
   }
   if('DIV' !== element.parentNode.nodeName){
     ancestors.push(element);
   }
});

Check out this second JsFiddle for another alternative: https://jsfiddle.net/3nhzvat9/1/

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

tslint issues detected within a line of code in a function

I am a novice when it comes to tslint and typescript. Attempting to resolve the error: Unnecessary local variable - stackThird. Can someone guide me on how to rectify this issue? Despite research, I have not been successful in finding a solution. The err ...

Updates to Firebase data do not automatically reflect in Google Charts

I've successfully connected my Firebase to Google Charts, however I am facing an issue in displaying a 'no data' message when there is no data available. Currently, I have set a Firebase data value of 'NA' for a single user, which ...

What is the best way to retrieve the value of a text box in VUEjs using its unique identifier?

In my form, there are a total of two text boxes with predefined values. However, I am looking for a way to retrieve the value of one specific textbox based on the entered ID number. For example, if I input "1," I expect to see the value of text box 1 only ...

Updating the variable in Angular 6 does not cause the view to refresh

I am facing an issue with my array variable that contains objects. Here is an example of how it looks: [{name: 'Name 1', price: '10$'}, {name: 'Name 2', price: '20$'}, ...] In my view, I have a list of products bei ...

Using Angular 2 to pass a function as an argument to a constructor

How can I create a custom @Component factory where a function is called to return a component, passing the widgetName to the constructor or super constructor? export function createCustomKendoComponent(selector: string, **widgetName**: string) { @Co ...

Troubleshooting AngularJS binding problem when using ngRepeat to handle Collapse and Expand Caret icon

I am experimenting with implementing collapsible and expandable panels within an ngRepeat loop. Here is my approach: <tbody ng-repeat="item in Items"> <tr data-toggle="collapse" class="accordion-toggle"> <td>{{item.name}}< ...

Repeated instances of the same name appearing in the dropdown list every time the tab button is clicked

When using my function in the onclick nav tabs event (triggered by clicking on any tab), I have a requirement where I need to ensure that no duplicate names are inserted into the dropdown list. The current function is working perfectly, but I am looking fo ...

A guide on fetching the selected date from a datepicker in framework7 with the help of vuejs

Here is a snippet of the code for a component I am working on: <f7-list-input label=“Fecha de nacimiento” type=“datepicker” placeholder=“Selecciona una fecha” :value=“perfil.fecha_nacimiento” @input=“perfil.fecha_nacimiento = $event.t ...

Utilize jQuery to style the background of the webpage, while excluding the Modal element when using Bootstrap

I'm currently working on implementing foggy.js () to create a blurred background effect for Bootstrap's Modal. While I've successfully blurred all elements in the background, the Modal Popup itself is also appearing blurry. So my question is ...

Sending properties of an element to a function within Angular version 4 or 5

Trying to pass attribute values of elements to a function on button click, this is my approach: <div> <ul #list> <li class="radio" *ngFor="let option of options; let j = index" id={{i}}-{{j}} #item> <label><input t ...

Send location data to the PHP server through AJAX and then fetch it in JavaScript once it has been handled

I am looking to transfer coordinates from client-side JavaScript to server-side PHP using Ajax, and then retrieve the processed result back to JavaScript for further use. While I have managed to pass the data to PHP successfully, I am struggling to figure ...

React - Stopping the Submit Action

Recently, I have been delving into React development. In my exploration, I have incorporated the Reactstrap framework into my project. However, I have encountered an issue where the HTML form submits when a button is clicked. Is there a way to prevent this ...

Strip certain tags from HTML without including iframes

Removing specific tags from HTML can be tricky, especially when working with PHP and JavaScript. One approach is to fetch an HTML page in a PHP file using curl and getJSON, storing the result in a .js file. However, this method may encounter issues with mu ...

Synchronizing other components with a filtered query from the List in Admin-on-rest: Best practices

I am looking to incorporate the following structure into my page: 1. Cards displaying summaries (revenue, users, etc.) 2. Google Maps integration 3. List element Within the List element, there is filtering involved. I am struggling with how to utili ...

Terminate a targeted recipient following the completion of the on event

I am currently running a node service with socket.io and utilizing an event emitter to send updates based on user context. For example, context A may have users 1, 2, and 3 while context B has users 4 and 5. Once a successful connection is established wit ...

Encountering difficulties while attempting to delete with a router.delete command - receiving a 404 not

Within my application, I am passing the request parameter 'id' in the router.delete method and communicating it with the Vuex service. However, when triggering the action, an API call is made but it results in a 404 error indicating "not found" a ...

Is there a way to remove a row through fetch using onclick in reactjs?

I'm completely new to this and struggling with deleting a row using fetch. I've written some messy code and have no idea if it will even work. Please help, I feel so lost... renderItem(data, index) { return <tr key={index} > &l ...

retrieve the most recent file following a $group operation

I am currently utilizing the official MongoDB driver specifically designed for Node.js. This is how my message data is organized. Each post consists of a timestamp, a user ID, and a topic ID. [ { "_id" : ObjectId("5b0abb48b20c1b4b92365145"), "t ...

MUI Gradient Tracked Button

Take a look at this Codepen example I created. It showcases a hover effect where the gradient follows the mouse cursor. In order to achieve this effect, I have defined two CSS variables - --x and --y, to keep track of the mouse position on the button. The ...

Using Next-auth.js: Redirecting from getServerSideProps to a specific callback URL - How can it be done?

Hey, I've been working on implementing authentication with the NextAuth library in my Next.js application. While following the documentation, I encountered a situation that wasn't covered. I'm attempting to create a 'route guard' o ...