Randomize the layout of a set of existing images with JavaScript - javascript

I have a grid of images (baseball and football teams), with names associated with them (like captions).
What I'm trying to do is randomize the placement of the images with the names so they appear at different spots of the grid.
I can get the images and names to display (the names come from a textarea and are stored into a names array) just fine, but as soon as I hit the random button, the img.src disappears, leaving me with blank spots.
The names still randomize though and work fine.
This is my code so far:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sports</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="container-fluid">
<h1>Sports Random</h1>
<br>
<div class="container">
<div class="row" id="parent">
</div>
<div class="row">
</div>
<textarea id="textarea"></textarea>
<div id="buttonGroup">
<button id="random">Random</button>
<button id="baseball">Baseball</button>
<button id="football">Football</button>
<button id="reset">Reset</button>
</div>
</div>
<br>
</div>
<script src="js/main.js"></script>
</body>
</html>
JavaScript:
bGroup.addEventListener("click", function images(e) {
tDisplay.innerHTML = "";
for (var i = 1; i < names.length; i++) {
var newDiv = document.createElement("div");
var newImg = document.createElement("img");
var userName = document.createElement("p");
newDiv.className = "col-sm-3 col-md-3 col-lg-2"
newDiv.appendChild(newImg);
newDiv.appendChild(userName);
userName.textContent = names[Math.random() * i | 0];
if (e.target.id === "baseball") {
newImg.src = "images\\baseball\\team" + i + ".jpg";
} else if (e.target.id === "football") {
newImg.src = "images\\football\\team" + i + ".gif";
}
tDisplay.appendChild(newDiv);
};
});
// random the images
random.addEventListener("click", function random() {
for (var i = 0; i < tDisplay.children.length; i++) {
tDisplay.appendChild(tDisplay.children[Math.random() * i | 0]);
tDisplay.getElementsByTagName("p")[Math.random() * i | 0].textContent =
names[Math.random() * i | 0];
}
});
Right now the buttons on my page are in a div group (bGroup) and then I delegate events depending on which button is clicked.
I had this working when I had separate functions for "baseball" and "football" images; just trying to reduce code.
The random button is included in the button group but I kept it separate just for the sake of organization; kind of wondering if that is good practice or not?
If possible I would like any answers strictly in JavaScript.

Interference
The cause of the problem is that your Random button has its own "click" event listener whilst being a child of bGroup which also has a "click" event listener.
The exact process that results in empty clones is kind of irrelevant, but could be examined (if interested) by use console.log().
When you click Random, you trigger both the randomization of the contents of tDisplay and the initialization/creation of <div><img><p></p></div> children.
You have two options:
Either stopPropagation() of the "click" event in the unique listener attached to Random.
Or include the randomization functionality in the function triggered by a "click" on bGroup.
I have built a simplified version of your code below, using the second option stated above.
Since the randomization code Math.random() * i | 0 is used many times, I've created a function to return the random result of any number parsed through it.
The const declarations at the top of the JS are to store values that will never change and that are repeatedly used throughout the script.
The const declaration creates a read-only reference to a value. It does not mean the value it holds is immutable, just that the variable identifier cannot be reassigned. For instance, in the case where the content is an object, this means the object's contents (e.g., its parameters) can be altered.
I've used let instead of var where suitable, since it's less leaky.
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
I chose to use forEach() instead of a for loop to iterate through names, but retained the for loop to handle the randization.
Each has its own merits.
The arrow function called by forEach() uses two arguments; n and i, where n is the name on each loop, and i is the index of the name (a loop counter).
I also chose to use querySelectorAll() instead of getElementsByTagName() for brevity.
Those changes have benefits, but the only significant change is in moving the "click" handling for Random into the handler for clicks on bGroup.
Be aware that the randomization may not change the display every time (the nature of random), but keep clicking and you'll see that it does in fact work; a longer names Array (and thus more images) would likely randomize more noticeably.
const tDisplay = document.querySelector( ".container" ),
bGroup = document.querySelector( "#buttonGroup" ),
names = [ "foo", "bar", "baz", "qux" ];
function randIndex( i ) {
return ( Math.random() * i | 0 );
}
bGroup.addEventListener( "click", function( evt ) {
let trg = evt.target.id;
if ( trg !== "random" ) {
tDisplay.innerHTML = "";
names.forEach( ( n, i ) => {
let newDiv = document.createElement( "div" ),
newImg = document.createElement( "img" ),
userName = document.createElement( "p" );
//newDiv.className = "col-sm-3 col-md-3 col-lg-2";
newDiv.appendChild( newImg );
newDiv.appendChild( userName );
userName.textContent = names[ randIndex( i ) ];
if ( trg === "sports" ) {
newImg.src = "https://lorempixel.com/100/70/sports/" + i;
} else if ( trg === "cats") {
newImg.src = "https://lorempixel.com/100/70/cats/" + i;
}
tDisplay.appendChild( newDiv );
} );
} else {
for ( let i = 0; i < tDisplay.children.length; i++ ) {
tDisplay.appendChild( tDisplay.children[ randIndex( i ) ] );
tDisplay.querySelectorAll( "p" )[ randIndex( i ) ].textContent =
names[ randIndex( i ) ];
}
}
});
body {
font-family: sans-serif;
}
h1, p {
margin: .5em 0;
}
#buttonGroup {
margin-bottom: 1em;
}
.container div {
display: inline-block;
margin-right: 1em;
}
<h1>Images Random</h1>
<div id="buttonGroup">
<button id="random">Random</button>
<button id="sports">Sports</button>
<button id="cats">Cats</button>
</div>
<div class="container"></div>

Related

Why is my method running twice and resetting the object value I'm trying to change?

I declare an Object called gameRooms, which contains Objects representing the rooms and their properties. Then I declare gameState and include a property for 'currentRoom.' When the game starts and I set the 'currentRoom' variable to 'Graveyard' via the ChangeRoom() method, it works just fine.
However, when I click a button that executes the function declared in a room's func property, then function writes:
"You Enter The Main Hall.
You Enter The Graveyard."
...to the window. When I check the console, I'm still in the Graveyard. Why?
JSfiddle here: https://jsfiddle.net/SirenKing/fz8pg05L/2/
gameRooms = {
Graveyard: {
description: 'The graveyard is small, enclosed by tall walls. The tombstones are weathered and worn. The names are hard to make out. An abandoned shed sits in one corner. A closed door leads into the castle.',
name: "Graveyard",
navButtons: [
{
name: "Go Inside",
state: "locked",
func: function () {
ChangeRoom( gameRooms.MainHall );
},
id: "tomainhall",
},
(etc etc etc, more rooms and buttons...)
],
},
}
gameState = {
State: {
currentRoom: null,
actionIDs: [ "wakeup" ],
navigationIDs: [],
inventoryIDs: [],
globalMenuIDs: [],
health: 100,
}
};
function ChangeRoom( room ) {
gameState.State.currentRoom = room;
Say( "You enter the " + room.name );
UpdateNavigation( );
UpdateActions( );
return gameState.State.currentRoom;
};
function Say(content) {
let comment = content;
let newParagraph = document.createElement('p');
newParagraph.textContent = comment;
let newestParagraph = document.getElementById("narrative").appendChild(newParagraph);
document.getElementById("narrative").id = "old";
newestParagraph.id = "narrative";
scrollTo(0,document.body.scrollHeight);
};
The HTML, and the button-constructing method, are here:
<html>
<body>
<div style="width:40%;" id="narrative"></div>
<div id="nav" style="top:200px; right:100px; position:fixed;">
<label>Navigate</label>
</div>
</body>
</html>
function UpdateNavigation( ) {
let oldNavigation = gameState.State.navigationIDs;
console.log(oldNavigation);
console.log("removing old nav buttons by id");
for (i = 0; i < oldNavigation.length; i++) {
// for ids in gameState.State.navigationIDs, remove the button elements by those ids
let elem = document.getElementById( gameState.State.navigationIDs[i] );
elem.remove();
}
gameState.State.navigationIDs = [];
let navs = GetCurrentRoom().navButtons;
let roomName = GetCurrentRoom().name;
console.log( roomName, "'s navButtons are ", navs);
console.log("building new list of nav button ids");
for (i = 0; i < navs.length; i++) {
gameState.State.navigationIDs.push( navs[i].id );
};
let currentNavigation = gameState.State.navigationIDs;
console.log(currentNavigation);
console.log("building nav buttons from list of ids");
for (i = 0; i < currentNavigation.length; i++) {
// for objects in room.navButtons, create new action buttons with ids of the rooms you can enter
// add innerHTML and onclick and type to the button, then append it into the navsDiv
elem = document.createElement("button");
elem.innerHTML = navs[i].name ;
elem.type = "button";
elem.id = currentNavigation[i];
elem.onclick = GetCurrentRoom().navButtons[i].func;
let navsDiv = document.getElementById( "nav" );
navsDiv.lastElementChild.appendChild(elem);
};
};
There are a few problems with your code; Say() does nothing with its second parameter, and should either do so, or concatenate the parameters sent to it: function Say(one, two) { return one + two; } or Say(one + two);... But mainly, the largest problem is that you append your buttons to the label element.
If this element were anything but a label, your code would work without issue. This is because the label element propagates click or touch events to the element(s) within it, so every time you click either of the buttons in that label element, the element clicks both buttons at once.
Thus, the simple fix is to remove the code that appends the buttons to the label:
navsDiv.lastElementChild.appendChild(elem);
Should be changed to:
navsDiv.appendChild(elem);
This of course goes the same for any code that appends buttons to the label elements. Also relevant is your UpdateActions() function, where you append content to the last child... A label.
Fixed fiddle

JS function that outputs a DOM-tree by button click

I need to write a js function that outputs a DOM-tree by button click. The tree should be output as an unnumbered list (ul) with attachments and it's needed to use the name of the element, i.e. head, body, p, div, etc., and the element id as the text output in the list item (of course if it is specified). I've tried to write it but I don't know how to make it work and what's wrong here
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body><div id="container1" style="background-color: cyan;">
<h1 id="header1">Header</h1>
<p id="paragraph1">Paragraph</p>
<div id="container2" style="background-color: red;">
</div>
</div>
<ul id="tree"></ul>
<input type="text" id="formText">
<br>
<button id= "confirmButton" style="margin-top: 5px;">Build a DOM tree</button>
</body>
</html>
function DOM_Tree(e) {
for (let i = 0; i < document.body.childNodes.length - 1; i++) {
if (document.body.childNodes[i].id != 'tree') {
let ul = document.getElementById('tree');
let li = document.createElement('li');
let el = document.body.childNodes[i];
let ul1 = document.createElement('ul');
if (el.hasChildNodes()) {
li.innerText = document.body.childNodes[i].id;
ul.append(li);
for (let j = 0; j < el.childNodes.length; j++) {
if (el.childNodes[j].id != undefined) {
let li1 = document.createElement('li');
li1.innerText = el.childNodes[j].id;
ul1.append(li1);
}
let li1 = document.createElement('li');
li1.innerText = el.childNodes[j].id;
ul1.append(li1);
}
ul.append(ul1);
}
else {
if (document.body.childNodes[i].id != undefined) {
li.innerText = document.body.childNodes[i].id;
ul.append(li);
}
}
}
}
}
confirmButton.onclick = function() {
DOM_Tree(document.body);
alert('click');
}
Your code had a number of logical flaws.
Here's a working solution:
function DOM_Tree(e, ul = document.getElementById('tree')) {
for (let i = 0; i < e.childNodes.length - 1; i++) {
if (e.childNodes[i].id != 'tree') {
let li = document.createElement('li');
let el = e.childNodes[i];
if (e.childNodes[i].id != undefined) {
li.innerText = e.childNodes[i].nodeName + ' ' + e.childNodes[i].id;
ul.append(li);
}
let ul1 = document.createElement('ul');
DOM_Tree(e.childNodes[i], ul1);
ul.append(ul1);
}
}
}
confirmButton.onclick = function() {
DOM_Tree(document.body);
// alert('click');
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body><div id="container1" style="background-color: cyan;">
<h1 id="header1">Header</h1>
<p id="paragraph1">Paragraph</p>
<div id="container2" style="background-color: red;">
</div>
</div>
<ul id="tree"></ul>
<input type="text" id="formText">
<br>
<button id= "confirmButton" style="margin-top: 5px;">Build a DOM tree</button>
</body>
</html>
And here's all the changes I did
A high level explanation of what your code was doing:
DOM_Tree got called with document.body as a parameter (but you didn't actually use that parameter anywhere)
You iterate over all of the nodes in document.body
If the node contains children, then you put that node's info into the DOM, then start iterating over the node's children. If it does not contain children, then you just add it's info to the DOM
You pretty much do the exact same thing that you did in step 3, but for the grandchildren.
Hopefully this high level explanation helps show why it wasn't working. If a human was following those instructions, they wouldn't end up with the desired results either.
What I did was
eliminate step 4 (the inner loop) - it's the exact same logic as step 3, and only needs to be done once
I cleaned it up just a tad. In step 3, weather or not there's children, you're going to add that node's info to the DOM. So, I just move that outside the if, which eliminated the need for an else, and actually the if wasn't needed either, because if there were no children, then the loop wouldn't happen.
I stopped hard coding in the usage of document.body. Instead, I referred to the "e" parameter you were passing in, which was initially set to document.body anyways.
This allowed me to recursively call this function. Once I got to the point where I needed to process the children of document.body, I just called DOM_Tree again, passing in each child. Each child in turn would call DOM_Tree for each of its children. Thus, the whole DOM would get looked at, not just the first two levels like you had in your implementation.
I made it take in an additional parameter - the node where we're currently inserting the information at. This is important so that the recursive DOM_Tree calls can insert the information it finds into the right spot in the DOM.
Are you getting the error:
Uncaught TypeError: document.body.getElementById is not a function
at DOM_Tree (<anonymous>:4:32)
at HTMLButtonElement.confirmButton.onclick (<anonymous>:26:7)
DOM_Tree # VM167:4
confirmButton.onclick # VM167:26
You can check this error in the console output in your browser (in Chrome, press F12, or right click and 'inspect', you'll see the console output, with useful errors or debugging console logs:
You need document.getElementById not document.body.getElementById.
You're also adding two click events. Your first onclick="DOM_Tree(e)" I suspect didn't work because you meant onclick="DOM_Tree(event)" or onclick="DOM_Tree(this)". You might not even want the event (you don't really want to loop through the children of the button!) in which case onclick="DOM_Tree()" would work.

Restructure a Multi-Level Content Presentation System

I am new to javascript, and I'm developing educational materials for my students at our school in India. I have created this structure that allows for cycling through lists of words with prev/next buttons, changing categories, and the ability to click on any given word to hear it spoken via text-to-speech.
It works and it's the most complicated thing I've been able to do, but I know it has to be horribly inefficient and outdated in the way it's constructed. I'm trying to learn more about event listeners, and how to simplify structures in general. I've spent the last five hours trying to change this structure using what I've learned, but the prev/next nav part and the fact that my items are in an array makes it beyond my scope.
Hopefully it's not out of line to ask for general input about how to reconfigure this to be more economical. I have put a lot of effort into getting this far with my woeful skillset. Many thanks for any assistance. If snippet isn't working page can be seen [here].1
var levelone = [
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Apple</div>",
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Banana</div>",
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Grapes</div>",
];
var leveltwo = [
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Dog</div>",
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Cat</div>",
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Horse</div>",
];
var levelthree = [
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Red</div>",
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Yellow</div>",
"<div onclick=\"jQuery(this).articulate('rate',1).articulate('setVoice','language','hi').articulate('speak')\" data-articulate-append=\"\">Blue</div>",
];
var box = document.getElementById('box');
var i = -1;
function next() {
i = i >= start.length - 1 ? 0 : i + 1;
box.innerHTML = start[i];
}
function prev() {
i = i > 0 ? i - 1 : sav.length - 1;
box.innerHTML = start[i];
}
$("#nextBtn").click(function() {
var nextDiv = $(".step:visible").next(".step");
if (nextDiv.length == 0) { // wrap around to beginning
nextDiv = $(".step:first");
}
$(".step").hide();
nextDiv.show();
});
$("#prevBtn").click(function() {
var prevDiv = $(".step:visible").prev(".step");
if (prevDiv.length == 0) { // wrap around to end
prevDiv = $(".step:last");
}
$(".step").hide();
prevDiv.show();
});
function lone() {
start = levelone.slice(0);
box.innerHTML = levelone[0];
}
function ltwo() {
start = leveltwo.slice(0);
box.innerHTML = leveltwo[0];
}
function lthree() {
start = levelthree.slice(0);
box.innerHTML = levelthree[0];
}
body {
padding: 20px;
font-size: 20px;
line-height: 1.5em
}
div {
cursor: pointer
}
<meta http-equiv="Content-Language" content="hi">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="http://clients.brettcolephotography.com/test/jquery-3.1.1.min.js"></script>
<script src="http://clients.brettcolephotography.com/test/articulate.min.js"></script>
<div id="box">Pick a category. Then click on each word to hear it spoken.</div>
<div>
<button type="button" onclick="prev()">prev</button>
<button type="button" onclick="next()">next</button>
</div>
<div>
<div onclick="lone()">Fruits</div>
<div onclick="ltwo()">Animals</div>
<div onclick="lthree()">Colors</div>
</div>
I can suggest a design model:
Have a array , something like const items=[];
Have another array something like const categories=["fruit","animal"]
When your page loads fetch the top category from above the array, ex: fruits, then have a render function which will push data into items based upon the category. After the push call the render function again which loads the list item based upon data avilable in the items array.
attach eventhandler to the new list items rendered through above code.

Anchor Tag href functions

I am trying to create a function that finds the id of an element that an anchor tag directs to, and attach a class to that element. I need this to be vanilla js not jquery.
For Example:
<div id="firstDiv"></div>
<div> Destination </div>
<div> Destination #2 </div>
The idea would be that once you click on the anchor tag that it would take you to the div and attach a class to that div to initiate a function.
The script that I've written runs for loops that add the values of the href attribute and the id attribute from javascript objects. See Below:
var rooms = [
{
id: 1,
name: Example,
},
{
id:2,
name: Example #2,
}
]
for ( x in rooms ) {
var i = document.getElementById("firstDiv");
var a = document.createElement("a");
i.appendChild(a);
a.href = "#" + rooms[x].id;
a.innerHTML += rooms[x].name + "<br>";
a.classList.add("show");
}
var rect = document.getElementsByTagName("rect");
for ( i = 0; i < rect.length; i++ ) {
rect[i].setAttribute("id", rooms[i].id);
}
Output:
<div id="firstDiv">
Example
Example #2
</div>
<div id="1"> Destination </div>
<div id="2"> Destination #2 </div>
The function below is an example of what I want the function to do for each corresponding div when an a tag is clicked.
function attach() {
var div = document.getElementById(rooms[i].id);
div.classList.toggle("active");
}
Any advice on how to properly write this function for my particular needs. Would a for loop be best for this or an if/else statement. I've tried both but neither has worked.
Please let me know if I need to clarify more.
It seems like this is what you are asking about. See comments inline.
document.addEventListener("click", function(event){
// First, check to see if it was an anchor that was clicked
// in the document
if(event.target.nodeName === "A"){
// If so, add a class to the target
// Get the href attribute and strip off the "#", then find that element and toggle the class
document.getElementById(event.target.getAttribute("href").replace("#","")).classList.toggle("highlight");
}
});
.highlight { background-color:yellow; }
<div id="firstDiv">
Example
Example #2
</div>
<div id="one"> Destination </div>
<div id="two"> Destination #2 </div>
Try this:
let anchors = document.querySelectorAll("#firstDiv a");
for (let i = 0; i < anchors.length; i++) {
anchors[i].addEventListener("click", () => {
var div = document.getElementById(rooms[i].id);
div.classList.toggle("active");
}
You're almost there you just need to add an onclick property for the anchor tag now you can or cannot use the third attach function.I am including both implementations for your reference.
for ( x in rooms ) {
var i = document.getElementById("firstDiv");
var a = document.createElement("a");
i.appendChild(a);
a.href = "#" + rooms[x].id;
a.innerHTML += rooms[x].name + "<br>";
a.classList.add("show");
a.onClick=`document.getElementById(${rooms[x].id}).classList.toggle("active")`;
}
If you want to have a dedicated function your loop becomes
for ( x in rooms ) {
var i = document.getElementById("firstDiv");
var a = document.createElement("a");
i.appendChild(a);
a.href = "#" + rooms[x].id;
a.innerHTML += rooms[x].name + "<br>";
a.classList.add("show");
a.onClick=`attach(${rooms[x].id})`;
}
Your attach function then becomes:
function attach(id) {
var div = document.getElementById(id);
div.classList.toggle("active");
}
Do let me know if you have any issues.
CSS Only 😎
This is a bit off-topic but you can use the :target selector to set the style of the corresponding id without JS.
/* element with an id matching the URL fragment */
:target {
background: gold;
}
/* you can combine target with other selectors */
#c:target {
background: orange
}
/* just a bit of styling */
a,div {
padding: .5rem;
display: block;
width: 20%;
float:left;
}
Link to A
Link to B
Link to C
Link to D
<div id="a">A</div>
<div id="b">B</div>
<div id="c">C</div>
<div id="d">D</div>

Any way to shuffle content in multiple div elements

I'm relatively new to Javascript and was wondering if there's a quick way to shuffle content that is contained in multiple <div> tags. For example
<div id='d1'>
<span>alpha</span>
<img src='alpha.jpg'>
</div>
<div id='d2'>
<span>beta</span>
<img src='beta.jpg'>
</div>
<div id='d3'>
<span>gamma</span>
<img src='gamma.jpg'>
</div>
<button onclick='shuffle_content();'>Shuffle</button>
After clicking on the button, I'd like the content in d1, d2, d3 to change places (for example maybe d3 would be first, then d1, then d2).
A quick way to kind of move things around is to copy the first div element (d1), then put it at the very end (after d3), and then delete the original d1. But that doesn't really randomize things. It just makes things go in the cycle (which might be ok).
Any suggestions would be appreciated. Thanks.
are you ok with using a javascript library like jQuery? here's a quick jQuery example to accomplish what you're after. the only modification to your HTML is the addition of a container element as suggested:
<div id="shuffle">
<div id='d1'>...</div>
<div id='d2'>...</div>
<div id='d3'>...</div>
</div>
and javascript:
function shuffle(e) { // pass the divs to the function
var replace = $('<div>');
var size = e.size();
while (size >= 1) {
var rand = Math.floor(Math.random() * size);
var temp = e.get(rand); // grab a random div from our set
replace.append(temp); // add the selected div to our new set
e = e.not(temp); // remove our selected div from the main set
size--;
}
$('#shuffle').html(replace.html() ); // update our container div with the
// new, randomized divs
}
shuffle( $('#shuffle div') );
A recent question was just closed as duplicate of this, but I feel I've got a better answer than any here. This method is very direct. There's no mucking with copying HTML, thus preserving changes to the DOM, styles, event handlers, etc.
To shuffle all the children of some parent element, select a random child and append it back to the parent one at a time until all the children have been re-appended.
Using jQuery:
var parent = $("#shuffle");
var divs = parent.children();
while (divs.length) {
parent.append(divs.splice(Math.floor(Math.random() * divs.length), 1)[0]);
}
Demo: http://jsfiddle.net/C6LPY/2
Without jQuery it's similar and just as simple:
var parent = document.getElementById("shuffle");
var divs = parent.children;
var frag = document.createDocumentFragment();
while (divs.length) {
frag.appendChild(divs[Math.floor(Math.random() * divs.length)]);
}
parent.appendChild(frag);
Demo: http://jsfiddle.net/C6LPY/5/
Edit: Here's a break down of the code:
// Create a document fragment to hold the shuffled elements
var frag = document.createDocumentFragment();
// Loop until every element is moved out of the parent and into the document fragment
while (divs.length) {
// select one random child element and move it into the document fragment
frag.appendChild(divs[Math.floor(Math.random() * divs.length)]);
}
// appending the document fragment appends all the elements, in the shuffled order
parent.appendChild(frag);
You can grab the content of each div
c1 = document.getElementById('div1').innerHTML
c2 = document.getElementById('div2').innerHTML
c3 = document.getElementById('div3').innerHTML
Then determine a new order for them randomly .. and then put each content in the new destination
say for instance, the randomness gave:
c1_div = 'div2'
c2_div = 'div1'
c3_div = 'div3'
then you just:
document.getElementById(c1_div).innerHTML = c1
document.getElementById(c2_div).innerHTML = c2
document.getElementById(c3_div).innerHTML = c3
Expanding on the nice answer by #gilly3, using jQuery one can actually avoid appending randomly-chosen elements of divs in a loop, by randomly sorting divinstead and appending them all at once:
$(function() {
var parent = $("#shuffle");
var divs = parent.children();
divs.sort(function(a, b) {
return 0.5 - Math.random();
});
parent.append(divs);
});
Demo: http://jsfiddle.net/ey70Lxhk/
Note however that this technique is not accurate in terms of randomness, and relies on sort which does not scale linearly with the number of elements.
I'd use server side code to accomplish this. I know this isn't really an answer to your question, but it is an alternative implementation.
Best Regards, Frank
I'd wrap the divs in an outer div, then pass its id to shuffle_content().
In there, you could create a new div, cloning the wrapper div's nodes in a random order to fill it, then replace the wrapper div with the new div.
For your HTML, the short answer to your question is:
function shuffle_content() {
var divA = new Array(3);
for(var i=0; i < 3; i++) {
divA[i] = document.getElementById('d'+(i+1));
document.body.removeChild(divA[i]);
}
while (divA.length > 0)
document.body.appendChild(divA.splice(Math.floor(Math.random() * divA.length),1)[0]);
}
To get there I wrote the following, which I think works better:
<html>
<div id="cards">
<div id="card0">Card0</div><div id="card1">Card1</div>
<div id="card2">Card2</div><div id="card3">Card3</div>
<div id="card4">Card4</div><div id="card5">Card5</div>
<div id="card6">Card6</div><div id="card7">Card7</div>
<div id="card8">Card8</div><div id="card9">Card9</div>
</div>
<button id="shuffle">Shuffle</button>
<script language="javascript">
<!--
document.getElementById('shuffle').onclick = function () {
var divCards = document.getElementById('cards');
var divCardsArray = new Array(
document.getElementById('card0'),
document.getElementById('card1'),
document.getElementById('card2'),
document.getElementById('card3'),
document.getElementById('card4'),
document.getElementById('card5'),
document.getElementById('card6'),
document.getElementById('card7'),
document.getElementById('card8'),
document.getElementById('card9')
);
return function() {
var mDivCardsArray=divCardsArray.slice();
while (divCards.childNodes.length > 0) {
divCards.removeChild(divCards.firstChild);
}
while (mDivCardsArray.length > 0) {
var i = Math.floor(Math.random() * mDivCardsArray.length);
divCards.appendChild(mDivCardsArray[i]);
mDivCardsArray.splice(i,1);
}
return false;
}
}()
//-->
</script>
</html>
I was trying to pack down that last while statement to:
while (mDivCardsArray.length > 0) {
divCards.appendChild(
mDivCardsArray.splice(
Math.floor(Math.random() * mDivCardsArray.length)
,1)[0]
);
}
but this is pretty hard to read and prone to error.
Going with jQuery or Prototype you could follow the same basic structure and get the result you're looking for.
Personally, I think it looks even better if you add 2 more divs to the cards stack, expand the divCardsArray, insert the following style block, and add this code right after the divCardsArray definition.
<html>
...
<style>
html,body{height:100%;width:100%;text-align:center;font-family:sans-serif;}
#cards,#cards div{padding:5px;margin:5px auto 5px auto;width:100px;}
</style>
...
<div id="cardA">CardA</div><div id="cardB">CardB</div>
...
var colorCardsArray = new Array(
'#f00', '#f80', '#ff0', '#8f0', '#0f0', '#0f8',
'#0ff', '#08f', '#00f', '#80f', '#f0f', '#f08' );
for(var i=0;i<divCardsArray.length;i++)
divCardsArray[i].style.backgroundColor=colorCardsArray[i];
...
</html>
I would suggest you randomize the content, not the actual Divs themselves. You could accomplish this by putting the content in separate html pages - no header info or body, just the content.
Then use a function on page load to randomly assign which div gets what content and use this to change the DIV's content:
<script type="text/javascript">
function ajaxManager(){
var args = ajaxManager.arguments;
if (document.getElementById) {
var x = (window.ActiveXObject) ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
}
if (x){
switch (args[0]){
case "load_page":
if (x)
{
x.onreadystatechange = function()
{
if (x.readyState == 4 && x.status == 200){
el = document.getElementById(args[2]);
el.innerHTML = x.responseText;
}
}
x.open("GET", args[1], true);
x.send(null);
}
break;
case "random_content":
ajaxManager('load_page', args[1], args[2]); /* args[1] is the content page, args[2] is the id of the div you want to populate with it. */
break;
} //END SWITCH
} //END if(x)
} //END AjaxManager
</script>

Categories

Resources