Toniq
Toniq

Reputation: 5016

Reorder html elements in dom

I have a list of elements:

<div class="wrapper">
    <div class="abc"></div>
    <div class="abc"></div>
    <div class="abc"></div>
</div>

And I have an array or numbers which represent new order:

var arr = [2,1,0];

I would like to reposition these abc divs to new positions inside parent wrapper.

Important thing is that abc divs have events and data attached to them and this needs to be preserved!

I have came up with this and it seems to be working as expected:

var wrapper = $('.wrapper'), items = wrapper.children('div.abc');

var orderedItems = $.map(arr, function(value) {
    return $(items).clone(true,true).get(value);
});
wrapper.empty().html(orderedItems);

I wanted to make sure this is the right way.

I could do with javascript solution as well if possible.

Upvotes: 18

Views: 34168

Answers (6)

Dani Amsalem
Dani Amsalem

Reputation: 1616

After reading through the others' solutions, I've created a simple bit of code to reverse elements such as divs or or lis that are wrapped in a single container element.

// Select all divs which need to be reordered
const items = document.querySelectorAll("#plan-carousel .plans-carousel .column-holder")
// Create an array of the divs and reverse them
const reverseItems = Array.from(items).reverse()
// Select the parent or wrapper element
const wrapper = document.querySelector("#plan-carousel .plans-carousel")
// Iterate over the reversed array and append each one to the parent element
reverseItems.forEach(item => wrapper.appendChild(item))

References:

  1. According to the MDN's appendChild() article: If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position.

In other words, since these items already exist, when we append them they are simply moved rather than duplicated.

Upvotes: 0

Emeeus
Emeeus

Reputation: 5260

This solution works better for me, we build an array of elements ([...wrapper.children]) then we use .sort based on a model ([5,4,3,2,1,0]) and then we use appendChild. Since the elements are the same as the originals, the event listeners remain working fine. Here the code:

//this is just to add event listeners
[...document.getElementsByClassName("abc")].forEach(e =>
  e.addEventListener("click", ev => console.log(ev.target.innerText))
);

var wrapper = document.getElementsByClassName("wrapper")[0];
var items = [...wrapper.children];

const newOrder = [5,4,3,2,1,0]

items.sort((a, b)=>newOrder.indexOf(items.indexOf(a)) - newOrder.indexOf(items.indexOf(b)));

items.forEach(it=>wrapper.appendChild(it));
<div class="wrapper">
  <div class="abc">0</div>
  <div class="abc">1</div>
  <div class="abc">2</div>
  <div class="abc">3</div>
  <div class="abc">4</div>
  <div class="abc">5</div>
</div>

As you can see, if you click on 0, 1, etc, the event listener works.

Upvotes: 4

John Balvin Arias
John Balvin Arias

Reputation: 2896

Keep in mind that when you add an element that is already in the DOM, this element will be moved, not copied.

CodePen

let wrapper=document.querySelector(".wrapper");
let children=wrapper.children;
let newOrder=[3,2,1,0];
//it means that the first element you want it to be the four element
//The second element you want it to be the third
//the third element you want it to be the second
//the four element you want it to be the first
for(let i=0;i<newOrder.length;i++){
  for(let j=0;j<newOrder.length;j++){
    if(i==newOrder[j]){
      wrapper.appendChild(children[j]);
      break;
    }
  }
}
<div class="wrapper">
  <div>a</div>
  <div>b</div>
  <div>c</div>
  <div>d</div>
</div>

Upvotes: 15

Sitemas3
Sitemas3

Reputation: 188

I know this is an old question, but google lead me to it. There is a sub property on flexbox (css) called 'order', that allow you to choose the order that elements are displayed. Is possible to use javascript to change this sub property and reorder the displayed elements.

https://www.w3schools.com/cssref/css3_pr_order.asp

Edit 1 - code example:

<style>
 .wrapper {
   display: flex;
   flex-flow: column;
 }
</style>

<div class="wrapper">
  <div class="abc">One</div>
  <div class="abc">Tow</div>
  <div class="abc">Three</div>
</div>
<button id="reorder" type="button">Reorder</button>
<button id="unreorder" type="button">Unreorder</button>

<script>
var reorderButton = document.querySelector('#reorder');
var unreorderButton = document.querySelector('#unreorder');
var abcDivs = document.querySelectorAll('.abc');
var newOrder = [2, 1, 0];

reorderButton.addEventListener('click', function() {
  abcDivs.forEach(function(element, index) {
    element.style.order = newOrder[index];
  });
});
unreorderButton.addEventListener('click', function() {
  abcDivs.forEach(function(element, index) {
    element.style.order = null;
  });
});
</script>

Upvotes: 12

j2e
j2e

Reputation: 566

If you want a pure Javascript solution (no jQuery involved)

var arr = [2,1,0];
var wrapper = document.getElementsByClassName("wrapper");
var items = wrapper[0].children;
var elements = document.createDocumentFragment();

arr.forEach(function(idx) {
    elements.appendChild(items[idx].cloneNode(true));
});

wrapper[0].innerHTML = null;
wrapper[0].appendChild(elements);

A little improvement of my previous answer. Fiddle: https://jsfiddle.net/jltorresm/1ukhzbg2/2/

Upvotes: 23

Thomas
Thomas

Reputation: 3593

no need to copy all items .. twice

var wrapper = $('.wrapper'), 
    items = wrapper.children('.abc'),
    arr = [2,1,0];

//items.detach(); if arr doesn't reuse all items
wrapper.append( $.map(arr, function(v){ return items[v] }) );

Upvotes: 8

Related Questions