How to bind array in DOM using Vue.js - javascript

I am working on an application where I bind the list of data into DOM using Vue.js. But it is not working I have used v-for, v-list, and v-repeat but don't get it working. Here is my code both for the template and the script.
<div class="weather-info" v-if="weather!=undefined">
<div v-repeat="item in weather">
<div class="location-box">
<div class="location">{{item.day}}
</div>
<!-- <div class="date">{{ todaysDate() }}</div> -->
</div>
<div class="weather-box">
<div class="temp">{{ Math.round(item.temprature) }}°c</div>
<div class="weather">{{Math.round(item.windSpeed)}}</div>
<div class="icon">
<img src="{{iconUrl}}.png"/>
</div>
</div>
</div>
</div>
Here is the code of the Script
export default {
data() {
return {
url_base: "https://localhost:7197/api/weather/",
weather: undefined,
};
},
methods : {
async fetchWeather(e) {
if (e.key == "Enter") {
let response = await axios.get(`${this.url_base}forecast?city=${this.query}`);
this.setResults(response.data);
}
},
setResults(res) {
console.log(res)
if(res.isSuccessful === true){
this.weather = res.response;
}else{
// error message
}
},
},
};
The JSON i received in res is show below.

Please try to use v-for instead of v-repeat, you can replace it as follow:
<div v-for="(item, key) in weather" :key="key">
{{ item }}
...
</div>

Your code should work fine, Just wondering from where you are invoking the fetchWeather method. I just created a working demo below. Please have a look and try to find the root cause of the issue.
Just for a demo purpose I am using mock data but you can replace that with an API call.
new Vue({
el: '#app',
data: {
weather: undefined
},
mounted() {
this.weather = [
{ day: 'Day 1' },
{ day: 'Day 2' },
{ day: 'Day 3' },
{ day: 'Day 4' }
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="weather-info" v-if="weather && weather.length">
<div v-for="(item, index) in weather" :key="index">
{{ item.day }}
</div>
</div>
</div>

Related

Pass selected option <b-form-select> to axis post from child to parent

I created a form using Vue.js. All my input fields are getting passed but I can't get the value in the <b-form-select> to pass. How do I pass the value of the selected option in <b-form-select> to parent?
child:
<label>
for="source"
class="inline-3-columns--camp-wizard"
>
<span class="title__field">Network</span>
<b-form-select
id="source"
v-model="networkAudience.source"
data-vv-as="source"
name="source"
value-field="value"
text-field="label"
:options="sources"
/>
<span
class="title__field"
data-vv-as="source"
>
{{ networkAudience.source }}
<input
v-model="networkAudience.source"
name="source"
type="hidden"
data-vv-as="source"
>
</span>
</label>
Here is my script:
data()
{
return {
selectedclientId: null,
networkAudience: {
source: '',
name: '',
},
sources: [
{value: "Group1", label:"Group1"},
{value: "Group2", label:"Group2"}],
};
},
methods: {
async submit()
{
axios.post(projConfig.apiRoot + `/s/audiences/${this.audienceId}/network-audiences/`, this.networkAudience)
.then(response =>
{
this.networkAudiences = response.data;
console.log(response);
this.$emit('on-success');
})
.catch(error =>
{
console.error(error);
});
},
}
Parent:
<transition
name="fade"
#after-leave="VueEvent.$emit('close-preview-panel')"
>
<preview-panel
v-if="showAddNetwork"
:content-classes="'panel__content--camp-creation mw-1150'"
:title="'Add Network Audience'"
#close-preview="showAddNetwork = false"
>
<template #content>
<AddNetworkAudience
:audience-id="currentAudId"
#on-cancel="showAddNetwork = false"
#on-success="handleAddNetworkAudienceSuccess"
/>
</template>
</preview-panel>
</transition>
I've tried changing my submit method but it isn't working.
I get the following errors after submitting the form:
error in network
enter in console
I am not sure what issue you are facing but I created a snippet and it is working fine as per the expectation. Can you please have a look in the below code snippet and let me know what issue you are facing.
new Vue({
el: '#app',
data: {
networkAudience: {
source: '',
name: '',
},
sources: [
{
value: "Group1", label:"Group1"
}, {
value: "Group2", label:"Group2"
}
]
},
methods: {
submitForm() {
console.log(this.networkAudience);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css"/>
<div id="app">
<b-form-select v-model="networkAudience.source"
:options="sources"
value-field="value"
text-field="label"
></b-form-select>
<div>Selected Option: <strong>{{ networkAudience.source }}</strong></div>
<button type="submit" #click="submitForm()">Submit</button>
</div>

Vue.js and input's value that is never displayed

I'm making a simple shopping list app in Vue.js and I was curious if there's a standard way of doing what I need to do. I have a list of items with add and delete buttons:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
newItem: ''
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" v-model="newItem" placeholder="Item name">
<button #click="addItem">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem() {
const item = this.newItem.trim();
if (item === '') return;
this.items.push(item);
this.newItem = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
It works just as it should, but I'm not sure about using data entry that is never displayed anywhere. There's also another approach with $refs:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" placeholder="Item name" ref="newItem">
<button #click="addItem($refs.newItem.value)">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem(item) {
item = item.trim();
if (item === '') return;
this.items.push(item);
this.$refs.newItem.value = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
Instead of using separate data entry and v-model, I'm using $refs directly. Is any of these approaches more widely accepted in Vue.js community or guidelines? Or perhaps there's even more popular way?
I just wanted to share my views here. Personally I like to use v-model as it provides few added benefits like:
We can use .trim modifier with v-model which automatically trims whitespace from user input like:
<input v-model.trim="msg">
This way you don't need to write additional code to trim text like item = item.trim();. Few lines of code saved here.
Using this.newItem = '' we can easily clear out the previously entered text after button click using v-model reactivity feature. So, again less line of code instead of doing this.$refs.newItem.value = '';
Another advantage of using v-model is that, instead of doing
<button #click="addItem($refs.newItem.value)">
You can simply call the function like:
<button #click="addItem">
So, you can see these are the few benefits of using a simple v-model, which is mostly related to the developer experience (DX) point of view.
Working Demo:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
newItem: ''
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" placeholder="Item name" v-model.trim="newItem">
<button #click="addItem">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem() {
if (this.newItem === '') return;
this.items.push(this.newItem);
this.newItem = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="app">
</div>

How create multiple store filter in VueJS?

i'm new to VueJS and this is my first big project. In this project I have 2 filters for my elements, one is a search bar and the other is based on checkboxs. As you can see in my code, I have a computed propertie with 2 filters, the second filter is supposed to display the products with the same brand bu it doesn't work and I don't know why. If anybody have an idea it would be cool ;)
<div class="col-xs-12">
<input type="text" v-model="search" placeholder="Rechercher" class="search">
</div>
<div class="row">
+ filtres
<transition name="fade">
<div v-if="show" class="filter__container">
<ul>
<li>
<input type="checkbox" v-model="getBrand" v-on:click="filtered" v-bind:value="brand" />
<label for="apple">Apple</label>
</li>
<li>
<input type="checkbox" v-model="getBrand" v-on:click="filtered" v-bind:value="brand" />
<label for="samsung">Samsung</label>
</li>
</ul>
</div>
</transition>
</div>
<div class="row between-xs no-margin grid">
<div v-for="product in filtered" class="containers no-padding no-margin item">
<router-link to="/items">
<div #click="getProduct(product)">
<img :src="product.img" :alt="product.alt" class="img">
<div class="content">
<h3>{{ product.title }}</h3>
<p>{{ product.description }}</p>
<p>{{ product.brand }}</p>
</div>
</div>
</router-link>
</div>
</div>
<script>
import app from '../App'
import {mapActions} from 'vuex';
export default {
components: {
app
},
data() {
return {
show: false,
search: '',
brand: ['apple','samsung'],
getBrand:[],
}
},
computed: {
products() {
return this.$store.state.products;
},
filtered: function () {
return this.products.filter((product) => {
return product.title.toLowerCase().match(this.search.toLowerCase())
return product.brand.match(this.getBrand.includes(brand))
})
},
},
methods: {
...mapActions([
'currentProduct',
]),
getProduct(product) {
this.currentProduct(product);
}
},
};
</script>
export const store = new Vuex.Store({
state: {
products: [{
img: '../icons/img.png',
alt: 'logo',
title: 'Title',
description: 'Description',
brand: 'Apple'
},
{
img: '../icons/img.png',
alt: 'logo',
title: 'Title2',
description: 'Description2',
brand: 'Apple'
},
{
img: '../icons/img.png',
alt: 'logo',
title: 'Title3',
description: 'Description3'
brand: 'Samsung'
},
{
img: '../icons/img.png',
alt: 'logo',
title: 'Title4',
description: 'Description4'
brand: 'Samsung'
}
],
currentProduct: {},
},
getters: {
getProduct: state => state.currentProduct,
},
mutations: {
CURRENT_PRODUCT: (state, product) => {
state.currentProduct = product;
}
},
actions: {
currentProduct: (context, product) => {
context.commit('CURRENT_PRODUCT', product);
}
}
})
You can't return twice from the same function. Either chain the conditions using && or chain in another call to filter.
You're also misusing match. The argument needs to be a RegExp or something that can safely be converted to a RegExp. You can see the problem in the console if you type in a character like [ that has a special meaning in a RegExp. Perhaps you meant includes?
The second condition also seems to be incorrect. Not entirely clear what that combination of match and includes is trying to achieve but I think you're looking for something like this:
return this.products.filter((product) => {
return product.title.toLowerCase().includes(this.search.toLowerCase()) &&
this.getBrand.includes(product.brand)
})
It is worth noting that while both conditions are using a method called includes they are two different methods, one on a string and the other on an array.
This also seems to be wrong:
v-bind:value="brand"
brand is an array of strings and you aren't looping over them with a v-for. Change it to value="Apple" and value="Samsung" instead, ensuring the case matches the data.
I also suggest removing v-on:click="filtered". Not sure what that's trying to do but it seems to be treating a computed property as a click listener.

How render component in v-for by button from parent

How i can render v-if component by button(button in parent) click inside v-for loop? and should render only in that item where clicked
<div v-for="item in items">
<button #click >Show child<button>
<div>{{item.name}}</div>
<child v-if="this button clicked" :item="item"><child>
<div>
You have to store info about state of every item (if it was clicked) in your data. Then, when you click on button you should update clicked property for particular item. Finally if item.clicked is set on true you will show your child component (or any other html).
<template>
<div>
<div v-for="item in items" :key="item.id">
<button #click="item.clicked = true" >Show child</button>
{{item.name}}
<div v-if="item.clicked">Item child</div>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data: function() {
return {
items: [
{
id: 1,
name: 'test1',
clicked: false
},
{
id: 2,
name: 'test2',
clicked: false
},
{
id: 3,
name: 'test3',
clicked: false
}
]
}
}
}
</script>
Plain and simple you just have to set some flag for latter v-if:
<div id="app">
<div v-for="item in items">
<button #click="$set(item, 'shown', true)">Show child</button>
<div>{{ item.name }}</div>
<div v-if="item.shown">Child component</div>
</div>
</div>
Here, $set() is used because initial item could lack shown field, so setting it directly with item.shown=true won't be reactive.
You can also hide button after click:
<button #click="$set(item, 'shown', true)" v-if="!item.shown">Show child</button>
To toggle visibility you just have to do it like this:
<button #click="$set(item, 'shown', !item.shown)">
{{ item.shown ? 'Hide' : 'Show' }} child
</button>
JSFiddle
You can take advantage of an item... index available in v-for directive (e.g. v-for="(item, i) in items"), to bind it (index of the item) to the function which shows an item by changing it's property:
Update: Initial answer has been deleted after requirements refinement.
Since you prefer to keep from mutation of items, you can wrap them in Map object (as keys) and keep visibility settings separately as Map values. Unfortunately, as far as I know, for the time being Vue.js does not support reactivity for Map objects, that's why I have to trigger rerendering manually by using forceUpdate:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('child', {
template: '<p>Visible child</p>'
})
new Vue({
el: "#demo",
template: `
<div>
<div v-for="item in items">
<button #click="toggleChild(item)">Toggle child</button>
<div>{{item.name}}</div>
<child v-if="isVisible(item)" :item="item"></child>
</div>
</div>
`,
data () {
return {
itemsMap: new Map(
[
{ name: 'test1' },
{ name: 'test2' }
].map(item => [item, { visible: false }])
)
};
},
methods: {
toggleChild(item) {
this.itemsMap.set(item, { visible: !this.itemsMap.get(item).visible });
this.$forceUpdate();
},
isVisible(item) {
return this.itemsMap.get(item).visible;
}
},
computed: {
items: function() {
return Array.from(this.itemsMap.keys());
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo"></div>

Leveraging the v-for loop for places outside the v-for

While looping on array of sales , i need to capture the object of which salesPerson === "bar" and print its sellValue outside the v-for block.
Of course i can't access the array in hard-coded way. i have to assume that the position of the object i'm looking for is random.
also, i can't add another loop on top of the one loop that already exist here. (v-for is a loop obviously).
i need way to do achieve it.
here is an example component:
<template>
<div id="app">
<!-- i need to print here the sellValue of 'bar' -->
<p v-for="(sell,index) in sales"
:key="index">{{sell.sellValue}}</p>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
sales: [
{
salesPerson: 'foo',
sellValue: 1
},
{
salesPerson: 'bar',
sellValue: 2
}
]
}
}
}
</script>
You can try using a custom HTML tag (not registered as a component for Vue), it's quite "ugly" but that is the only solution I could think of (beware of Vue's warnings if not disabled) :
<template>
<div id="app">
<uglyTag v-for="(sell,index) in sales" :key="index">
{{sell[ sell.findIndex( e=>e.salesPerson==="bar" ) ].sellValue}}
<p>{{ sell.sellValue }}</p>
</uglyTag>
</div>
</template>
Another solution would be to rethink the construction of your data so you could have (but still needs the uglyTag method) :
data(){
return {
salesTitle: 2,
sales: [
{
salesPerson: 'foo',
sellValue: 1
},
{
salesPerson: 'bar',
sellValue: 2
}
]
}
}
and
<template>
<div id="app">
<uglyTag v-for="(sell,index) in sales" :key="index">
{{ salesTitle }}
<p>{{ sell.sellValue }}</p>
</uglyTag>
</div>
</template>
Perhaps I didn't understand the question correctly, but you are still in the same scope of your component. Why don't you add a getter for the value you are interested in and display it where you want.
Vue.component('my-template', {
template: ' <div id="app">\
<!-- i need to print here the sellValue of \'bar\' -->\
<p>{{ saleValue }}</p>\
<p v-for="(sell,index) in sales" :key="index">{{sell.sellValue}}</p>\
</div>',
data: function() {
return {
sales: [{
salesPerson: 'foo',
sellValue: 1
}, {
salesPerson: 'bar',
sellValue: 2
}]
}
},
computed: {
saleValue: function() {
return this.sales.filter(function(val) {
return val.salesPerson === 'bar';
})[0].sellValue;
}
}
});
var vm = new Vue({
el: '#vm',
data: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="vm">
<my-template></my-template>
</div>

Categories

Resources