Randomize a sequence of div elements with jQuery

JqueryHtmlSortingHtml Manipulation

Jquery Problem Overview


I'm trying to do my frist steps with jQuery but I have some trouble to understand how to find a list of child elements from a div parent element. I'm used to work with ActionScript 2 and ActionScript 3 so i could mistake some concept, like what is the better way to randomize a sequence of div elements with jQuery!

I have this simple portion of HTML code:

<div class="band">
    <div class="member">
        <ul>
            <li>John</li>
            <li>Lennon</li>
        </ul>
    </div>
    <div class="member">
        <ul>
            <li>Paul</li>
            <li>McCartney</li>
        </ul>
    </div>
    <div class="member">
        <ul>
            <li>George</li>
            <li>Harrison</li>
        </ul>
    </div>
    <div class="member">
        <ul>
            <li>Ringo</li>
            <li>Starr</li>
        </ul>
    </div>
</div>

I have attempted some way to do that like map .member divs in one array and then changing the sort order but without success.

function setArrayElements (element_parent) {
    var arr = [];
    //alert (element_parent[0].innerHTML);
    for (var i = 0; i < element_parent.children().length; i ++) {
        arr.push (element_parent[i].innerHTML);
    }
}
setArrayElements($(".band"));

when i attempted to alert element_parent[0] i thought to get the first child of my .member list of divs but it isn't.

if i make an alert with element_parent[0].innerHTML i see that:

<div class="member">
    <ul>
        <li>John</li>
        <li>Lennon</li>
    </ul>
</div>
<div class="member">
    <ul>
        <li>Paul</li>
        <li>McCartney</li>
    </ul>
</div>
<div class="member">
    <ul>
        <li>George</li>
        <li>Harrison</li>
    </ul>
</div>
<div class="member">
    <ul>
        <li>Ringo</li>
        <li>Starr</li>
    </ul>
</div>

Why? How can I do to get exactly one of the members like this?

<div class="member">
    <ul>
        <li>Paul</li>
        <li>McCartney</li>
    </ul>
</div>

I'm sure this should be easy but I just don't know how :(

please help
thanks
vittorio


EDIT:

Thanks for the fastness and this various ways to get the selected children, I'll take a note of these ways for the future!
I tried this methods, but it seems I couldn't get the entire div (please tell'me if i mistake something, it' could be too much possible!!).

I shoud get this content:

<div class="member">
    <ul>
        <li>Ringo</li>
        <li>Starr</li>
    </ul>
</div>

but with one of this methods like $("div.band div.member:eq(2)") or the other useful ways, I get this:

alert ($('div.band div.member')[0]);
/* result
<ul>
    <li>Ringo</li>
    <li>Starr</li>
</ul>
*/

so is there a way to get the .member div container too in my node?

Jquery Solutions


Solution 1 - Jquery

$('div.band div.member');

will give you a jQuery object containing <div> that match the selector i.e. div with class member that are descendents of a div with class band.

The jQuery object is an array-like object in that each matched element is assigned a numerical property (think like an index) of the object and a length property is also defined. To get one element is

// first element
$('div.band div.member')[0];

or

// first element
$('div.band div.member').get(0);

Instead of selecting all elements, you can specify to select a specific element according to position in the DOM. For example

// get the first div with class member element
$("div.band div.member:eq(0)")

or

// get the first div with class member element
$("div.band div.member:nth-child(1)")

EDIT:

Here's a plugin I just knocked out

(function($) {

$.fn.randomize = function(childElem) {
  return this.each(function() {
      var $this = $(this);
      var elems = $this.children(childElem);
      
      elems.sort(function() { return (Math.round(Math.random())-0.5); });  

      $this.detach(childElem);  

      for(var i=0; i < elems.length; i++)
        $this.append(elems[i]);      

  });    
}
})(jQuery);

###Working Code:

$('button').click(function() {
  $("div.band").randomize("table tr td", "div.member");
});

(function($) {

  $.fn.randomize = function(tree, childElem) {
    return this.each(function() {
      var $this = $(this);
      if (tree) $this = $(this).find(tree);
      var unsortedElems = $this.children(childElem);
      var elems = unsortedElems.clone();

      elems.sort(function() {
        return (Math.round(Math.random()) - 0.5);
      });

      for (var i = 0; i < elems.length; i++)
        unsortedElems.eq(i).replaceWith(elems[i]);
    });
  };

})(jQuery);

body {
  background-color: #000;
  font: 16px Helvetica, Arial;
  color: #fff;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="band">
  <table>
    <tr>
      <td>
        <div class="member">
          <ul>
            <li>John</li>
            <li>Lennon</li>
          </ul>
        </div>
      </td>
    </tr>
    <tr>
      <td>
        <div class="member">
          <ul>
            <li>Paul</li>
            <li>McCartney</li>
          </ul>
        </div>
      </td>
    </tr>
    <tr>
      <td>
        <div class="member">
          <ul>
            <li>George</li>
            <li>Harrison</li>
          </ul>
        </div>
      </td>
    </tr>
    <tr>
      <td>
        <div class="member">
          <ul>
            <li>Ringo</li>
            <li>Starr</li>
          </ul>
        </div>
      </td>
    </tr>
  </table>
</div>
<button>Randomize</button>

Here's an Editable Working Demo. add /edit to the URL to see the code. If you need any details about how it works, please leave a comment. It could probably do with being made more robust to handle certain situations (like if there are other child elems of the jQuery object the plugin is chianed to) but it'll suit your needs.

Solution 2 - Jquery

I modified Russ Cam's solution so that the selector is optional, and the function can be called on multiple container elements, while preserving each randomized element's parent.

For example, if I wanted to randomize all LIs within each '.member' div, I could call it like this:

$('.member').randomize('li');

I could also do it like this:

$('.member li').randomize();

So the two ways to call this are as follows:

$(parent_selector).randomize(child_selector);

OR

$(child_selector).randomize();

Here's the modified code:

$.fn.randomize = function(selector){
	(selector ? this.find(selector) : this).parent().each(function(){
		$(this).children(selector).sort(function(){
			return Math.random() - 0.5;
		}).detach().appendTo(this);
	});

	return this;
};

Minified:

$.fn.randomize=function(a){(a?this.find(a):this).parent().each(function(){$(this).children(a).sort(function(){return Math.random()-0.5}).detach().appendTo(this)});return this};

Solution 3 - Jquery

Randomization using sort does not always randomize the elements. It is better to use a shuffle method like the one from https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript

Here's my updated code

(function($) {
	$.fn.randomize = function(childElem) {
		function shuffle(o) {
			for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
			return o;
		};
		return this.each(function() {
			var $this = $(this);
			var elems = $this.children(childElem);
			
			shuffle(elems);
			
			$this.detach(childElem);  

			for(var i=0; i < elems.length; i++) {
				$this.append(elems[i]);      
			}
		});    
	}
})(jQuery);

Solution 4 - Jquery

Just in case you are ok with doing it without jquery:

/** @param {HTMLElement} parent */
const shuffleChildren = (parent) => {
    const cards = [...parent.children];
    for (const child of cards) {
        const newPlace = Math.floor(Math.random() * cards.length);
        parent.insertBefore(child, cards[newPlace + 1] || null);
    }
};

(an answer unrelated to jquery was closed as duplicate of this one, so posting here)

Solution 5 - Jquery

This solution is inspired by the existing solutions, but uses the Fisher-Yates algorithm for sorting.

This ensures that the elements are randomized fairly, which is important for many use cases.

$.fn.randomizeChildrenOrder = function() {
    this.each(function(){
        let childrenArray = $(this).children().toArray();
        let shuffledChildrenArray = fisherYatesShuffle(childrenArray);
        $(shuffledChildrenArray).detach().appendTo(this);
    });

    return this;
    
    // https://stackoverflow.com/a/2450976/665825
    function fisherYatesShuffle(array){
        let currentIndex = array.length, temporaryValue, randomIndex;
        while (currentIndex !== 0) {
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }
        return array;
    }
}

Usage: call the function on the parent element of the elements you need to shuffle. If the elements share the parent element with elements that need to be left alone, add an extra parent around them, so this is no longer the case.

$('.member ul').randomizeChildrenOrder();

Generalized:

$(parentSelector).randomizeChildrenOrder();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionvittoView Question on Stackoverflow
Solution 1 - JqueryRuss CamView Answer on Stackoverflow
Solution 2 - JquerygrupplerView Answer on Stackoverflow
Solution 3 - JquerywhitebrowView Answer on Stackoverflow
Solution 4 - JqueryKlesunView Answer on Stackoverflow
Solution 5 - JqueryMark LagendijkView Answer on Stackoverflow