18

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.

Toniq
  • 4,492
  • 12
  • 50
  • 109
  • 2
    for the record, jQuery is a Framework written *in Javascript* which means what you've got *is a Javascript solution* – Andrue Anderson Jan 08 '16 at 20:19
  • 1
    If it's working, then it works! You also might look into re-ordering using CSS with flexbox and `order`: http://www.w3schools.com/cssref/css3_pr_order.asp – RobertAKARobin Jan 08 '16 at 20:20

6 Answers6

22

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/

j2e
  • 556
  • 3
  • 10
13

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>
Nat
  • 325
  • 4
  • 14
John Balvin Arias
  • 2,632
  • 3
  • 26
  • 41
9

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>
Sitemas3
  • 158
  • 2
  • 3
  • 1
    It would be good to provide an example that applies to the question, in addition to the w3schools link. – Mark Stewart May 24 '18 at 19:10
  • This is a great, simple suggestion for doing this in cases when you can use flexbox. Thanks! – Milan Iliev Aug 28 '18 at 15:29
  • Good suggestion – alib0ng0 Apr 12 '19 at 14:56
  • Neat idea. Your tip is much appreciated – DroidOS Dec 01 '21 at 13:38
  • The specification, and [MDN](https://developer.mozilla.org/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items), explicitly recommend against using this kind of trick (except when the reordering is purely visual and has no semantics): “If you are using a reverse value, or otherwise reordering your items, you should consider whether you actually need to change the logical order in the source. The specification continues with a warning not to use reordering to fix issues in your source” – Olivier Cailloux Dec 27 '21 at 08:58
  • This solution will not be accessibly compliant, it breaks the tabbing order vs visual order and will not work great for some screen readers. – LB Ben Johnston Feb 08 '22 at 06:53
8

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] }) );
Thomas
  • 3,513
  • 1
  • 13
  • 10
  • I am not sure I understand what you mean by copy twice. I have tried your example, this does not copy events which I mentioned in my original post. (that is why I used jquery clone). I am missing something? – Toniq Jan 08 '16 at 23:20
  • Please show the exact code aou use and explain what particularly doesn't work. The Point in my code is, that it creates an intermediate Array of the actual DOM-Nodes in the correct order, and appends them to the wrapper. By adding the DOM-Node to a (new) parent it is first removed from the node it was in, and then added to the new one. No Node is cloned, no references/Events/Properties/anything changed, only the Position in the DOM-Tree. – Thomas Jan 09 '16 at 00:43
  • BY Copy twice i mean: first you create a clone of the current Node, and afaik $.html() this converts it into the string representation and parses this string-representation again (where again the Nodes are basically cloned). – Thomas Jan 09 '16 at 00:47
4

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.

Emeeus
  • 5,072
  • 2
  • 25
  • 37
0

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.

Dani Amsalem
  • 1,266
  • 17
  • 26