Vue.js - Getting data from multiple keys in an object - javascript

I'm trying to set something up in my app where I can select an option from a list and change the background of the app depending on what's selected.
Let's say I have a list like:
<li v-for="item in items">
<label class="radio">
<input type="radio" value="{{ item.name }}" v-model="itemSelection">
{{ item.name }}
</label>
</li>
items is an array that's stored in my store.js:
items: [
{name: 'item1', img: 'placehold.it/200x200-1'}
{name: 'item2', img: 'placehold.it/200x200-2'}
{name: 'item3', img: 'placehold.it/200x200-3'}
],
So when you select item1 I want to not only pull the name from the selection (which gets passed up to the parent component in itemSelection to display there) but also the img link to place that in css to change the background of the body. I'm not entirely sure how to go about this, as I'm pretty new to vue and this is basically something I'm building to help me learn!
Thanks!

You can do this by several ways e.g:
watch : {
itemSelection: function(val) { ... }
}
There is some examples. Check this fiddle

Related

Remove duplicate list item in v-for

I'm retrieving all groups from an axios request at the page load and I store that data inside an empty array inside a reactive object like this,
const groupHandler = reactive({
groups: [],
});
And when the user clicked one of his product and click on edit a form will appear like this,
Here you can see Group 1 has been repeated. And there is another reactive object to store that user's product's group id,
const productForm = reactive({
group: 2,
});
So when the user clicks on a product productForm.group will be filled with that product's group id. I want to prevent this been duplicated in my edit product form. I'm using the v-for directive to loop the groups array,
<li
v-for="group in groupHandler.groups"
:key="group.id"
:group-id="group.id" >
{{ group.name }}
</li>
So how to prevent this duplicate? In the v-for directive, I could use a condition like if group.id is not equal to productForm.group print group.name But I have no clue to do this. Really appreciate if somebody could help thanks.
You can use v-for in the template element, and then in the li element, you can use v-if condition to only render the group which doesn't have that id
<template v-for="group in groupHandler.groups">
<li
v-if="group.id !== productForm.group"
:key="group.id"
:group-id="group.id" >
{{ group.name }}
</li>
</template>
You can simply achieve this by using Array.filter() method in the v-for directive itself.
v-for="group in groupHandler.groups.filter(({ name }) => !uniqPropValue[name] && (uniqPropValue[name] = true))"
Live Demo :
new Vue({
el: '#app',
data: {
uniqPropValue: {},
groupHandler: {
groups: [{
id: 1,
name: 'Group 1'
}, {
id: 2,
name: 'Group 1'
}, {
id: 3,
name: 'Group 2'
}, {
id: 4,
name: 'Group 3'
}]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<li
v-for="group in groupHandler.groups.filter(({ name }) => !uniqPropValue[name] && (uniqPropValue[name] = true))"
:key="group.id"
:group-id="group.id">
{{ group.name }}
</li>
</div>

V-model doesn't update on checkbox list change

I have a list of checkboxes that are rendered with a v-for on a component. When checked, the checkboxes fill up an array with currently selected checkboxes. The problem comes when one or more of the checked checkboxes is removed - the v-model still counts the removed checkbox. How do I update the v-model as my array updates? I tried force re-rendering the whole component which solves the problem but it's not the solution I need.
<div v-for="player in players" :key="player.id">
<input
v-model="selectedPlayers"
:value="player.id"
type="checkbox"
:id="player.id"
/>
<label :for="player.id">
{{ player.name }}
</label>
</div>
Sandbox
Problem
Desired outcome
V-model won't remove data when the component is no longer rendered, you need to do that explicitly.
You could filter selectedPlayers from the #click handler so that it only includes ids that are in the new variable.
this.selectedPlayers = this.selectedPlayers.filter(
id => players2.find(
player => player.id === id
)
)
So from what I understand, you have this player2 array, that you need to compare against. Whatever is there in the players2 array needs to be there in the selectedPlayers array. To do this just use the use map array function to iterate over the players2 array to return only the ids of the players and then store them in the selected players array which being a reactive property will automatically patch the DOM. There's absolutely no need to re-render the component.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: "#app",
data() {
return {
selectedPlayers: [],
players: [
{name: "Arnold",id: "1"},
{name: "Rambo",id: 2},
{name: "Terminator",id: 3},
{name: "Titan",id: 4},
{name: "Odin",id: 5},
],
players2: [
{name: "Titan",id: 4},
{name: "Odin",id: 5},
],
};
},
methods: {
clicked() {
this.players = this.players2;
this.selectedPlayers = this.players2.map(p => p.id);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
If you check all checkboxes and press the button, the array still contains all elements
<div v-for="player in players" :key="player.id">
<input v-model="selectedPlayers" :key="players.length" :value="player.id" type="checkbox" :id="player.id" />
<label :for="player.id">
{{ player.name }}
</label>
</div>
{{ selectedPlayers }}
<button #click="clicked">Press me</button>
</div>

Generating HTML from JSON in Angular

Im trying to generate HTML code to Angular component template from JSON and im looking for best practice.
Actually Im fetching JSON and use *NgFor and *NgIf to achieve that
const blocks = [
{
id: '1',
type: 'header',
fields: [{
content : 'H1 Header'
}]
},
{
id: '2',
type: 'image',
fields: [{
src : 'https://i.pinimg.com/originals/5e/f6/83/5ef68313994aaf68e87d190de943f104.jpg',
title: 'My Image'
}]
},
]
<mat-accordion multi="true" *ngIf="blocks.length > 0">
<mat-expansion-panel *ngFor="let block of blocks">
<mat-expansion-panel-header>
{{ block.type }}
</mat-expansion-panel-header>
<ul>
<div *ngFor="let field of block.fields">
<div *ngIf="block.type == 'header'">
<h1>{{ field.content }}</h1>
</div>
<div *ngIf="block.type == 'image'">
<img style="width:100%;" src={{field.src}} />
</div>
<div *ngIf="block.type == 'paragraph'">
<p>{{field.content}}</p>
</div>
</div>
</ul>
</mat-expansion-panel>
</mat-accordion>
Is there better way to do that?
Instead if using several ngif, I would use ngswitch
this is not a good practice, in angular you have a template and you should bind properties and events to make your app dynamic. Keep in mind that angular sanitizes your input so for example you cannot render scripts and style in the way you are tryng to do so you won't be able to rendere an entire page. If you want to change a title dynamically you can do something like this:
// in your component
title: string;
// in template
<h1>{{title}}</h1>
same for image
// in component
src: string;
//in template
<img [src]="src">
Angular is made to build apps, but apps have a structured page, if not what kind of app would you build?

How to toggle a class onclick event to only a <li> element in a list of lis using Vuejs?

I am studying a bit of Vuejs to up my game in the JS ecosystem. I am building a to-do list. I have managed to render a list of lis. I was also able to checked them using onlick event. However, when I clicked on a li it applied the class to all of them.
I tried looping through the list but I don't think I am doing it right. If anybody kindly could set me in the right direction please.
HTML Code:
<div id="app">
<h1>{{ message }}</h1>
<ul>
<li v-bind:class="{ completed: isActive }" v-on:click="checked" v-for="todo in todos">
{{ todo.task }}
</li>
</ul>
</div>
JS Code:
var app = new Vue({
el: '#app',
data: {
message: 'List of things to do today',
todos: [
{ task: 'Have breakfast' },
{ task: 'Go to the gym' },
{ task: 'Study Vuejs' }
],
isActive: false
},
methods: {
checked: function(todos){
this.isActive = !this.isActive
}
}
});
I need to find a way to toggle the class in only the li that I click on...
Here is a a JSfiddle sample: https://jsfiddle.net/mercenariomode/pL3g6q14/2/
If you want the click to modify isActive of todo, then you should store it (and read from) there:
<li v-for="todo in todos"
:class="{ completed: todo.isActive }"
#click="$set(todo, 'isActive', !todo.isActive)">
{{ todo.task }}
</li>
data: {
message: 'List of things to do today',
todos: [
{ task: 'Have breakfast'},
{ task: 'Go to the gym'},
{ task: 'Study Vuejs'}
],
}
You don't need isActive in initial todo's state, that is covered by using $set.
Working fiddle: https://jsfiddle.net/iStyx/508bn4se/
Explanation about using $set:
Initially, todo object doesn't have isActive property, so adding or changing it afterwards will not be reactive. Of course, you can add isActive: false to all of your todo objects, but that is quite messy and sloppy. Another way is to use Vue.$set method (could be called from Vue instances by this.$set) which should be used to add new properties to reactive object (it also makes them reactive). Excerpt from the doc:
Adds a property to a reactive object, ensuring the new property is also reactive, so triggers view updates. This must be used to add new properties to reactive objects, as Vue cannot detect normal property additions
Useful link to read: Vue.js → Reactivity in Depth
Why don’t you make you todos an array of tuples or objects, like so:
todos: [
{ task: ‘Have Breakfast’, isActive: false},
// etc...
]
Then in your checked method, filter for the todo matching the name and only change that property:
checked: function(task) {
this.todos.filter((todo) => todo.task === task)
.forEach((match) => match.isActive = !match.isActive)
}
Which then allows you to add this to your template:
<li v-for="todo in todos" v-bind:class="{ completed: todo.isActive }" v-on:click="checked">
{{ todo.task }}
</li>
In the checked function you may not be able to modify the isActive property of the todo in place, in which case it may be a little more complicated, but I’m sure this gives you the gist.
Hope that helps.

VueJS does not rerender list even though array has changed & v-key is specified

I have two entities: categories and subcategories and two lists. When user selects a category I display subcategory list.
At first try it works fine, but when I change category I got old subcategory list.
Both categories and subcategories are in vuex store.
I tried to calculated subcategories next ways:
Computed property
When user selects a category I invoke a method, which filters subcategories by category_id and returns new list
Nothing worked for me. Here is an example of computed property:
subCategories() {
return this.getClothingSubCategories.filter((subCategory) => {
return subCategory.clothing_category_id === this.category.clothing_category_id
})
},
Where this.getClothingSubCategories is a vuex getter.
What's weird is that both in vue plugin (in chrome developer tools) and console I got the list updated, but in html the list is old.
Here is how I display the list:
<VuePerfectScrollbar class="categories"
v-if="showSubCategories"
v-once>
<ul>
<li v-for="subCategory in subCategories"
:key="subCategory.clothing_subcategory_id"
#click="selectSubCategory(subCategory)">
<div class="title">{{ subCategory.title }}</div>
<div class="arrow">></div>
</li>
</ul>
</VuePerfectScrollbar>
So, the subCategories property relies on category object, which I set simply:
selectCategory(category) {
this.category = category
},
When user selects a category.
I specified :key, tried different approaches but nothing worked, I always get the old list of subCategories.
What could be the reason?
update
Vuex getter:
export const getClothingSubCategories = (state) => {
return state.clothingSubCategories
}
Component data:
data() {
return {
gender: 'female',
category: null,
subCategory: null,
good: null,
filters: {
gender: 'female',
clothing_subcategory_id: null
}
}
},
Consider your declaration:
<VuePerfectScrollbar class="categories"
v-if="showSubCategories"
v-once>
<ul>
<li v-for="subCategory in subCategories"
:key="subCategory.clothing_subcategory_id"
#click="selectSubCategory(subCategory)">
<div class="title">{{ subCategory.title }}</div>
<div class="arrow">></div>
</li>
</ul>
</VuePerfectScrollbar>
The subCategories value, which is a computed property:
subCategories() {
return this.getClothingSubCategories.filter((subCategory) => {
return subCategory.clothing_category_id === this.category.clothing_category_id
})
},
is supposed to be updated whenever category changes.
As you reported, it does. As it should.
Your problem, though is:
v-once
Details:
Render the element and component once only. On subsequent re-renders,
the element/component and all its children will be treated as static
content and skipped. This can be used to optimize update performance.
That is, as soon as Vue renders the subcategory collection the first time it "freezes" it. Basically, because of v-once it will never be updated again.
Solution: remove v-once.
I suggest you remove from other places in your code as well. Any element you want to be updated should not have v-once.

Categories

Resources