javascript for loop not iterating completely through object - javascript

I'm currently experimenting with dynamically loading data from a JS Object into html containers by iterating over it with a For Loop.
The wall I have hit seems to be because the For Loop won't iterate through the whole object, even though I'm setting high threshold of the For Loop based on a calculation of the amount of objects that are held within the JS Object.
A working solution will have all objects loaded into their respective html containers. At this point I'm not fussed about showing the objects inside the achievements property object.
This experiment is to better understand Pure Javascript so no Jquery or framework suggestions please.
Data Object:
var data = { projects: [{
title: "GET BORN",
tags: ["Live Events", "Stage Design", "Event Promotion", "Music"],
date_started: "21/09/12",
date_finished: "Finish Date",
description: "Music events that explores the underground sound",
achievements: [{milestone:"Launched Brand", date:"datetime", details:"blah"}, {milestone:"Hosted First Night", date:"datetime", details:"moreblah"}, {milestone:"Sold Out Lakota +1000 People", date:"datetime", details:"moreblah"}],
position: 1
}, {
title: "FAIRSTREAM",
tags: ["Web Application", "Trademark", "Music streaming"],
date_started: "10/05/16",
date_finished: "Finish date",
description: "Equal opportunity music streaming application",
achievements: [{milestone:"Launched Brand", date:"datetime", details:"blah"}],
position: 2
}]}
View Generating Function:
const buildProjectView = (dataSet) => {
const currentProjectIndex = 0
let dataLen = Object.keys(dataSet.projects[currentProjectIndex]).length
// console.log(dataLen)
let objKey = Object.keys(dataSet.projects[currentProjectIndex])
let objValue = Object.values(dataSet.projects[currentProjectIndex])
// console.log(objValue)
for (let i = 0; i < dataLen; i++) {
// console.log("count: " + i)
console.log(objKey[i] + ": " + objValue[i])
let theTitle = document.getElementById(objKey[i])
let content = document.createTextNode(objValue[i])
theTitle.appendChild(content)
}
}
window.onload = buildProjectView(data)
HTML Boilerplate:
<html>
<head>
  <title>Mysite Playground</title>
</head>
<body>
<div class="container">
<section class="head">
<h1 id="title"/>
<h2 id="description"/>
<h3 id="date_started"/>
</section>
<section class="body">
<p id="position">#</p>
<p id="achievements"/>
<p id="tags"/>
</section>
</div>
</body>
</html>
My coding test platform with example and some basic styling:
https://codepen.io/wntwrk/pen/bqXYMO
Thanks for your help in advance.

This should give you a starting point to give you something to work with.
You have an array with arrays in it and an array of objects.
Note that simply "pushing" stuff into the DOM on the existing markup/boilerplate does not help you, you need to clone that to allow more instances - which is what I did.
Using this data provided:
/*jshint esversion: 6 */
var data = {
projects: [{
title: "GET BORN",
tags: ["Live Events", "Stage Design", "Event Promotion", "Music"],
date_started: "21/09/12",
date_finished: "Finish Date",
description: "Music events that explores the underground sound",
achievements: [{
milestone: "Launched Brand",
date: "datetime",
details: "blah"
}, {
milestone: "Hosted First Night",
date: "datetime",
details: "moreblah"
}, {
milestone: "Sold Out Lakota +1000 People",
date: "datetime",
details: "moreblah"
}],
position: 1
}, {
title: "FAIRSTREAM",
tags: ["Web Application", "Trademark", "Music streaming"],
date_started: "10/05/16",
date_finished: "Finish date",
description: "Equal opportunity music streaming application",
achievements: [{
milestone: "Launched Brand",
date: "datetime",
details: "blah"
}],
position: 2
}]
};
I added some functions to hopefully make it easier to understand.
// convert the "boilerplate" to a class based avoiding duplicate id's
function idToClass(node) {
var child, nodes = node.childNodes;
for (var i = 0, len = nodes.length; i < len; i++) {
child = nodes[i];
if (typeof child.id === 'string' && child.id !== "") {
child.className = child.id;
child.removeAttribute("id");;
}
if (child.childNodes && child.childNodes.length) {
idToClass(child);
}
}
}
let addItem = (project, aclone, name) => {
let el = aclone.getElementsByClassName(name);
if (el.length && typeof project[name] !== "object") {
el[0].textContent = project[name];
}
if (el.length && typeof project[name] === "object") {
/// do something with objects like the "achievements"
}
};
let addProject = (project, aclone) => {
var k = Object.keys(project);
k.forEach(function(item) {
addItem(project, aclone, item);
});
return aclone;
};
const buildProjectView = (dataSet) => {
let y = document.getElementsByClassName('container');
//clone and change ids to classes
let aclone = y[0].cloneNode(true);
idToClass(aclone);
for (let i = 0; i < dataSet.projects.length; i++) {
let n = addProject(dataSet.projects[i], aclone.cloneNode(true));
y[0].parentNode.appendChild(n, aclone);
}
// COULD remove the boilerplate here:
// y[0].removeNode();
};
window.onload = buildProjectView(data);

Related

Javascript arrays how to print sorted elements

I have a question about arrays. I'm new to javascript and I'm writing a program that has a function where it filters 20 elements of an array by category. That is, I have 3 buttons where, by clicking on one of them, the function is turned on and it starts displaying the filtered elements. Please tell me how can this be done? I have already tried a bunch of ways, but in the end nothing happened, although I think that I made a mistake somewhere.
array:
window.products = [
{
id: "i8",
title: "Iphone 8",
description:
"The iPhone 8 ",
price: 19900,
discontinued: false,
categories: ["c1"]
},
{
id: "i10",
title: "Iphone X",
description: "Iphone 10",
price: 39900,
discontinued: false,
categories: ["c1"]
},
{
id: "i11",
title: "Iphone 11",
description: "The iPhone 11 ",
price: 69900,
discontinued: false,
categories: ["c1"]
};
my function
function myFunction() {
document.getElementById("selected-category").innerHTML = "Iphones";
document.getElementById("category-products").innerHTML = "";
for (i = 0; i < 20; i++) {
document.getElementById("category-products").innerHTML = window.id;
const found = window.products.find(window.products.categories == "c1");
console.log(found);
}
}
part html code with button
<button onclick="myFunction()">Iphones</button>
First, you have syntax error in your windows.products = [ ... };. It's not closed properly (you need a ] before the };.
Then, the find method needs to be passed a function that processes an element of the Array. What you tried window.products.categories == "c1" evaluates to false, because the property categories does not exist in the window.products array. You get undefined on the left hand side and a string on the right hand side, so it's always false. You'd get "false is not a function".
Examples of using find() with a function:
const found = window.products.find( element => element.categories == "c1" );
or
const found = window.products.find( function(element) {
return element.categories == "c1"
});
But then:
The above == "c1" isn't what you should use, because it only matches due to type coercion from an array to a string, and only matches when the categories has only "c1" and no other elements. You should use includes method.
"find" will only give you one matching product. Use "filter" to find all matching ones.
If you only need to search for one key "c1", you can use
const found = window.products.filter( product=> product.categories.includes("c1")); and for two keys "c1" and "c2": const found = window.products.filter( product => product.categories.includes("c1") && product.categories.includes("c2"));
But I don't think you should use the above, because you should handle the case where the user searches for multiple keys.
const searchList = ['c1', 'c2', 'c3']
const found = window.products.filter ( function( product) {
//returns true if element.categories contains all of 'c1', 'c2' and 'c3'
return searchList.every( searchKey => product.categories.includes(searchKey)) ;
})
You can also do the search in one line, but may be harder to read :
const searchList = ['c1', 'c2', 'c3']
const found = window.products.filter ( product => searchList.every( searchKey => product.categories.includes(searchKey)) );

Pick one line to improve. Beginner efficiency code question

I was given homework on BigO Notation and we are supposed to improve a set of code for each exercise but I am having trouble on the final question. For this, they want us to improve the code efficiency by changing one line of code, two at most. When I run it through VSCode, it tells me that .values does not exist on object types so I am assuming to improve efficiency, it has something to do with that line but not sure what to change.
let myMovies = [
{
"title":"Rush Hour 2",
"year":2001,
"cast":[
"Jackie Chan",
"Chris Tucker"
],
"genres":[
"Comedy"
]
},
{
"title":"The Safety of Objects",
"year":2001,
"cast":[
"Glenn Close",
"Dermot Mulroney",
"Patricia Clarkson"
],
"genres":[
"Drama"
]
},
{
"title":"Rush Hour 2",
"year":2001,
"cast":[
"Jackie Chan",
"Chris Tucker"
],
"genres":[
"Comedy"
]
},
//etc...
]
function removeDuplicates(movies) {
let indexedMovies = {};
movies.forEach( (movie) => {
if (Object.keys(indexedMovies).indexOf(movie.title) < 0) {
indexedMovies[movie.title] = movie;
}
})
return indexedMovies.values();
}
let fixedMovies = removeDuplicates(myMovies);
I think the idea is that you would take this section of code:
movies.forEach( (movie) => {
if (Object.keys(indexedMovies).indexOf(movie.title) < 0) {
indexedMovies[movie.title] = movie;
}
})
And replace it with
movies.forEach( (movie) => {
indexedMovies[movie.title] = movie;
})
Because when you do indexedMovies[movie.title] it will replace any existing "duplicate", so there's no need to check for it explicitly with all the code in the if statement, which is pretty inefficient on its own -- creating an array just to linearly search for an item in that array.
1) There is no need to search for existing movie title as
Object.keys(indexedMovies).indexOf(movie.title) < 0
since it will take O(n) and increase the complexity, You can directly check for existence as(if you want to)
!indexedMovies[movie.title]
Any even you can eliminate this process and directly assign movie
indexedMovies[movie.title] = movie;
2) There is not such method on values() on indexedMovies object. Either return indexedMovies object or its values as
Object.values(indexedMovies)
let myMovies = [{
title: "Rush Hour 2",
year: 2001,
cast: ["Jackie Chan", "Chris Tucker"],
genres: ["Comedy"],
},
{
title: "The Safety of Objects",
year: 2001,
cast: ["Glenn Close", "Dermot Mulroney", "Patricia Clarkson"],
genres: ["Drama"],
},
{
title: "Rush Hour 2",
year: 2001,
cast: ["Jackie Chan", "Chris Tucker"],
genres: ["Comedy"],
},
//etc...
];
function removeDuplicates(movies) {
let indexedMovies = {};
movies.forEach((movie) => indexedMovies[movie.title] = movie);
return Object.values(indexedMovies);
}
let fixedMovies = removeDuplicates(myMovies);
console.log(fixedMovies);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}

How to access array object value and return it to a new element text content or inner text?

I am new to JS. I am struggling to write a simple program to access an object's property value from an array and set an interval to cycle between the properties of the objects and place them inside a span after a few seconds.
let nameList = [
{
title: "Hotel Apple Den",
location: "Paris"
},
{
title: "Hotel Garden View",
location: "NY"
},
{
title: "Resident Inn",
location: "India"
},
{
title: "Indian Villa",
location: "India"
}
];
let nameChange = document.querySelector(".randomNameOne");
for (let i = 0; i < nameList.length; i++) {
document.querySelector(".addText").innerHTML = setInterval(() => {
`<span> ${nameList[i].title}</span>`;
});
}
You can do that in following steps:
Create a variable to store current element of the array.
Use setInterval() and pass a call back to it with the time you want.
Inside function increase the variable i(current element of array).
Use insertAdjacentHTML() instead of innerHTML
let nameList = [
{
title: "Hotel Apple Den",
location: "Paris"
},
{
title: "Hotel Garden View",
location: "NY"
},
{
title: "Resident Inn",
location: "India"
},
{
title: "Indian Villa",
location: "India"
}
];
const shuffle = (arr) => arr.slice().sort((a,b) => Math.random() - 0.5)
let shuffled = shuffle(nameList)
let i = 0;
const elm = document.querySelector(".addText")
let interval = setInterval(() => {
elm.insertAdjacentHTML
("afterend",`<span> ${shuffled[i].title}</span><br>`)
i++;
if(i===nameList.length) clearInterval(interval)
},1000)
<div class="addText"></div>

Split Pushed Elements Into A Javascript Empty Array

I have an empty array called result[]. A user then clicks a link which calls a function called getId() that passes the clicked id to check if it matches the id in another array called productsArray[].
Jackets
var productsArray = [
{id:0, title:"Product A",description:"description 0"},
{id:0, title:"Product B",description:"description 1"},
{id:2, title:"Product C",description:"description 2",},
{id:0, title:"Product D",description:"description 3",},
{id:4, title:"Product A",description:"description 4",},
{id:5, title:"Product A",description:"description 5",}
],
result = [];
If the user clicks the Jackets link (which has an id of 0) the 3 items in the productsArray, will get returned: Product A, B and D.
The code I have to achieve this actually works:
var output;
var container;
for(key in productsArray) {
if (productsArray[key].id == id) {
result.push(productsArray[key].title, productsArray[key].description);
}
}
container = document.getElementById('content');
output=result;
container.innerHTML=output;
<div id="content"></div>
But the problem is that ALL the data is assigned to the result[] key index:
For example:
[0]Produduct A title
[1]Product B title
[2]Product C title
What I would like to achieve a way of splitting up the data along the lines of the following:
output+= result[0].title + result[0].description
Any help appreciated.
Cheers
You could map the result and the wanted keys of the product.
function getId(id) {
document.getElementById('content').innerHTML = productsArray
.filter(function (product) {
return product.id == id;
})
.map(function (product) {
return ['title', 'description']
.map(function (key) { return product[key]; })
.join(' ');
})
.join('<br>');
}
var productsArray = [{ id: 0, title: "Product A", description: "description 0" }, { id: 0, title: "Product B", description: "description 1" }, { id: 2, title: "Product C", description: "description 2" }, { id: 0, title: "Product D", description: "description 3" }, { id: 4, title: "Product A", description: "description 4" }, { id: 5, title: "Product A", description: "description 5" }];
Jackets
<div id="content"></div>
You have to assign a variable that takes value of title and description and then push that value to array. like below code
function getId(id){
var output;
var container;
for(key in productsArray) {
if (productsArray[key].id == id) {
newObj= "" ; // assign to blank
newObj = productsArray[key].title +" "+ productsArray[key].description; //assign as per you need the format
result.push(newObj); // push it to array
}
}
container = document.getElementById('content');
output=result;
container.innerHTML=output;}

Populating arrays after their definition

I have a ul containing li's which contain names of different recipe ingredients for a recipe page. I'm trying to get those ingredients and store them into a JavaScript array within an object. I already know the title of the recipe so I put that right into the object property title, but I don't know how many ingredients there will be for each recipe. Here is what I have:
var recipeobj = {
title: $('h3.title').val(),
ingredients: [
ingredient,
optional
]
}
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
recipeobj.ingredients[index].ingredient = $(ingredient).html();
recipeobj.ingredients[index].optional = false;
})
If I try to do console.log(recipeobj.ingredients) I just get the error Uncaught ReferenceError: ingredient is not defined
No doubt this is simple, I just rarely need to use arrays in JavaScript so have little experience with them.
Open your console and run it
var recipeobj = {
title: $('h3.title').html(),
// ingredients is empty for now
ingredients: []
};
$.each($('ul.ingredients > li > h4'), function(index, ingredient) {
// Get the name
var name = $(ingredient).html(),
// Find out if it is 'optional'(using a class here)
optional = $(ingredient).hasClass('optional');
// Push a new ingredient into the array
recipeobj.ingredients.push({ name: name, optional: optional });
});
console.log(recipeobj);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="title">Pork and beans</h3>
<ul class="ingredients">
<li>
<h4>Pork</h4>
</li>
<li>
<h4>Beans</h4>
</li>
<li>
<h4 class="optional">Salt*</h4>
</li>
</ul>
This should output:
{
"title": "Pork and beans",
"ingredients": [
{ name : "Pork", optional : false },
{ name : "Beans", optional : false },
{ name : "Salt*", optional : true}
]
}
var rObj = {
title: $('h3.title').val(),
ingredients: [
'source cream',
'cheese',
'chopped meat'
],
optional: true
};
accessing
var rItem = rObj.ingredients[1];
or you want
var rObj = {
title: $('h3.title').val(),
ingredients: {
ingredient_list: ['one','two','three'],
optional: true
}
};
accessing
var rItem = rObj.ingredients.ingredient_list[1];
The structure you are attempting to use looks like the structure should be like
var rObj = {
title: $('h3.title').val(),
things: [{
ingredient: 'source cream',
optional: true
},
{
ingredient: 'cheese',
optional: false
}]
};
accessing
var ingred = rObj.things[1].ingredient;
var rObj = {
title: $('h3.title').val(),
ingredients : []
};
you can add ingredients:
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
rObj.ingredients.push({ingredient: $(ingredient).html(), optional :false})
})

Categories

Resources