$.each loop of array is looping too many times - javascript

I have the following array and I'm trying to loop through it to create a div, within that div will be the image and a button that toggles the ingredients showing.
var cocktailsArray = [
{image: "mojito.jpeg", ingredients:["lime", "rum", "sugar"]},
{image: "pinacolada.jpeg", ingredients:["coconut liquer", "rum", "pineapple juice"]},
{image: "tomcollins.jpeg", ingredients:["lemon", "gin", "sugar"]}
]
These are my functions:
var createObject = function(){
$("div/div").addClass("cocktail").appendTo("main")
}
var createImage = function(image){
var img = $("<img>").addClass("cocktail-image").appendTo(".cocktail");
img.attr('src', image)
}
var createIngredients = function(ingredients){
$("<button/>").text("Ingredients").addClass("ingredients-button").appendTo(".cocktail")
$("div/div").addClass("ingredients").appendTo(".cocktail")
$.each(ingredients, function(key, ingredient){
var li = $("<li/>").text(ingredient).appendTo(".ingredients")
})
}
var addCocktail = function(picture, ingredients){
createObject();
createImage(picture);
createIngredients(ingredients);
}
and its called like this:
$(document).ready(function(){
$.each(cocktailsArray, function(key, cocktail){
addCocktail(cocktail.image, cocktail.ingredients)
})
});
The output i'm getting is:
mojito pinacollada tomcollins
pinacollada tomcollins
tomcollins
Can anyone explain why the second two lines are populating? Why does it not just stop making objects once its looped through once?

Well that is because you are appending content to all elements with class cocktail(using .appendTo()). you should be only appending to last element in matched set.
You can use :last selector in this case:
var img = $("<img>").addClass("cocktail-image").appendTo(".cocktail:last");

Firstly note that "div/div" is not a valid selector. If you want to create a div you can use $('<div /').
The issue is because you're appending to the new elements by classname within each iteration of the loop. This means that on successive iterations you fill the new elements, but also those .cocktail and .ingredient elements which were created previously.
To fix this you need to keep a reference to the created .cocktail and .ingredients within the loop, and then only append to them directly, something like this:
var cocktailsArray = [{
image: "mojito.jpeg",
ingredients: ["lime", "rum", "sugar"]
}, {
image: "pinacolada.jpeg",
ingredients: ["coconut liquer", "rum", "pineapple juice"]
}, {
image: "tomcollins.jpeg",
ingredients: ["lemon", "gin", "sugar"]
}]
var createObject = function() {
return $("<div />").addClass("cocktail").appendTo("main")
}
var createImage = function($obj, image) {
var img = $("<img>").addClass("cocktail-image").appendTo($obj);
img.attr('src', image)
}
var createIngredients = function($obj, ingredients) {
$("<button/>").text("Ingredients").addClass("ingredients-button").appendTo($obj)
var $ings = $("<div />").addClass("ingredients").appendTo($obj)
$.each(ingredients, function(key, ingredient) {
var li = $("<li/>").text(ingredient).appendTo($ings)
})
}
var addCocktail = function(picture, ingredients) {
var $obj = createObject();
createImage($obj, picture);
createIngredients($obj, ingredients);
}
$(document).ready(function() {
$.each(cocktailsArray, function(key, cocktail) {
addCocktail(cocktail.image, cocktail.ingredients)
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<main></main>
Note how createObject() now returns the new div element, and that reference is used in the createImage() and createIngredients() functions. The same technique is used to store a reference to the .ingredients div, although that's kept local within createIngredients().

Related

How do I replace an item in an array with one that isn't in said array?

So I want to have one of the images in the array change to a question mark. Here's what I have so far.
var icons = new Array ();
icons [0] = { html: "Animals/Ape.png" };
icons [1] = { html: "Animals/Bat.png" };
icons [2] = { html: "Animals/Cow.png" };
icons [3] = { html: "Animals/Emu.png" };
icons [4] = { html: "Animals/Pig.png" };
icons [5] = { html: "Animals/Dog.png" };
icons [6] = { html: "Animals/Rat.png" };
icons [7] = { html: "Animals/Yak.png" };
icons [8] = { html: "Animals/Cat.png" };
function showBack()
{
document.getElementById('table').style.display = "none";
document.getElementById('tableblank').style.display = "block";
icons[0]="Animals/Q.png";
setTimeout(showFront,3000);
}
function showFront()
{
document.getElementById('table').style.display = "block";
document.getElementById('tableblank').style.display = "none";
document.getElementById('speech').style.display = "block";
document.getElementById('typewindow').style.display = "block";
}
I've tried a couple of solutions but I haven't been able to make any work. Thanks for the help :)
if you want to re-assign this object you may do it like that :
icons[0].html = "Animals/Q.png";
I would do something like this...
var icons = [ "Animals/Ape.png",
"Animals/Bat.png",
"Animals/Cow.png" ];
icons[1] = "Animals/Q.png";
document.write(icons[1]);
or if you want to keep an the url as an object...
var oIcons = [{ html:"Animals/Ape.png" },
{html:"Animals/Bat.png"},
{html:"Animals/Cow.png"}];
oIcons[2].html = "Animals/Q.png";
document.write(oIcons[2].html);
So basicly on show back you should have...
icons[0].html="Animals/Q.png";
Considering the code you posted in your comment:
<div id="containerGameplay">
<div id = "table">
<script>
for (i = 0; i < 9 ; i++) {
document.write ("<div class = 'tile'><img src='" + icons[i].html+ "'></div>");
}
</script>
</div>
You are running inline code on your page, which is executed as soon as the browser loads it. To change an image later, you'll need to create a reference to the element you want to change. The easiest way to do this is to give your elements IDs, which would change your code like so:
document.write("<div class='title'><img id='icon_" + i + "' src='" + icons[i].html+ "'></div>");
This will result in the first image (icon[0]) being applied to the element with the ID "icon_0", and so on for each image.
Later, when you want to change an image, you can get a reference to the correct element, and then change the src to that element:
elem = document.getElementById('icon_0');
elem.src = "Animals/Q.png";
Or, following what I believe you were trying to do originally, you can change the array, then set the new source (src):
icons[0].html = "Animals/Q.png";
document.getElementById('icon_0').src = icons[0].html;
They're both the same, but the second example is also storing the value in your array. Keep in mind that there is no automatic data-binding, so simply changing the array does not automatically update the image element.

How to append multiple elements to a div using DocumentFragment

function JGallery() {
this.elements = this._init();
this.overlay = this.elements.overlay;
this.media_hld = this.elements.media_hld;
}
JGallery.prototype._init = function(){
var overlay = document.createElement('div');
var media_hld = document.createElement('div');
return{
'overlay': overlay,
'media_hld': media_hld
}
};
This is where I create a document fragment and using it so I can add several div to same element:
JGallery.prototype.getReference = function(holder) {
var overlay = this.overlay;
var media_hld = this.media_hld;
var that = this;
var holderChildren = holder.querySelectorAll('img');
var docfrag = document.createDocumentFragment();
holderChildren.forEach(function (e) {
e.addEventListener('click', JGallery.prototype.showMe.bind(that), false);
var media_holder = that.media_hld;
media_holder.textContent = "<img src="+e.getAttribute('src')+">";
docfrag.appendChild(media_holder);
//it only appends the last child of my array...
});
overlay.appendChild(docfrag);
};
my goal is to have something like this:
<div class="JGallery_BG">
<div class="JGallery_mediaContainer"><img src="images/thumb_video.jpg"></div>
<div class="JGallery_mediaContainer"><img src="images/thumb_video.jpg"></div>
</div>
by the way the forEach function works well, 8 or 9 times. But I'm not sure whether it adds node to docFrag on every run or not.
Another thing, I'm not insisting to use a document fragment, if there is a better way to add multiple elements to one element, I like to know about it and use it.
One of the problems is that you are constantly re-using the same media holder <div> element in every iterations.
In the code below that.media_hld is always referencing the same element.
var media_holder = that.media_hld;
media_holder.textContent = "<img src="+e.getAttribute('src')+">";
docfrag.appendChild(media_holder);
If you clone the node it should work and you also need to set the innerHTML property, not the textContent.
var media_holder = that.media_hld.cloneNode();
One other thing I did spot is that what's returned from querySelectorAll is not an array and thus doesn't have a forEach method.
You could borrow forEach from an array instance though:
[].forEach.call(holderChildren, forEachBodyFunction);
The entire thing could read:
JGallery.prototype.getReference = function(holder) {
var docfrag = document.createDocumentFragment(),
images = holder.querySelectorAll('img');
[].forEach.call(images, function (img) {
img.addEventListener('click', JGallery.prototype.showMe.bind(this), false);
var media_holder = this.media_hld.cloneNode();
media_holder.appendChild(img.cloneNode());
docfrag.appendChild(media_holder);
}.bind(this));
this.overlay.appendChild(docfrag);
};

Javascript: Get the innerHTML of a dynamically created div

I am retrieving some information from an xml file ( movie information ) and I am creating dynamically some DOM elements according to each movie. I want, when I click on the test element, to get the value of the title of the movie. Right now, no matter which movie I click, it gets the title of the last movie that was introduced.
How can I get the title of each individual movie when I click on that div and not the last one introduced by the for-loop?
xmlDoc=xmlhttp.responseXML;
var x=xmlDoc.getElementsByTagName("movie");
for (i=0;i<x.length;i++)
{
var titlu = x[i].getElementsByTagName("title")[0].childNodes[0].nodeValue;
var description = x[i].getElementsByTagName("description")[0].childNodes[0].nodeValue;
var descriere = document.createElement('div');
descriere.className='expandedDescriere';
descriere.innerHTML = description;
var titlediv = document.createElement('div');
titlediv.className = 'title';
titlediv.id='title';
titlediv.innerHTML = title;
var test=document.createElement('div');
test.className='test';
test.onclick= function(){
var filmName= test.previousSibling.innerHTML;
alert(filmName);
}
placeholder.appendChild(titlediv);
placeholder.appendChild(test);
placeholder.appendChild(descriere);
}
I think your problem might be in the function you assigned to onclick:
test.onclick= function(){
var filmName= test.previousSibling.innerHTML; // <===
alert(filmName);
}
the marked line should be var filmName= this.previousSibling.innerHTML;
My guess is that the var test is hoisted out of the for loop, meaning that when the loop finished, all the onclick function are referencing the same test variable which is the last element you created.
Use this to reference the clicked element:
test.onclick = function() {
var filmName = this.previousSibling.innerHTML;
alert(filmName);
};

jQuery appending to a dynamically created div

Trying to add a save/load feature using JSON to a diagram that uses jsPlumb. The save feature works like a charm however the load feature is not able to replicate full initial saved state. That is, the problem occurs when jQuery is trying to append to a freshly/dynamically created div.
The jsfiddle has the following functionality:
I can add projects which are div containers. Inside these I can add tasks by clicking on the green projects.
http://jsfiddle.net/9yej6/1/
My saving code plugins everything into an array which then becomes a string (using JSON).
$('#saveall').click(function(e) {
// Saving all of the projects' parameters
var projects = []
$(".project").each(function (idx, elem) {
var $elem = $(elem);
projects.push({
blockId: $elem.attr('id'),
positionX: parseInt($elem.css("left"), 10),
positionY: parseInt($elem.css("top"), 10)
});
});
// Saving all of the tasks' parameters
var tasks = []
$(".task").each(function (idx, elem) {
var $elem = $(elem);
tasks.push({
blockId: $elem.attr('id'),
parentId: $elem.parent().attr('id')
});
});
// Convert into string and copy to textarea
var flowChart = {};
flowChart.projects = projects;
flowChart.tasks = tasks;
var flowChartJson = JSON.stringify(flowChart);
$('#jsonOutput').val(flowChartJson);
});
The load code does the same in reverse.
$('#loadall').click(function(e) {
// Delete everything from the container
$('#container').text("");
// Convert textarea string into JSON object
var flowChartJson = $('#jsonOutput').val();
var flowChart = JSON.parse(flowChartJson);
// Load all projects
var projects = flowChart.projects;
$.each(projects, function( index, elem ) {
addProject(elem.blockId);
repositionElement(elem.blockId, elem.positionX, elem.positionY)
});
// Try to load all tasks
var tasks = flowChart.tasks;
$.each(tasks, function( index, elem ) {
//Problem occurs here, I am unable to reference the created project
$(elem.parentId).text('This is a test');
addTask(elem.parentId, 0);
});
});
Basically, what's not working is the $(parentId).append(newState); line 75 in the jsFiddle, I can't seem to reference that div because it was just created using jquery ?
edit:
More specifically, I make use of these functions to create actual project and task divs
function addProject(id) {
var newProject = $('<div>').attr('id', id).addClass('project').text(id);
$('#container').append(newProject);
jsPlumb.draggable(newProject, {
containment: 'parent' });
}
function addTask(parentId, index) {
var newState = $('<div>').attr('id', 'state' + index).addClass('task').text('task ' + index);
$(parentId).append(newState);
}
It should be:
$('#' + parentId).append(newState);
To search for an ID in a jQuery selector, you need the # prefix.

How do I update html element in a string inside an object?

I'm having a hard time doing something that seems really simple.
I have a javascript object:
var main = {
pages : {
"123" : {
"content": "<div><p>The div</p></div><nav><p>The nav</p></nav>",
"foo":"bar"
},
"456" : {
"content": "<div><p>The div</p></div><nav><p>The nav</p></nav>",
"foo":"bar"
}
}
};
I'm trying the following code:
$(function(){
var p = main.pages;
$.each(p,function(i,el){
var c = p[i].content;
var newc = $(c).find('nav').html('<p>New content</p>');
//console.log(c);
//console.log(newc);
});
//console.log(p)
});
To make my object look like:
var main = {
pages : {
"123" : {
"content": "<div><p>The div</p></div><nav><p>New content</p></nav>",
"foo":"bar"
},
"456" : {
"content": "<div><p>The div</p></div><nav><p>New content</p></nav>",
"foo":"bar"
}
}
};
But I'm having a hard time doing it. What am I missing?
I have set up the former example in jsfiddle
I do understand that it's generally not a good idea trying to manipulate strings as dom elements but I have no other choice since the object comes from a RESTful server and it doesn't make much sense to inject the html in the current page for that.
You can actually build the HTML in jQuery without appending it into the DOM.
$(function () {
var p = main.pages;
//a temporary div
var div = $('<div>');
$.each(p, function (i, el) {
//append to div, find and replace the HTML
div.append(p[i].content).find('nav').html('<p>New content</p>');
//turn back into a string and store
p[i].content = div.html();
//empty the div for the next operation
div.empty();
});
console.log(p);
});
Because $(p[i].content) is not one element but two, so based on your code:
var tmp = $(p[i].content);
tmp.eq(1).html('<p>new content');
console.log(tmp.html());

Categories

Resources