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>
Related
I am trying to check which div has bigger height and than place a class inside the one that is greater.
I have this code
$(document).ready(function () {
var sideNavMenu = $(".col-md-3").height();
var mainColumn = $(".col-md-9").height();
if (sideNavMenu > mainColumn)
{
$(".col-md-3").addClass('dotRight');
}
else
{
$(".col-md-9").addClass('dotLeft');
}
});
The goal here is to check if sideNavMenu is greater than mainColumn than place dotRight on its div tag.
If the mainColumn is greater, then place dotLeft on its div tag.
But its not working.
Any suggestion how to change/improve it.
Thanks a lot
You should reference these by IDs and not classes, since there can be multiple elements with these class names on the page. There should only be one with each ID.
$(document).ready(function () {
var sideNavMenu = $("#sidebar").height();
var mainColumn = $("#main").height();
if (sideNavMenu > mainColumn) {
$("#sidebar").addClass('dotRight');
} else {
$(".#main").addClass('dotLeft');
}
});
Of course, you need to add the id's to your <div>s respectively.
The jQuery docs say:
Get the current computed height for the first element in the set of matched elements or set the height of every matched element.
But, I was just playing with it in jsfiddle and it seems to return an object containing the height of the first element.
http://jsfiddle.net/wwx2m/2/
Which means you should be able to do:
$(document).ready(function () {
var sideNavMenu = $(".col-md-3").height();
var mainColumn = $(".col-md-9").height();
if (JSON.stringify(sideNavMenu) > JSON.stringify(mainColumn)) {
$(".col-md-3").addClass('dotRight');
} else {
$(".col-md-9").addClass('dotLeft');
}
});
But the first way I said is preferred. This is not stable, since there can be more objects introduced with the same class. The only reason I'm even mentioning it is to explain why you were having problems with your original code. :)
http://jsfiddle.net/wwx2m/4/
I put the jsfiddle together for you
<html>
<div class='col-md-3'>
</div>
<div class='col-md-9'>
</div>
<script>
var sideNavMenu = $(".col-md-3").height();
var mainColumn = $(".col-md-9").height();
if (sideNavMenu > mainColumn){
$(".col-md-3").addClass('dotRight');
}
else{
$(".col-md-9").addClass('dotLeft');
}
jsFiddle
updated jsFiddle with Animation
I have a large HTML source, and I'd like to break it into multiple parts. I've been able to achieve most of this task, but I'm struggling with a single aspect.
When all of the HTML is wrapped in tags, I have no problem; however, if text nodes are mixed with HTML tags, I'm unable to capture all parts.
What am I doing wrong?
Below, is a jsFiddle that shows an example of the problem.
http://jsfiddle.net/acrashik/aDm8L/1/
Here is the code I have written so far to attempt to break up the HTML:
function parseElement(selector,parts, cycle) {
var cc = $(selector),
content = cc.children(),
total = content.length,
maxHeight = cc.height(),
spaceLeft = maxHeight,
cycle = cycle || 0;
function addToPage(elem,elemSize) {
elem.appendTo(parts[cycle]);
spaceLeft -= elemSize;
}
function startNewPage() {
cycle++
parseElement(selector,parts, cycle);
}
$.each(content,function(index,v){
var elem = $(v),
tag = elem[0].tagName.toLowerCase(),
elemSize = elem.outerHeight(true);
if (elemSize <= spaceLeft) {
addToPage(elem,elemSize);
} else if (elemSize > spaceLeft) {
startNewPage();
}
});
}
Question
How can I parse all the HTML text, even unwrapped text nodes, preserving structure and order?
Update
Thanks for help, case solved, only possible way to measure text node height is to wrap it, here is code how to achieve that:
$(selector).contents().filter(function(){
return this.nodeType === 3
}).wrap('<span />');
and here is fully working example:
http://jsfiddle.net/acrashik/aDm8L/20/
thanks everyone.
The problem you are facing comes from using jQuery.children():
Note also that like most jQuery methods, .children() does not return
text nodes;
Instead, you should use the native DOM property, childNodes which does contain text nodes:
cc[0].childNodes;
Or, better yet, pass in the reference to the DOM node:
function parseElement(element, parts, cycle) {
var cc = $(element),
content = element.childNodes,
total = content.length,
maxHeight = cc.height(),
spaceLeft = maxHeight,
cycle = cycle || 0;
//Your other functions down here...
}
And call like:
parseElement(document.getElementById('source2'), ['#part3','#part4']);
Notice You'll need to revisit your other methods to accommodate these changes.
You can use css to split into several columns. I don't know your targeted browsers but here is the code (only on modern browsers) :
html :
<div class="columns">
very long text split into 4 columns
</div>
css :
.columns {
-webkit-columns: 4;
-moz-columns: 4;
columns: 4;
}
sample here : http://codepen.io/raphaelgoetter/pen/ehfxb
I have a set of two divs - First set: when people mouse over these divs, it will fire an event, Second set: when the event is fired, these divs will be displayed.
When you mouse over a div in the first set, it should display its corresponding div in the second set. I thought an easy way to match the mouseover divs with the correct div to display would be using arrays. I've been able attach the event listeners properly, but I can't figure out how to set it up so that when you mouseover one object of an array, it displays the array object with the same index number. I think if I could figure out how to recoginze the index number of the object I am mousing over, I could get it to work. I've tried a lot of things, but haven't been able to create anything that works. Here's the code:
<script>
$(document).ready(function(){
//create array of divs to mouse over
var ar = new Array();
ar[0] = $("#home");
ar[1] = $("#doc");
var length = ar.length;
//create array of divs to display when event is fired
var des = new Array();
des[0] = $("#homeDes");
des[1] = $("#docDes");
// start for
for ( var i = 0; i< length; ++i )
{
ar[i].bind("mouseover",function(){$(des[i]).css("display","block");});
ar[i].bind("mouseout",function(){$(des[i]).css("display","none");});
}
//end for
});
//end
</script>
I would tend toward making a more flexible approach to this so that you don't need to change your javascript when you change your HTML. Consider classing your elements that need to have the bindings and providing data attribute to specify the target. Your HTML for divs to be bound might look like this:
<div id="home" class="mouseoverToggleTrigger" data-target="#homeDes">...</div>
And the jQuery might look like this:
$(document).ready(function(){
$('.mouseoverToggleTrigger').hover(function() {
var $target = $($(this).data('target'));
$target.toggle();
}
});
Note this is assuming you are using HTML5 for which jQuery, by default, converts data-* into values retrievable via data().
For pages that are not HTML5, this more generalized solution will work
$(document).ready(function(){
$('.mouseoverToggleTrigger').hover(function() {
var $target = $($(this).prop('data-target'));
$target.toggle();
}
});
One additional bit of flexibility this gives, is that you now don't have to limit yourself to a one-to-one trigger to target mapping. You could specify a class name or other jQuery selector for data-target values to get highly customized behavior, such as one trigger toggling all elements of a certain class that are children of another class.
$(document).ready(function(){
//create array of divs to mouse over
var ar = new Array();
ar[0] = $("#home");
ar[1] = $("#doc");
var length = ar.length;
//create array of divs to display when event is fired
var des = new Array();
des[0] = $("#homeDes");
des[1] = $("#docDes");
// start for
for ( var i = 0; i< length; ++i )
{
// WRAP THE BODY OF THE FOR LOOP IN A FUNCTION
function(index) {
ar[index].bind("mouseover",function() {
$(des[index]).css("display","block");}
);
ar[index].bind("mouseout",function() {
$(des[index]).css("display","none");
});
}(i);
}
//end for
});
When the events are fired the value of i is the length of the array, you have to pass the value of i to another function so that in each function scope the value of index will be the value of i when it was called.
A simpler approach code wise is to give the common elements common classes and then use jQuery index() and eq() to match pairings
HTML
<a id="home" class="hoverMe">
<a id="doc" class="hoverMe">
<div id="homeDes" class="content">
<div id="docDes" class="content">
JS
var $content=$('.content')
var $links=$('.hoverMe').hover(function(){
$content.eq( $links.index(this) ).show()
},function(){
$content.eq( $links.index(this) ).hide()
})
index() API Docs
eq() API Docs
I have a list of elements on a page, for the sake of discussion we can say I have the following:
<div id="group_01">
<div id="entry_1-01">stuff x</div>
<div id="entry_1-02">stuff x</div>
</div>
<div id="group_02">
<div id="entry_2-01">stuff x</div>
<div id="entry_2-02">stuff x</div>
</div>
The delete link calls an Ajax request and deletes the entry, after a succesful Ajax call, the entry div is removed from the page. My question is:
How can I remove the containing group div once all of it's entries have been deleted?
I hope that's a detailed enough question. I feel like this isn't anything new, yet two days of search has resulted in nothing.
Before you delete the child element, get its parent, count the number of children, and then after deleting the child, delete the parent if the child count is zero. Here is a quicky piece of sample code:
function d (x)
{
var e = document.getElementById(x);
var p = e.parentNode;
p.removeChild (e);
if (p.childNodes.length == 0) {
var pp = p.parentNode;
pp.removeChild (p);
}
}
I added onclicks to your divs like this:
<div id="group_01">
<div id="entry_1_01">stuff 11<a onclick="d('entry_1_01');" href="#delete">x</a></div>
<div id="entry_1_02">stuff 12<a onclick="d('entry_1_02');" href="#delete">x</a></div>
</div>
I also changed the link to "#delete". You could tidy this up in various ways.
A function like this should would work:
function removeNodeIfEmpty(node) {
var container = document.getElementById(node);
var nodeCount = 0;
for (i = 0; i < container.childNodes.length, i++) {
if (container.childNodes[i].nodeType == 1) {
nodeCount += 1;
}
}
if (nodeCount < 1) {
container.parentNode.removeChild(node);
}
}
This should account for the whitespace issue.
Assuming you do something like this to remove an entry:
entryDiv.parentNode.removeChild(entryDiv);
then you should be able to use the following code to remove the group div when the last child is removed:
var groupDiv = entryDiv.parentNode;
groupDiv.removeChild(entryDiv);
if (!groupDiv.firstChild) {
groupDiv.parentNode.removeChild(groupDiv);
}
...although you need to watch out for whitespace-only text nodes, if these entries haven't been created directly by script.
Really depends what library you're using
http://docs.jquery.com/Traversing/parent#expr
should be a suitable expression
I was taking a look at http://www.zenfolio.com/zf/features.aspx and I can't figure out how they are doing the accordion style expand and collapse when you click on the orange links. I have been using the Web Developer Toolbar add-on for firefox, but I have not been able to find anything in the source of the page like JavaScript that would be doing the following. If anyone knows how they are doing it, that would be very helpful.
This is actually unrelated, but if all you answers are good, who do I give the answer too?
They're setting the .display CSS property on an internal DIV from 'none' to '', which renders it.
It's a bit tricky, as the JS seems to be in here that's doing it:
http://www.zenfolio.com/zf/script/en-US/mozilla5/windows/5AN2EHZJSZSGS/sitehome.js
But that's basically how everyone does this. It's in there, somewhere.
EDIT: Here it is, it looks like:
//... (bunch of junk)
zf_Features.prototype._entry_onclick = function(e, index)
{
var cellNode = this.dom().getElementsByTagName("H3")[index].parentNode;
while (cellNode.tagName != "TD") cellNode = cellNode.parentNode;
if (this._current != null) this.dom(this._current).className = "desc";
if ("i" + index != this._current)
{
cellNode.className = "desc open";
cellNode.id = this.id + "-i" + index;
this._current = "i" + index;
}
else this._current = null;
zf_frame.recalcLayout();
return false;
};
Basically, what they're doing is a really roundabout and confusing way of making the div inside of TD's with a class "desc" change to the class "desc open", which reveals its contents. So it's a really obtuse roundabout way to do the same thing everyone does (that is, handling onclick to change the display property of a hidden div to non-hidden).
EDIT 2:
lol, while I was trying to format it, others found it too. =) You're faster than I ;)
Using jQuery, this effect can be built very easily:
$('.titleToggle').click(function() {
$(this).next().toggle();
});
If you execute this code on loading the page then it will work with any markup that looks like the following:
<span class="titleToggle">Show me</span>
<div style="display:none">This is hidden</div>
Notice that this code will work for any number of elements, so even for a whole table/list full of those items, the JavaScript code does not have to be repeated or adapted in any way. The tag names (here span and div) don't matter either. Use what best suits you.
It is being done with JavaScript.
When you click a link, the parent td's class changes from 'desc' to 'desc open'. Basically, the expanded text is always there but hidden (display: none;). When it gets the css class of 'open' the text is no longer being hidden (display: block;).
If you look in the sitehome.js and sitehome.css file you can get an idea about how they are doing that.
btw I used FireBug to get that info. Even though there is some feature duplication with Web Developer Toolbar it's worth the install.
They're using javascript. You can do it too:
<div id="feature">
Feature Name
<div id="desc" style=="display:none;">
description here...
</div>
</div>
<script type="text/javascript">
function toggle()
{
var el=document.getElementById('desc');
if (el.style.display=='none')
el.style.display='block'; //show if currently hidden
else
el.style.display='none'; //Hide if currently displayed
}
</script>
The function above can be written using Jquery for smooth fade in/fade out animations when showing/expanding the descriptions. It has also got a SlideUp and Slidedown effect.
There is a lot of obfuscated/minified JS in their master JS include. It looks like they are scraping the DOM looking for H3's and checking for table cells with class desc and then processing the A tags. ( or some other order, possibly ) and then adding the onclick handlers dynamically.
if (this._current != null) this.dom(this._current).className
= "desc"; if ("i" + index != this._current) { cellNode.className = "desc open"; cellNode.id = this.id
+ "-i" + index; this._current = "i" + index; }
http://www.zenfolio.com/zf/script/en-US/safari3/windows/5AN2EHZJSZSGS/sitehome.js
The script is here.
The relevant section seems to be (re-layed out):
// This script seems over-complicated... I wouldn't recommend it!
zf_Features.prototype._init = function()
{
// Get a list of the H3 elements
var nodeList = this.dom().getElementsByTagName("H3");
// For each one...
for (var i = 0; i < nodeList.length; i++)
{
// ... set the onclick to be the function below
var onclick = this.eventHandler(this._entry_onclick, i);
// Get the first <a> within the H3 and do the same
var node = nodeList[i].getElementsByTagName("A")[0];
node.href = "#";
node.onclick = onclick;
// And again for the first <span>
node = nodeList[i].getElementsByTagName("SPAN")[0];
node.onclick = onclick;
}
};
zf_Features.prototype._entry_onclick = function(e, index)
{
// Get the parent node of the cell that was clicked on
var cellNode = this.dom().getElementsByTagName("H3")[index].parentNode;
// Keep going up the DOM tree until we find a <td>
while (cellNode.tagName != "TD")
cellNode = cellNode.parentNode;
// Collapse the currently open section if there is one
if (this._current != null)
this.dom(this._current).className = "desc";
if ("i" + index != this._current)
{
// Open the section we clicked on by changing its class
cellNode.className = "desc open";
cellNode.id = this.id + "-i" + index;
// Record this as the current one so we can close it later
this._current = "i" + index;
}
else
this._current = null;
// ???
zf_frame.recalcLayout();
return false;
};
Edit: added some comments
Unfortunately their code is in-lined and hard to read (http://www.zenfolio.com/zf/script/en-US/mozilla5/windows/5AN2EHZJSZSGS/sitehome.js), but this looks quite simple to implement... something along these lines (using prototypejs):
<script>
var showHide = {
cachedExpandable: null
,init: function() {
var containers = $$(".container");
for(var i=0, clickable; i<containers.length; i++) {
clickable = containers[i].getElementsByClassName("clickable")[0];
Event.observe(clickable, "click", function(e) {
Event.stop(e);
showHide.doIt(containers[i]);
});
}
}
,doIt: function(container) {
if(this.cachedExpandable) this.cachedExpandable.hide();
var expandable = container.getElementsByClassName("expandable")[0];
if(expandable.style.display == "none") {
expandable.show();
} else {
expandable.hide();
}
this.cachedExpandable = expandable;
}
};
window.onload = function() {
showHide.init();
};
</script>
<div class="container">
<div class="clickable">
Storage Space
</div>
<div class="expandable" style="display: none;">
Description for storage space
</div>
</div>
<div class="container">
<div class="clickable">
Galleries
</div>
<div class="expandable" style="display: none;">
Description for galleries
</div>
</div>
Its also caching the earlier expandable element, so it hides it when you click on a new one.