Error while looping nested array of objects using pure javascript - javascript

I want to display tasks array of object values(given below) using javascript. When I iterate through tasks array and render elements, it's okay. But I want to have loop inside outer loop to display sub tasks of each task. But, if I loop through each sub task as given below, then it throws error in the console as task.subTasks.forEach(...) is not a function
const tasks = [
{
id: 0,
description: "task 1",
subTasks: [
{ id: 0, description: "subtask 1" },
{ id: 1, description: "subtask 2" },
],
},
{
id: 1,
description: "task 2",
subTasks: [
{ id: 0, description: "subtask 3" },
{ id: 1, description: "subtask 4" },
],
},
{
id: 2,
description: "task 3",
subTasks: [
{ id: 0, description: "sub task 5" },
{ id: 1, description: "sub task 6" },
],
},
];
const accordion = document.querySelector("#accordion");
function displayTasks() {
tasks.forEach((task, index) => {
let html = `
<div class="accordion-item" id="${index}">
<div class="todo-task">
<i class="far fa-circle"></i>
<input value="${task.description}" placeholder="Update your task" type="text">
<i class="fas fa-pen"></i>
<i class="fa fa-trash"></i>
<i class="fas fa-chevron-down"></i>
</div>
<div class="todo-sub-tasks">
`
task.subTasks.forEach((item,index)=>{
<div class="todo-sub-task">
<i class="far fa-circle"></i>
<input placeholder="Update your sub task" type="text" />
<i class="fas fa-pen"></i>
<i class="fa fa-trash"></i>
</div>
})
`
</div>
</div>
`;
accordion.insertAdjacentHTML("beforeend", html);
});
}
displayTasks();

Your html variable needs to be declared outside the loop call, so that you can feed the result of the loop into it.
An inner variable is used in the example below to compile the required HTML for each task.
const tasks = [{
id: 0,
description: "task 1",
subTasks: [
{ id: 0, description: "subtask 1" },
{ id: 1, description: "subtask 2" }
]
}, {
id: 1,
description: "task 2",
subTasks: [
{ id: 0, description: "subtask 3" },
{ id: 1, description: "subtask 4" }
]
}, {
id: 2,
description: "task 3",
subTasks: [
{ id: 0, description: "sub task 5" },
{ id: 1, description: "sub task 6" }
]
}];
const accordion = document.querySelector("#accordion");
function displayTasks() {
const html = tasks.map((task, index) => {
let taskHTML = `
<div class="accordion-item" id="${index}">
<div class="todo-task">
<i class="far fa-circle"></i>
<input value="${task.description}" placeholder="Update your task" type="text">
<i class="fas fa-pen"></i>
<i class="fa fa-trash"></i>
<i class="fas fa-chevron-down"></i>
</div>
<div class="todo-sub-tasks">
`;
taskHTML += task.subTasks.map((subTask) => (`
<div class="todo-sub-task">
<i class="far fa-circle"></i>
<input value="${subTask.description}" placeholder="Update your sub task" type="text" />
<i class="fas fa-pen"></i>
<i class="fa fa-trash"></i>
</div>
`));
taskHTML += `
</div>
</div>
`;
return taskHTML;
});
accordion.insertAdjacentHTML("beforeend", html);
}
displayTasks();
<div id="accordion"></div>

Related

How do I add a conditional class to elements rendered with v-for?

I have a group of objects that are rendered with v-for.
<div v-for="answer in answers" :key="answer.id" class="field has-addons">
<p class="control">
<button #click="addNewAnswer" class="button is-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Добавить</span>
</button>
</p>
<p class="control">
<input
#input="answers[answerCount - 1].answer = $event.target.value"
type="text"
class="input"
>
</p>
</div>
These are the models I use to render
data() {
return {
answers: [
{
id: 0,
answer: '',
isEntered: false,
}
],
answerCount: 1,
}
},
And this is the code for adding a new line to enter the answer
addNewAnswer() {
const newAnswer = {
id: Date.now(),
answer: '',
isEntered: false,
};
this.answers.push(newAnswer);
this.answers[this.answerCount - 1].isEntered = true;
this.answerCount += 1;
},
I need to add an is-static class to the input after adding the entered response. How do I do this correctly?
If I add this to the input element
:class="{ 'is-static':answers[answerCount - 1].isEntered }"
Then the class will not be added to the past element as it should be, because I will be referring to an already new object of the array answer. I couldn't think of any alternatives
As per your problem statement, No need to manage a separate answerCount variable. As you are iterating answers array in the top most element. You can access it's object properties directly using dot(.) notation.
Suggestions :
Replace #input="answers[answerCount - 1].answer = $event.target.value" with v-model="answer.answer"
Pass answer.id as a param in addNewAnswer method and then update the property value of the passed object id.
Live Demo :
const app = Vue.createApp({
data() {
return {
answers: [
{
id: 0,
answer: '',
isEntered: false,
}
],
answerCount: 1,
}
},
methods: {
addNewAnswer(answerId) {
const newAnswer = {
id: Date.now(),
answer: '',
isEntered: false,
};
this.answers.push(newAnswer);
this.answers.forEach(obj => {
if (obj.id === answerId) {
obj.isEntered = true;
}
})
},
}
})
app.mount('#app')
.is-static {
color: red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<div v-for="answer in answers" :key="answer.id" class="field has-addons">
<p class="control" >
<button #click="addNewAnswer(answer.id)" class="button is-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Добавить</span>
</button>
</p>
<p class="control">
<input
v-model="answer.answer"
type="text"
class="input"
:class="{ 'is-static': answer.isEntered }"
>
</p>
</div>
</div>
Try with v-model:
const app = Vue.createApp({
data() {
return {
answers: [
{
id: 0,
answer: '',
isEntered: false,
}
],
answerCount: 1,
}
},
methods: {
addNewAnswer() {
const newAnswer = {
id: Date.now(),
answer: '',
isEntered: false,
};
this.answers.push(newAnswer);
this.answers[this.answerCount - 1].isEntered = true;
this.answerCount += 1;
},
}
})
app.mount('#demo')
.is-static {
color: red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="answer in answers" :key="answer.id" class="field has-addons">
<p class="control" >
<button #click="addNewAnswer" class="button is-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Добавить</span>
</button>
</p>
<p class="control">
<input
v-model="answer.answer"
type="text"
class="input"
:class="{ 'is-static':answer.isEntered }"
>
</p>
</div>
{{ answers }}
</div>

Two different objects in a nested v-for loop VueJS

I encounter a problem close but still different that my previous question.
Here is my Data object:
componants: {
element1: {
base: {
title: `Example`,
description: `Color Modifier`,
modifierClass: `Color ModifierClass`,
},
modifiers: {
block1: {
/* Modifier Class */
class: 'doc_button--green',
/* Description of the usage of the class */
description: 'Primary Button'
},
block2: {
class: 'doc_button--orange',
description: 'Secondary Button'
},
block3: {
class: 'doc_button--red',
description: 'Tertiary Button'
}
}
},
element2: {
base: {
title: `Example`,
description: `Size Modifier`,
modifierClass: `Size ModifierClass`,
},
modifiers: {
block1: {
class: 'doc_button--small',
description: 'Small Button'
},
block2: {
class: 'doc_button--big',
description: 'Big Button'
}
}
}
},
And how I use it for the nested loops:
<div>
<div v-for="(componant) in modifier" :key="componant">
<div v-for="(element, l) in componant" :key="l">
<h2 class="doc_title">
{{element.title}}
</h2>
<p class="doc_description">
{{element.description}}
</p>
<h3 class="doc_subtitle">
{{element.modifierClass}}
</h3>
</div>
<div v-for="(modifier) in componant" :key="modifier">
<ul class="doc_list doc_list--parameters" v-for="(block,k) in modifier" :key="k">
<li class="doc_list-text">
<p>{{block.class}}</p> : <p>{{block.description}}</p>
</li>
</ul>
<div class="doc_row">
<div class="doc_list-container">
<ul class="doc_list" v-for="(block,k) in modifier" :key="k">
<div class="doc_list-element" v-html="parentData.core.html"
:class="[parentData.core.class, `${block.class}`]">
</div>
<p class="doc_element-text"> {{block.class}} </p>
</ul>
</div>
</div>
<pre class="doc_pre">
<code class="language-html doc_code">
<div v-text="parentData.core.html">
</div>
</code>
</pre>
</div>
</div>
</div>
I should get only the second row of circles, with the colors, and don't understand why I get a first row of undefined ones.
There is one more layer of data inside modifiers:, comparent to base:.
But I added one more v-for loop, so doesn't it should work?
Thanks.
I eventually found the solution, so I put it here to solve my problem.
There was a mistake in the data organization, and once that was corrected, a little modification in the loops' structure.
No more block objects, but rather a modifiers array. It reduces the loop numbers and everything works.
componants: {
element1: {
base: {
title: `Example`,
description: `Color Modifier`,
modifierClass: `Color ModifierClass`,
},
modifiers: [{
/* Modifier Class */
class: 'doc_button--green',
/* Description of the usage of the class */
description: 'Primary Button'
},
{
class: 'doc_button--orange',
description: 'Secondary Button'
},
{
class: 'doc_button--red',
description: 'Tertiary Button'
}
],
},
element2: {
base: {
title: `Example`,
description: `Size Modifier`,
modifierClass: `Size ModifierClass`,
},
modifiers: [{
class: 'doc_button--small',
description: 'Small Button'
},
{
class: 'doc_button--big',
description: 'Big Button'
},
]
}
},
<template>
<div>
<div v-for="(componant) in modifier" :key="componant">
<!-- <div v-for="(element, l) in componant[0]" :key="l"> -->
<h2 class="doc_title">
{{componant.base.title}}
</h2>
<p class="doc_description">
{{componant.base.description}}
</p>
<h3 class="doc_subtitle">
{{componant.base.modifierClass}}
</h3>
<!-- </div> -->
<ul class="doc_list doc_list--parameters" v-for="(modifier) in componant.modifiers" :key="modifier">
<li class="doc_list-text">
<p>{{modifier.class}}</p> : <p>{{modifier.description}}</p>
</li>
</ul>
<div class="doc_row">
<div class="doc_list-container">
<ul class="doc_list" v-for="(modifier) in componant.modifiers" :key="modifier">
<div class="doc_list-element" v-html="parentData.core.html"
:class="[parentData.core.class, `${modifier.class}`]">
</div>
<p class="doc_element-text"> {{modifier.class}} </p>
</ul>
</div>
</div>
<pre class="doc_pre">
<code class="language-html doc_code">
<div v-text="parentData.core.html">
</div>
</code>
</pre>
</div>
</div>
</template>

Vue JS how to bind checkboxes?

How do we bind checkboxes value using vue js expression ? Below i have the data the filteroptions object and i manage to bind the value to the #html1 i have put below.
What I want to know is how can i bind the checked values to #html2 ? the selected values from the drpdown menu in #html1
As you can see on my filter by on html2 it is not dynamic.
Thank you.
html1
<div class="mh-filter-options">
<div class="filter-content">
<ul class="nav nav-pills">
<li class="dropdown" v-for="(opt, item) in filterOptions" :key="item">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true"
aria-expanded="false"> {{item}}</a>
<div class="dropdown-menu">
<a href="#" v-for="i in opt" :key="i.value">
<div class="inner">
<div class="checkbox">
<label class="filter-label-item">
<input type="checkbox" :data-prop="opt" :value=i.value />
<span class="cr"><i class="cr-icon fa fa-check"></i></span>
<span>{{i.value}} <span class="badge badge-dark">{{i.count}}</span></span>
</label>
</div>
</div>
</a>
</div>
</li>
</ul>
</div>
</div>
html 2
<div class="filtered-by">
<div class="filter-title">Filtered By: </div>
<div>
<div class="filter-item">
Year: <span> 2019 </span>
<i class="fas fas- fa-times"></i>
</div>
<div class="filter-item">
Model: <span> Escape </span>
<i class="fas fas- fa-times"></i>
</div>
<div class="filter-item">
Price range: <span> $5000-$10000 </span>
<i class="fas fas- fa-times"></i>
</div>
</div>
<div class="clear-all-filter">
Reset Filters
</div>
</div>
Method
setFilters() {
this.filterOptions = {
year: [{ value: '2019', checked: true, count: 10 }, { value: '2013', checked: false, count: 99 }, { value: '2017', checked: true, count: 10 }],
model: [{ value: 'Explorer', checked: true, count: 8 }, { value: 'Ram 1500', checked: false, count: 4 }, { value: 'Piloto', checked: false, count: 10 }],
priceRange: [{ value: '$10,000-$20,000', checked: false, count: 5 }, { value: '$20,000-$30,000', checked: false, count: 22 }, { value: '$30,000-$40,000', checked: false, count: 10 }],
mileage: [{ value: '1-1000', checked: false, count: 10 }, { value: '1001-2000', checked: false, count: 64 }, { value: '2001-3000', checked: false, count: 10 }],
bodyStyle: [{ value: 'Crew Cab Pickup', checked: false, count: 13 }, { value: 'Quad Cab Pickup', checked: false, count: 9 }, { value: 'Sport Utility', checked: true, count: 2 }]
}
},
You should bind checkboxes with v-model, which takes a boolean value. So in your case it might be <input type='checkbox' v-model='i.checked'>
I'm not sure I totally follow your code, particularly the setFilters() method, but the principal of Vue is that you encapsulate all your page state in a model, then html2 just looks at the model value that's 2-way bound to the checkbox. Does that make sense ?

grocery cart on Angular

I create add-to-cart app.
Want to click each item and add it to cart.
But firstly I need to click button 'add to cart' and increase its value with every click.
As I added ng-repeat, I don't know how to write a function that will be responsible for adding separate item.
angular.module('TransactionApp', [])
.controller('TransactionsCtrl', function($scope) {
$scope.title = 'Online-store';
$scope.itemsArray = [
{ price: 50, name: "Whey protein", img: 'img/item-1.png', quantity: 0},
{ price: 60, name: "Protein bar", img: 'img/item-2.png', quantity: 0 },
{ price: 35, name: "BCAA", img: 'img/item-3.png', quantity: 0 },
{ price: 50, name: "Whey protein", img: 'img/item-1.png', quantity: 0 },
{ price: 60, name: "Protein bar", img: 'img/item-2.png', quantity: 0 },
{ price: 80, name: "BCAA", img: 'img/item-3.png', quantity: 0 }
];
// $scope.count = 0;
$scope.addTo = function(){
}
});
here is html:
<h2 class="title">{{title}} <i class="em em-shopping_bags"></i></h2>
<div class="container">
<div class="row">
<div class="col-lg-4 col-md-2 col-sm-6">
<div class="card" style="width: 18rem;" ng-repeat='item in itemsArray'>
<img class="card-img-top" ng-src={{item.img}} alt="Card image cap">
<div class="card-body">
<h5 class="card-title"></h5>
<p class="card-text">{{item.name}}</p>
<p class="price">{{ item.price | currency }}</p>
<i class="em em-shopping_trolley"></i> Add to cart <span class="number">{{ item.quantity }}</span>
</p>
</div>
</div>
</div>
</div>
</div>
Pass the item to controller with addTo(item):
<a href="#" class="btn btn-warning" ng-click="addTo(item)">
<i class="em em-shopping_trolley"></i>
Add to cart
<span class="number">{{ item.quantity }}</span>
</a>
after your addTo accepts a parameter:
$scope.addTo = function(item){ // 'item' is a reference to an element in itemsArray
item.quantity++;
}
I believe each of your item in view has its own Add to Cart Button against it and I also believe you want to increase the quantity property of each of the item each time a user clicks the button against that item.
For that all you have to do is pass the item to addTo() method like :-
<i class="em em-shopping_trolley"></i> Add to cart <span class="number">{{ item.quantity }}</span>
and modify the method definition in controller
$scope.addTo = function(var item){
item.quantity++;
}

MeteorJs and Masonry

I am using MeteorJS (So great :) ), to develop simple app. I want to use masonry, so I am using sjors:meteor-masonry package.
When I use this code everything works fine:
var itemsData = [
{
title: 'First item',
description: 'Lorem 1',
price: 20
},
{
title: 'Secounde item',
description: 'Lorem 2',
price: 40
},
{
title: 'Third item',
description: 'Lorem 3',
price: 10
},
{
title: 'Fourth item',
description: 'Lorem 4',
price: 10
},
{
title: 'Five item',
description: 'sit 4',
price: 10
}
];
Template.itemsList.helpers({
items: itemsData
});
Template.itemsList.rendered = function() {
var container = document.querySelector('#main');
var msnry = new Masonry( container, {
// options
columnWidth: 200,
itemSelector: '.item'
});
};
But when I change part (code) for Template.itemsList.rendered to masonry don't work:
Template.itemsList.helpers({
items: function() {
return Items.find();
}
});
Any ideas ?
EDIT
myapp/lib/collections/items.js
Items = new Mongo.Collection('items');
And it is populated whit data from mongoshell. Data is ok, but masonry dont work.
EDIT 2
Masonry stops animating on screen resize and grid is not working as it should. No errors.
myapp/client/templates
<template name="itemSingle">
<div id="profile-widget" class="panel item">
<div class="panel-heading">
</div>
<div class="panel-body">
<div class="media">
<div class="media-body">
<h2 class="media-heading">{{title}}</h2>
{{description}}
</div>
</div>
</div>
<div class="panel-footer">
<div class="btn-group btn-group-justified">
<a class="btn btn-default" role="button"><i class="fa fa-eye"></i> 172</a>
<a class="btn btn-default" role="button"><i class="fa fa-comment"></i> 34</a>
<a class="btn btn-default highlight" role="button"><i class="fa fa-heart"></i> 210</a>
</div>
</div>
</div>
</template>
<template name="itemsList">
<div id="main">
{{#each items}}
{{> itemSingle}}
{{/each}}
</div>
</template>
I encountered the same problem, without never finding the perfect solution.
I tried to resolve with the setTimout() workaround :
Template.main.rendered = function() {
setTimeout(function() {
$('#container').masonry({
columnWidth: 35,
itemSelector: '.thumb',
gutter: 10,
isFitWidth: false
});
}, 1);
}

Categories

Resources