javascript - shuffle HTML list element order - javascript

I have a list:
<ul>
<li>milk</li>
<li>butter</li>
<li>eggs</li>
<li>orange juice</li>
<li>bananas</li>
</ul>
Using javascript how can I reorder the list items randomly?

var ul = document.querySelector('ul');
for (var i = ul.children.length; i >= 0; i--) {
ul.appendChild(ul.children[Math.random() * i | 0]);
}
This is based on Fisher–Yates shuffle, and exploits the fact that when you append a node, it's moved from its old place.
Performance is within 10% of shuffling a detached copy even on huge lists (100 000 elements).
http://jsfiddle.net/qEM8B/

Simply put, like this:
JS:
var list = document.getElementById("something"),
button = document.getElementById("shuffle");
function shuffle(items)
{
var cached = items.slice(0), temp, i = cached.length, rand;
while(--i)
{
rand = Math.floor(i * Math.random());
temp = cached[rand];
cached[rand] = cached[i];
cached[i] = temp;
}
return cached;
}
function shuffleNodes()
{
var nodes = list.children, i = 0;
nodes = Array.prototype.slice.call(nodes);
nodes = shuffle(nodes);
while(i < nodes.length)
{
list.appendChild(nodes[i]);
++i;
}
}
button.onclick = shuffleNodes;
HTML:
<ul id="something">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<button id="shuffle" type="button">Shuffle List Items</button>
Demo: http://jsbin.com/itesir/edit#preview

var list = document.getElementById("something");
function shuffleNodes() {
var nodes = list.children, i = 0;
nodes = Array.prototype.sort.call(nodes);
while(i < nodes.length) {
list.appendChild(nodes[i]);
++i;
}
}
shuffleNodes();

Use this:
function htmlShuffle(elem) {
function shuffle(arr) {
var len = arr.length;
var d = len;
var array = [];
var k, i;
for (i = 0; i < d; i++) {
k = Math.floor(Math.random() * len);
array.push(arr[k]);
arr.splice(k, 1);
len = arr.length;
}
for (i = 0; i < d; i++) {
arr[i] = array[i];
}
return arr;
}
var el = document.querySelectorAll(elem + " *");
document.querySelector(elem).innerHTML = "";
let pos = [];
for (let i = 0; i < el.length; i++) {
pos.push(i);
}
pos = shuffle(pos);
for (let i = 0; i < pos.length; i++) {
document.querySelector(elem).appendChild(el[pos[i]]);
}
}
htmlShuffle("ul");
<ul>
<li>milk</li>
<li>butter</li>
<li>eggs</li>
<li>orange juice</li>
<li>bananas</li>
</ul>

Here is a very simple way to shuffle with JS:
var points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return 0.5 - Math.random()});
http://www.w3schools.com/js/js_array_sort.asp

I was searching for a prototype function. Maybe this helps someone.
Element.prototype.shuffleChildren = function() {
for (var i = this.children.length; i >= 0; i--) {
this.appendChild(this.children[Math.random() * i | 0]);
}
};
document.querySelector('body').shuffleChildren();

Here's a solution that does not use a loop.
function shuffle_children(element) {
element.append(...Array.from(element.children).sort(function () {
return Math.random() - 0.5;
}));
}

Based no #Alexey Lebedev's answer, if you prefer a jQuery function that shuffles elements, you can use this one:
$.fn.randomize = function(selector){
var $elems = selector ? $(this).find(selector) : $(this).children();
for (var i = $elems.length; i >= 0; i--) {
$(this).append($elems[Math.random() * i | 0]);
}
return this;
}
And then call it like this:
$("ul").randomize(); //shuffle all the ul children
$("ul").randomize(".item"); //shuffle all the .item elements inside the ul
$(".my-list").randomize(".my-element"); //shuffle all the .my-element elements inside the .my-list element.

Related

unable to delete random array elements on onclick event

I have an array and displaying its content wrapped up in a P tag on the body, now how do I delete the element when I click on the specific element.
JS:
var arr = [10, 20, 30];
var demo = document.getElementById('demo');
for( var i = 0; i < arr.length; i++) {
demo.innerHTML += `<p class="tag">${arr[i]} </p>`;
}
var pTag = document.getElementsByClassName("tag");
for( var j = 0; j < pTag.length; j++) {
pTag[j].onclick = function() {
arr.splice(pTag[j], 1);
}
}
Use array methods instead - don't use var with asynchronous code, since it gets hoisted and has function scope instead of block scope.
const arr = [10, 20, 30, 40, 50];
const demo = document.getElementById('demo');
arr.forEach(num => {
const p = demo.appendChild(document.createElement('p'));
p.textContent = num;
p.addEventListener('click', () => {
arr.splice(arr.indexOf(num), 1);
p.remove();
console.log('now have ' + JSON.stringify(arr));
});
});
<div id="demo">
</div>
I'd recommend avoiding getElementsByClassName. The getElementsBy* methods return HTMLCollections, which can be difficult to work with. Consider using querySelectorAll instead, which returns a static NodeList - unlike an HTMLCollection, it can be iterated over directly, it won't change while it's being iterated over, and it's much more flexible.
Here is what you could do.
var arr = [10, 20, 30];
var demo = document.querySelector('#demo');
for (var i = 0; i < arr.length; i++) {
demo.innerHTML += `<p class="tag">${arr[i]} </p>`;
}
demo.addEventListener('click', function(event) {
event.target.remove();
});
<div id='demo'>
</div>
Modifying you code, I have came up with this:
var arr = [10, 20, 30];
var demo = document.getElementById('demo');
for (var i = 0; i < arr.length; i++) {
demo.innerHTML += `<p class="tag">${arr[i]} </p>`;
}
var pTag = document.getElementsByClassName("tag");
for (var j = 0; j < pTag.length; j++) {
pTag[j].onclick = function() {
console.log(this.innerText);
this.remove();
var num = parseInt(this.innerText);
arr.splice(arr.indexOf(num), 1);
console.log(arr)
}
}
<div id="demo">
</div>

Pure JavaScript alternative to jQuery's .not()

What would be the JS alternative to .not() from jQuery? I have $(".form :input").not, but need to transfer that to pure JS. Is there a guide that could help me?
var input = $(".form :input").not('button, [type="button"], [type="submit"]').on({
input: return
});
I'm looking to do that in JS
Modern browser do support a NOT clause in querySelectorAll():
document.querySelectorAll(".form :input:not(...)");
Example (jsFiddle):
<div>This should be colored!</div>
<div>This should be colored!</div>
<div id="this-not">This must not colored!</div>
<div>This should be colored!</div>
<div>This should be colored!</div>
var matchedElements = document.querySelectorAll("div:not(#this-not)");
for (var i=0; i<matchedElements.length; i++) {
matchedElements.item(i).style.backgroundColor = "red";
}
The equivalent in plain JS would be something like this
var forms = document.querySelectorAll('.form'),
inputs = [];
for (var i = forms.length; i--;) {
var els = forms[i].querySelectorAll('input, textarea, select');
for (var j = els.length; j--;) {
if (els[j].type != 'button' && els[j].type != 'submit') {
inputs.push(els[j]);
els[j].addEventListener('input', cback, false);
}
}
}
function cback(event) {
var b = [];
for (var i = inputs.length; i--;) {
if (!inputs[i].value.length) b.push(inputs[i]);
}
var l1 = b.length;
var l2 = inputs.length;
var top = document.querySelectorAll('.top');
for (var j = top.length; j--;) {
top[j].style.width = 100 - (l1 / l2) * 100 + "%";
}
}
FIDDLE
You could also use .filter() to exclude items in your array. You would use it like so (example from MDN):
function isBigEnough(element) {
return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]
It's supported in all modern browsers and IE9+. See Array.prototype.filter() on MDN for more information.
Unfortunately .filter() only works on Arrays so we have to do some extra manipulating to filter a NodeList.
HTML:
<ul>
<li>1</li>
<li>2</li>
<li class="not-me">3</li>
<li>4</li>
<li>5</li>
</ul>
Javascript:
var filter = Array.prototype.filter;
var excludeByClassName = function(className) {
return function (element) {
return element.className != className;
};
};
var LIs = document.getElementsByTagName('li');
// [li, li, li.not-me, li, li]
var filteredLIs = filter.call(LIs, excludeByClassName('not-me'));
// [li, li, li, li]
See this jsFiddle for a working example.

How to sort <ul><li>'s based on class with javascript?

I have a TODO list app with an Unordered list. Within it I have a few list items. The li classes are high,medium,low. I would like li's with the class high to be placed before li's with the class medium and last ones with low.
<ul id="tasks">
<li id="item3" class="priority low"><span></span><span>This is a low priority task</span></li>
<li id="item4" class="priority high"><></span><span>This is a high priority task</span></li>
<li id="item5" class="priority low"><span></span><span>This is another Low</span></li>
<li id="item7" class="priority medium"><span></span><span>And now a Medium</span></li>
</ul>
So the li with id of item4 should be first and then it should be item7 and then the li's with class low after.
Here's a pure JS version of #ŠimeVidas jQuery solution.
var tasks = document.querySelector('#tasks'),
items = document.querySelectorAll('#tasks > li');
for (var i = 0, arr = ['high', 'medium', 'low']; i < arr.length; i++) {
for (var j = 0; j < items.length; j++) {
if (~(" " + items[j].className + " ").indexOf(" " + arr[i] + " "))
tasks.appendChild(items[j]);
}
}
Assuming you can use jQuery, and assuming your list is not very big, and assuming you've only got these three fixed types with no plans on changing this, I'd probably just dump the whole set into memory, clear out the list, then put them back in the list in order. Something like:
jQuery(document).ready(function() {
var i;
var items = jQuery("#tasks li");
var lowItems = [];
var medItems = [];
var highItems = [];
for (i = 0; i < items.length; ++i) {
var jqItem = jQuery(items[i]);
if (jqItem.hasClass("low")) lowItems.push(jqItem);
if (jqItem.hasClass("medium")) medItems.push(jqItem);
if (jqItem.hasClass("high")) highItems.push(jqItem);
}
var tasks = jQuery("#tasks");
tasks.html("");
for (i = 0; i < highItems.length; ++i) {
tasks.append(highItems[i]);
}
for (i = 0; i < medItems.length; ++i) {
tasks.append(medItems[i]);
}
for (i = 0; i < lowItems.length; ++i) {
tasks.append(lowItems[i]);
}
});
Try this:
$(function(){
var sorter = [],
tasks = $('#tasks');
$('li.priority').each(function(){
var $this = $(this),
priority = $this.hasClass('high') ? 3 : ($this.hasClass('medium') ? 2 : 1);
sorter.push({
el : this,
priority : priority
});
}).detach();
sorter.sort(function(a, b){
return a.priority - b.priority;
});
$.each(sorter, function(){
tasks.append(this.el);
});
});
With no jquery:
<ul id="tasks">
<li id="item3" class="priority low"><span></span><span>This is a low priority task</span></li>
<li id="item4" class="priority high"><></span><span>This is a high priority task</span></li>
<li id="item5" class="priority low"><span></span><span>This is another Low</span></li>
<li id="item7" class="priority medium"><span></span><span>And now a Medium</span></li>
</ul>
<script type="text/javascript">
var tasks = document.getElementById("tasks");
var liElements = tasks.getElementsByTagName("li");
var lowPriority = [];
var mediumPriority = [];
var highPriority = [];
var removal = [];
for (var i = 0, len = liElements.length; i < len; i++) {
if (liElements[i].getAttribute("class").indexOf("low") > -1) lowPriority.push(liElements[i].cloneNode(true));
if (liElements[i].getAttribute("class").indexOf("medium") > -1) mediumPriority.push(liElements[i].cloneNode(true));
if (liElements[i].getAttribute("class").indexOf("high") > -1) highPriority.push(liElements[i].cloneNode(true));
removal.push(liElements[i]);
}
for (var i = 0, len = removal.length; i < len; i++ ) {
var liItem = removal[i];
liItem.parentNode.removeChild(liItem);
}
for( var i = 0, len = lowPriority.length; i < len; i++){
tasks.appendChild(lowPriority[i]);
}
for (var i = 0, len = mediumPriority.length; i < len; i++) {
tasks.appendChild(mediumPriority[i]);
}
for (var i = 0, len = highPriority.length; i < len; i++) {
tasks.appendChild(highPriority[i]);
}
</script>
Here's another jQuery–less option:
// Just a helper
function toArray(obj) {
var result = [];
for (var i=0, iLen=obj.length; i<iLen; i++) {
result[i] = obj[i];
}
return result;
}
// Uses querySelectorAll, but could use getElementsByTagName instead
function sortByPriority(id) {
var nodes;
var el = document.getElementById(id);
if (el) {
nodes = toArray(el.querySelectorAll('li.priority'));
nodes.sort(function(a, b) {
function getIndex(el) {
return el.className.indexOf('low') != -1? 1 :
el.className.indexOf('medium') != -1? 2 :
el.className.indexOf('high') != -1? 3 :
0; // default
}
return getIndex(b) - getIndex(a);
});
for (var i=0, iLen=nodes.length; i<iLen; i++) {
el.appendChild(nodes[i]);
}
}
}
It uses a few more lines that a jQuery (or perhaps any library) based solution but you don't have to load several thousand lines of library either.
Also, this runs about 5 times faster in Firefox and IE 9 and 10 times faster in Chrome than a jQuery solution (see http://jsperf.com/sortelementlist).
With pure JavaScript, and simple code!
var tasks = document.getElementById("tasks");
var lis = tasks.getElementsByTagName("li");
var lisarr = Array.prototype.slice.call(lis);
var priority = function(e){
var prio = {low: 0, medium: 1, high: 2};
return prio[e.getAttribute("class").match(/low|high|medium/)[0]];
};
lisarr.sort(function(a,b){
var ap = priority(a), bp = priority(b);
return bp - ap;
});
tasks.innerHTML = lisarr.reduce(function(prev, current){
return prev + current.outerHTML;
}, '');

Javascript Navigation Output (Minified)

I have a page where I output the following
This is the html in my template
<ul id="process"><span>Process: </span>
This is my minified Javascript (functions.js)
$(window).load(function () {
var a = $("#slider").children("img");
currentIndex = 0;
slideCount = a.length;
fadeDuration = 1E3;
navigation = $("#process");
navCnt = 0;
navNumFix = 1;
for (i = 0; i < slideCount; i++) navigation.append('<li>' + navNumFix+++"</li> ");
$(".navItem").click(function () {
var b = $(this).attr("data"),
c = currentIndex;
currentIndex = b;
c != currentIndex && ($(a[c]).fadeOut(fadeDuration), $(a[currentIndex]).fadeIn(fadeDuration))
})
});
For my slider this all outputs the ul tag and attaches the list item via Javascript as follows:
<ul id="process"><span>Process: </span>
<li>1</li>
<li>2</li>
</ul>
Instead of outputting the ul tag in my template, how can I output the ul tag only when the script calls for it? Kind of like the list item above.
This should do... putting your template in display none at first, then showing it after adding your list elements :
<ul style="display:none" id="process"><span>Process: </span></ul>
Javascript :
$(window).load(function () {
var a = $("#slider").children("img");
currentIndex = 0;
slideCount = a.length;
fadeDuration = 1E3;
navigation = $("#process");
navCnt = 0;
navNumFix = 1;
for (i = 0; i < slideCount; i++) navigation.append('<li>' + navNumFix+++"</li> ");
$(".navItem").click(function () {
var b = $(this).attr("data"),
c = currentIndex;
currentIndex = b;
c != currentIndex && ($(a[c]).fadeOut(fadeDuration), $(a[currentIndex]).fadeIn(fadeDuration))
})
// Display the ul if there are slides
if(slideCount > 0)
navigation.show();
});
EDIT : Added the condition to show the ul only when there are slides

Sorting a list alphabetically with a modulus

I don't have any trouble grabbing a list of elements and sorting them alphabetically, but I'm having difficulty understanding how to do it with a modulus.
### UPDATE ###
Here's the code working 'my way', however, I like the re-usability of the answer provided below more, so have accepted that answer.
<script type="text/javascript">
$(document).ready( function() {
$('.sectionList2').each( function() {
var oldList = $('li a', this),
columns = 4,
newList = [];
for( var start = 0; start < columns; start++){
for( var i = start; i < oldList.length; i += columns){
newList.push('<li>' + $(oldList[i]).text() + '</li>');
}
}
$(this).html(newList.join(''));
});
});
</script>
For example. Say I have the following unordered list:
<ul>
<li>Boots</li>
<li>Eyewear</li>
<li>Gloves</li>
<li>Heated Gear</li>
<li>Helmet Accessories</li>
<li>Helmets</li>
<li>Jackets</li>
<li>Mechanic's Wear</li>
<li>Pants</li>
<li>Protection</li>
<li>Rainwear</li>
<li>Random Apparel</li>
<li>Riding Suits</li>
<li>Riding Underwear</li>
<li>Socks</li>
<li>Vests</li>
</ul>
I have this list set to display in 4 columns with each li floated right. Visually this makes finding items in larger lists difficult. The output I need is this:
<ul>
<li>Boots</li>
<li>Helmet Accessories</li>
<li>Pants</li>
<li>Riding Suits</li>
<li>Eyewear</li>
<li>Helmets</li>
<li>Protection</li>
<li>Riding Underwear</li>
<li>Gloves</li>
<li>Jackets</li>
<li>Rainwear</li>
<li>Socks</li>
<li>Heated Gear</li>
<li>Mechanic's Wear</li>
<li>Random Apparel</li>
<li>Vests</li>
</ul>
What I'm looking for is a function that I can pass my array of list items and get my array returned, sorted alphabetically, with a modulus of choice; in this case 4.
Any help would be appreciated as I can find no documentation on the subject.
Alphabetize your list. This is already done, in your case, but if not:
function alphabetizeElements(a, b)
{
var aText = $(a).text();
var bText = $(b).text();
return aText > bText ? 1 : aText < bText ? -1 : 0;
}
var alphabetizedList = $("#myList li").sort(alphabetizeElements);
Store the alphabetized index of each element:
$.each(alphabetizedList, function(i)
{
$(this).data("alphaIndex", i);
});
Sort the alphabetized list by modulus first, then index:
function listColumnSortFn(columns)
{
return function(a, b)
{
var aIndex = $(a).data("alphaIndex");
var bIndex = $(b).data("alphaIndex");
return ((aIndex % columns) - (bIndex % columns)) || (aIndex - bIndex);
}
}
var columnSortedList = alphabetizedList.sort(listColumnSortFn(4));
Replace the list elements with your sorted elements:
$("#myList li").remove();
$("#myList").append(columnSortedList);
Here is the whole thing, all together:
function sortList(columns)
{
var alphabetizedList = $("#myList li").sort(alphabetizeElements);
$.each(alphabetizedList, function(i)
{
$(this).data("alphaIndex", i);
});
var columnSortedList = alphabetizedList.sort(listColumnSortFn(columns));
$("#myList li").remove();
$("#myList").append(columnSortedList);
}
function alphabetizeElements(a, b)
{
var aText = $(a).text();
var bText = $(b).text();
return aText > bText ? 1 : aText < bText ? -1 : 0;
}
function listColumnSortFn(columns)
{
return function(a, b)
{
var aIndex = $(a).data("alphaIndex");
var bIndex = $(b).data("alphaIndex");
return ((aIndex % columns) - (bIndex % columns)) || (aIndex - bIndex);
}
}
$(function()
{
sortList(4);
});
var columnify = function (a,n) {
var result = [];
for (var i = 0, lastIndex = a.length - 1; i < lastIndex; i++)
result.push(a[i * n % (lastIndex)]);
result[lastIndex] = a[lastIndex];
return result;
}
var products = ["Boots",
"Eyewear",
"Gloves",
"Heated Gear",
"Helmet Accessories",
"Helmets",
"Jackets",
"Mechanic's Wear",
"Pants",
"Protection",
"Rainwear",
"Random Apparel",
"Riding Suits",
"Riding Underwear",
"Socks",
"Vests",]
columnify(products, 4)
["Boots", "Helmet Accessories", "Pants", "Riding Suits", "Eyewear", "Helmets", "Protection", "Riding Underwear", "Gloves", "Jackets", "Rainwear", "Socks", "Heated Gear", "Mechanic's Wear", "Random Apparel", "Vests"]
Apply that function to the already sorted list, and then it will return a list of strings in the order (almost) that you want. Then add the list that was returned in order to the unordered list in the DOM.
Also, I haven't tested it with anything besides that list. So I'd do that if I were you. From what I see, it only works if the length of the list is a multiple of n. Not that great of a solution but it's late for me and I can't be bothered to come up with anything better.
EDIT: fixed the issue with the last element
See if this will work: http://jsfiddle.net/6xm9m/2
var newList = new Array();
var listItem = $('#list > li');
var mod = 4;
var colCount = Math.ceil(listItem.length / mod);
listItem.each(function(index) {
var newIndex = ((index % colCount) * mod) + Math.floor(index / colCount);
// $(this).text(newIndex);
newList[newIndex] = this;
});
$('#list').empty();
for(var i = 0; i < newList.length; i++){
$('#list').append(newList[i]);
}
Needs improvements, probably, but I'm not really sure how well this works at all.
Here you go. The code is surprisingly simple once you figure it out. I realize you are using jQuery but I'm not familiar enough with it to use its features. This is simple enough that maybe it's not necessary.
function pivotArray(arr, columns) {
var l = arr.length, out = [], ind = 0, i = 0;
for (; i < l; i += 1) {
out[ind] = arr[i];
ind += columns;
if (ind >= l) {
ind = ind % columns + 1;
}
}
return out;
}
And here's the test to prove it works (tested in Firefox 3.6.9, IE 6, Chrome 1.0.154.36):
<html>
<head>
<style type="text/css">
a.panelnum {
display:block;
float:left;
width:40px;
height:40px;
border:1px solid black;
text-align:center;
vertical-align:middle;
text-decoration:none;
font-size:2em;
}
</style>
</head>
<body onload="doit(17, 4);">
<div id="output" style="border:1px solid blue;">
</div>
<script type="text/javascript">
function pivotArray(arr, columns) {
var l = arr.length, out = [], ind = 0, i = 0;
for (; i < l; i += 1) {
out[ind] = arr[i];
ind += columns;
if (ind >= l) {
ind = ind % columns + 1;
}
}
return out;
}
function doit(size, columns) {
document.getElementById('output').innerHTML = 'starting';
var l = size;
var inp = [];
for (var i = 0; i < l; i += 1) {
inp[i] = i;
}
var result = pivotArray(inp, columns);
var str = '';
for (i = 0; i < l; i += 1) {
str += '<a class="panelnum">' + result[i] + '</a>';
}
var d = document.getElementById('output')
d.innerHTML = '<p>Some pre text</p>' + str + '<p style="clear:both;">and some post text</p>';
d.style.width = (columns * d.childNodes[1].offsetWidth + 2) + 'px';
}
</script>
</body>
</html>
One more thing: it might be useful to just move the elements around in-place. I almost had script for it but my script was running backwards (as if floats went from top to bottom first). If I get time I'll work on it and post the code.
P.S. Anyone want to give me pointers on why I had to add 2 to the width calculation for IE6? Wait... it's the borders of the div isn't it?

Categories

Resources