shuffling a nodelist and traversing dom properly in javascript - javascript

this has been kind of a hard question to explain so i'll do my best..
basically, i have a raffle app (of sorts). i have a grid of images that are assigned names(captions) and are created dynamically using javascript. the end goal of it is to have each picture on a different spot on the grid with a different name every time a button is clicked. the names are captured from a textarea and stored into an array. for the most part, i have it working. my only issue is that when the button is clicked, the images and names do not random independent of each other. in other words, the images will move on the grid, but the names stay the same for each picture.
i merely just want to shuffle the p tags around. i know it's because my img and p tags are part of the same div, but i'm lost on how to do these separately while still keeping the grid form, otherwise things start going out of place. the grid uses bootstrap.
now this line in particular did sort of work for me:
tDisplay.getElementsByTagName("p") [Math.random() * tDisplay.children.length | 0].textContent = names[Math.random() * tDisplay.children.length | 0];
i know it's long and ugly, just rough draft, but my problem with this is that i do not want names appearing any different than they appear in the names array. so if i have a names array:
["jon", "tim", "tim", "bob", "sally"]
these 5 names should appear on the grid, with "tim" showing 2 times and the other names appearing once. the random line shown above was breaking this rule. as an example when i tested it, "bob" would show up multiple times when it is only in the array once, and "jon" would be left out. i would just like to shuffle.
here is my code for my button logic. there are 3 buttons and a text area. if the baseball or football button is clicked, it will display teams of the respective sport, and then theres the actual random button at the bottom. the img and the p tags are appending to a div (newDiv), which is then appended to the display div tDisplay. i've commented out lines that do not work.
//button logic
bGroup.addEventListener("click", function images(e) {
if (e.target.id !== "random") {
tDisplay.innerHTML = "";
for (var i = 0; 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[i];
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);
};
} else {
for (var i = 0; i <= tDisplay.children.length; i++) {
tDisplay.appendChild(tDisplay.children[Math.random() * tDisplay.children.length | 0] );
// tDisplay.appendChild(tDisplay.children[Math.random() * tDisplay.children.length | 0].lastChild.innerHTML = p[Math.random() * tDisplay.children.length | 0]);
// tDisplay.getElementsByTagName("p") [Math.random() * tDisplay.children.length | 0].textContent = names[Math.random() * tDisplay.children.length | 0];
// names[Math.random() * tDisplay.children.length | 0];
}
}
});

I think its easier to just redraw the whole thing instead of shuffling it. For that we need to shuffle the names array, then take a random image and prevent duplicates:
//a shuffle function taken from https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
function shuffle(a) {
for (let i = a.length; i; i--) {
let j = Math.floor(Math.random() * i);
[a[i - 1], a[j]] = [a[j], a[i - 1]];
}
}
//a names array
var names = ["john","jack"];
//a number array
var numbers = Array.from({length:names.length}).map((_,i)=>i);
function update(){
//shuffle
shuffle(names);
shuffle(numbers);
tDisplay.innerHTML = "";
names.forEach(( name,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 = name;
if (e.target.id === "baseball") {
newImg.src = "images/baseball/team" + numbers[ i ] + ".jpg";
} else if (e.target.id === "football") {
newImg.src = "images/football/team" + numbers[ i ] + ".gif";
}
tDisplay.appendChild(newDiv);
});
}

Related

How to control each image from an array of CSS `url()` values?

I would like to control each image I have in my const imgURLArray, if possible via class or id.
If this is only possible via JS, could someone help me or teach me how to do it?
I have created a CodePen to see the infinite gallery in action.
const imgURLArray = [
"url(img1)",
"url(img2)",
"url(img3)",
"url(img4)",
];
Try editing your createImageGrid function to this:
function createImageGrid() {
for(let y = 0; y < rowNum; y++) {
let row = document.createElement("div");
row.className = rowClass;
for(let x = 0; x < imgNum; x++) {
let image = document.createElement("div");
image.className = imageClass;
// This gives it id
image.id = `image${y}-${x}`
row.appendChild(image);
}
document.querySelector(containerSelector).appendChild(row);
// Add the images to our representation
imgRep.push(gsap.utils.toArray(row.querySelectorAll(imageSelector)));
}
rows = document.querySelectorAll(rowSelector),
imgMidIndex = Math.floor(imgNum / 2),
rowMidIndex = Math.floor(rowNum / 2);
}
With that your images will have id of image${ITS_ROW}-${ITS_COLUMN} and you will be able to pinpoint the exact image this way.
Edit
(Based on comment.)
Just replace your function createImageGrid() {} with this one and it will give your images property id that is = image${y}-${x}.
Output shown here:

How to break array from filling div if the div is full

I'd like to ask a question regarding arrays and HTML divs.
If I have an array of:
const exampleArray = ['Germany','Australia','Canada','Mongolia','Argentina','China'];
And let's say I have a div like so, with a max length of around 100px (sorry for the long looking table, I couldn't format it on stack overflow)
| A div |
| -------- |
I want to try fill up the div as much as possible, but if it overflows, I want it to stop and end with ', ... 5' the number being the number of array items it couldn't print
For example:
| Germany, Australia, Canada, ... 2|
| -------- |
Not:
| Germany, Australia, Canada, Mong... 2|
| --------- |
Could anyone advise what is the best way to implement this?
Assuming you want the ellipsis and the bracketed number to be within the 100px, this snippet keeps adding an array item, plus its comma, calculates what the width of the ellipsis and bracketed remaining count is and if it fits, carries on.
If it has got too big then it reverts to the previous content and displays that.
Note: 100px is quite narrow given on my example at least the ellipsis plus bracketed number take at least 30px, so this snippet will show just one country name. Try it with 200px or smaller font size to test for more.
const exampleArray = ['Germany','Australia','Canada','Mongolia','Argentina','China'];
const div = document.querySelector('#list');
const endbit = document.querySelector('#endbit');
let i, endbitLength;
let divPrev = '';
let endbitPrev = '';
for (i = 0; i< exampleArray.length; i++) {
endbitPrev = endbit.innerHTML;
endbit.innerHTML = '...(' + (exampleArray.length - i) +')';
endbitLength = endbit.offsetWidth;
div.innerHTML = divPrev + exampleArray[i] + ', ';
if (div.offsetWidth > (100 - endbitLength)) {
break;
}
else {
divPrev = divPrev + exampleArray[i] + ', ';
endbitPrev = '...' + '(' + (exampleArray.length - i) + ')';
}
}
endbit.style.display = 'none';
div.innerHTML = divPrev + endbitPrev;
div {
display: inline-block;
}
<div id="list"></div>
<div id="endbit">...</div>
If you are ok with adding a span inside the div then this would do. I know there are better ways to do it, but you can try this function for now
function addToDiv(arr) {
const div = document.querySelector("#div");
const span = div.firstElementChild;
const divWidth = div.clientWidth;
let finalIndex = 0;
let remaining = 0;
for (const [index,c] of arr.entries())
{
span.append(c)
if(span.offsetWidth >= divWidth){
finalIndex = index-1;
remaining = arr.length - index;
break;
}
if(index != arr.length -1)
span.append(', ')
}
if(!remaining) return;
span.textContent = '';
for (const [index,c] of arr.entries())
{
if(index > finalIndex) break;
span.append(c+', ')
}
span.append('...'+remaining)
}
It periodically checks if the width of the span exceeds that of the div. I have written a fiddle here
https://jsfiddle.net/10fbkmg5/4/
You can play with the fiddle by changing the width of the div to check if it fits your requirements.
You can achieve your desired result by using below example, it is just reference for your requirement, just change the code or logic according.
const exampleArray = ['Germany','Australia','Canada','Mongolia','Argentina','China'];
var width=$(".tobefilled").width();
var news=exampleArray.slice(0,-width/15);
var remaining=exampleArray.length-news.length;
$(".tobefilled").html(news+",..."+remaining);
.tobefilled{
width:50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="tobefilled"></div>
You can create a clone to measure how many items fit, here is an example:
const exampleArray = ['Germany', 'Australia', 'Canada', 'Mongolia', 'Argentina', 'China'];
const target = document.querySelector('#target');
const temp = target.cloneNode();
temp.style.whiteSpace = 'nowrap';
temp.style.visibility = 'hidden';
target.parentElement.append(temp);
const content = exampleArray.filter((v, i, arr) => {
temp.textContent = arr.slice(0, i + 1).join(', ');
if (temp.scrollWidth <= target.clientWidth) {
// Make sure that this is the last element or if it is not, that the ellipsis still fits.
if (i === (arr.length - 1)) {
return true;
} else {
temp.textContent += ` ... ${arr.length - (i + 1)}`;
if (temp.scrollWidth <= target.clientWidth) {
return true;
}
}
}
});
const restCount = exampleArray.length - content.length;
target.textContent = content.join(', ')
if (restCount) {
target.textContent += ` ... ${restCount}`;
}
#target {
width: 200px;
}
<div id="parent">
<div id="target"></div>
</div>
What this does is:
Clone the #target element.
Add the clone to the parent of this element (to maximise the changes that it will have the same styling).
Filter the strings array. The filter function adds all the strings up to the current iteration's index to the clone and checks if the clone's width is bigger than the original element, it also makes sure that all the items up to this point as well as the ellipsis text will fit.
The result of the filter operation is then used to set the original element's text and to calculate the count of the remaining element (the ones that don't fit if any).

Randomize gallery array

I'm trying to randomize a gallery of images. The HTML contains only the images, the idea is to then get the array, shuffle it and add them to divs acting as thumbnails. I took the shuffling code from here (the Durstenfeld version): How to randomize (shuffle) a JavaScript array?
My code:
var pics = document.getElementsByTagName("img");
//the Durstenfeld shuffle
for (m = pics.length - 1; m > 0; m--) {
var k = Math.floor(Math.random() * (m + 1));
var temp = pics[m];
pics[m] = pics[k];
pics[k] = temp;
}
//giving ID to each image and adding them to a thumbnail div
for (e = 0; e < pics.length; e++) {
pics[e].id = e;
var photo = document.getElementById(e);
var parent_section = photo.parentNode;
var new_div = document.createElement("div");
new_div.classList.add("thumbnail");
new_div.appendChild(photo);
parent_section.appendChild(new_div);
}
The thing is, this works perfectly fine in IE, but in all of the other browsers I tried, the images are simply placed in the default order (the order in which they are written in the HTML). The shuffling itself should be correct so I'm sure there's something wrong with my implementation of it, but I can't figure it out.
Your Array isnt an Array. Do this:
var pics = Array.from(document.getElementsByTagName("img"));

Get same image randomly two times only

I am creating images flip game in jquery. I am having trouble with images. In image flip game we have only two images of the same type. I have 44 img tags and 22 images. I am taking images randomly.
Q1. How to take two images only of the same type?
Q2. If one image is clicked it should be displayed as it is working now but when any other image is clicked then if the sources (src) of both the images are same, both should be hidden forever. If not both should turn over again.
Here is my code of the script.
<script>
var imgSources = new Array();
var twoImagesOnly = [];
for(var c = 1; c < 22; c++){
imgSources.push(c + ".png");
}
$('#add').click(function(){
addElements(44);
$('#add').attr('disabled', 'true');
});
function addElements(times){
var main = $('#main');
for(j = 1; j <= times; j++){
var div = $("<div>");
var img = $("<img>");
img.attr('src', getImage());
img.attr('width', '100');
img.attr('height', '100');
img.click(function(event){
$(this).slideUp();
event.stopPropagation();
});
div.click(function(){
$(this).children('img').slideDown();
});
div.addClass('div');
div.append(img);
img.addClass('myimg');
main.append(div);
img.slideUp('fast');
}
}
var counter;
function getImage(){
var rand = Math.floor(Math.random() * 22) + 1;
var str = '';
if($.inArray(rand, twoImagesOnly) == -1){
str = rand + '.png';
twoImagesOnly[counter] = rand;
counter++;
}else{
getImage();
}
return str;
}
</script>
and here JSFiddle
Seems someone beat me to the punch with half of my solution while I was editing the fiddle, but I'll post this just because the second half should help you a bit with ensuring that only 2 of each card are posted.
First off, to initialize the array, use the following:
for(var c = 1; c < 23; c++){
imgSources.push(c + ".png");
imgSources.push(c + ".png");
}
This will iterate 22 times, adding files 1.png through 22.png twice each.
Then, to ensure only two of each image are used:
function getImage(){
var rand = Math.floor(Math.random() * imgSources.length);
var str = imgSources[rand];
imgSources.splice(rand,1);
return str;
}
What this will do is remove each array item as they are used, sort of like drawing cards from a deck, ensuring that only two of each image are used and avoiding the "keep trying until it works" approach you had.
Fiddle
Q1. A quick solution to be sure that exactly two images are present, could be to push twice to your array:
for(var c = 1; c < 22; c++){
imgSources.push(c + ".png");
imgSources.push(c + ".png");
}
And then randomize it (see https://stackoverflow.com/a/2450976/3207406 for function example)
And then fetch the image in order with a function like
getImage(i)
Q2. Regarding the "two clicks",
you could use one global variable:
first_image
Which will be null if no image was previously shown.
Otherwise, it will contain the details of the currently show image (like source and id). If the sources don't match, then you can turn back the two pictures after some time.

how to split & add array values into different 'div' components without using "if" condition?

I am using HTML5 + javascript for developing a webpage. I have an array with 100 values. And i have a 10 different HTML5 "div" components. I'm adding 1st 10 array values into 1st "div", 2nd 10 array values into 2nd "div" and similarly goes on. I am using HTML DOM to add these array values into particular "div" component.
Here i have used "if...elseif" condition & is working fine.
But i'm asked not to use "if" condition to add array values into different 'div' elements. Is there any other possible methods to do this?
My div components are 'div1','div2'.......'div10'(added in body tag)
var myArray = ['user1', 'user2', 'user3', ..., 'user100'];
for(i=0;i<myArray.length;i++)
{
var a = document.createTextNode(myArray[i]);
if(i<=10)
{
var container1 = document.getElementById('div1');
container1.appendChild(a);
}
elseif(i>10 && i<=20)
{
var container2 = document.getElementById('div2');
container2.appendChild(a);
}
...
...
...
...
else
{
var container10 = document.getElementById('div10');
container10.appendChild(a);
}
}
It's bad solution. The better one is following:
for(j=0;j<10;j++)
{
//get div1, div2, div3 etc.
var container = document.getElementById('div'+(j+1));
for(i=0;i<10;i++)
{
//get proper value
var a = document.createTextNode(myArray[i+j*10]);
//insert value into container
container.appendChild(a);
}
}
var set=myArray.length/10; /** no of sets of 10 **/
for(i=0;i<set;i++){ //loop through sets
for(int j=(i*10);j<(i+1)*10;j++){ //loop through each set 0-9, 10-19
var a = document.createTextNode(myArray[j]);
document.getElementById('div'+(i+1)).appendChild(a);
}
}
var myArray = ['user1','user2','user3',...'user100'];
for(var i = 0; i < 10; i++) {
var container = document.getElementById("div" + (i + 1));
for(var j = 0; j < 10; j++) {
container.appendChild(document.createTextNode(myArray[(i * 10) + j]));
}
}
If you don't mind array values being inserted into each div as one text node, you could do:
var div, i;
for (i = 1; i < 11; ++i) {
div = document.getElementById('div' + i);
div.innerHTML = myArray.splice(0, 10).join(' ');
}
You don't need 2 for loops. Do you?
for (var i = 1; i <= 100; i++)
{
document.getElementById('div' + ( i%10 + 1)) //this will give your target div
}
P.S: spare me... Typing on mobile...

Categories

Resources