Getting a single array from an array of object - vue.js

I am trying to iterate through the array of object called sources at change event of my select input then pushing this single array to a new element conditionally
<template>
<div class="sourceselection">
<div class="jumbotron">
<h2><span class="glyphicon glyphicon-list"></span> News List</h2>
<h4>Select News Source</h4>
<select class="form-control" #change="sourceChanged">
<option v-bind:value="source.id" v-for="source in sources" :key="source.id">{{source.name}}</option>
</select>
</div>
<div v-if="source">
<h6 >{{source.description}}</h6>
<a v-bind:href="source.url"></a>
</div>
</div>
</template>
<script>
export default {
name: 'SourceSelection',
data () {
return {
sources: [],
source: '',
}
},
methods: {
sourceChanged (event) {
for (var i = 0; i < this.sources.length; i++){
if (this.sources[i].id == event.target.value){
this.source = this.sources[i];
}
}
}
},
created : function () {
this.$http.get('https://newsapi.org/v2/sources?language=en&apiKey=cf0866b5aaef42e995f9db37bb3f7ef4')
.then(response => {
this.sources = response.data.sources;
})
.catch(error => console.log(error));
}
}
</script>
i expect to pull the description into my h6 if the source exist

At first create a state in data and name it sourceId,
then bind it to select <select v-model="sourceId" class="form-control">.
Now we need a computed filed which returns the source return this.sources.find(s => s.id === this.sourceId)
<template>
<div class="sourceselection">
<div class="jumbotron">
<h2><span class="glyphicon glyphicon-list"></span> News List</h2>
<h4>Select News Source</h4>
<select v-model="sourceId" class="form-control">
<option v-for="source in sources" :value="source.id" :key="source.id">
{{ source.name }}
</option>
</select>
</div>
<div v-if="source">
<h6 >{{ source.description }}</h6>
<a v-bind:href="source.url"></a>
</div>
</div>
</template>
<script>
export default {
name: 'SourceSelection',
data () {
return {
sources: [],
sourceId: ''
}
},
computed: {
source () {
return this.sources.find(s => s.id === this.sourceId)
}
},
created() {
this.$http.get('https://newsapi.org/v2/sources?language=en&apiKey=cf0866b5aaef42e995f9db37bb3f7ef4')
.then(response => {
this.sources = response.data.sources;
})
.catch(error => console.log(error));
}
}
</script>

Instead of using an event handler you can bind the selected value directly to your source data.
here is a demo:
Vue.config.devtools = false
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
sources: [{
id: '1',
description: 'one desc',
name: 'one'
}, {
id: '2',
description: 'two desc',
name: 'two'
}, ],
source: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select class="form-control" v-model="source">
<option v-bind:value="source" v-for="source in sources" :key="source.id">{{source.name}}</option>
</select>
<h6>{{ source.description }}</h6>
</div>

Related

Vue is not update DOM when object property changes in array of objects

I have a const array of objects which set to the data property on created(), I am trying to update the object properties as the user entered the form, the Console print shows the value updated, but the DOM is changing. DOM is updated when the key value is changed too but I do not want to change the key value
Data Array:
const invoices = [
{
number: "BBRFL",
client: "Ellison Daugherty",
amount: 8800,
status: "Paid",
},
{
number: "TYUBK",
client: "Myrna Vinson",
amount: 4097,
status: "Paid",
},
{
number: "IRUBR",
client: "Mcmillan Warner",
amount: 6466,
status: "Draft",
},
];
Here is the app.
const app = new Vue({
el: "#app",
components: {
"create-invoice": CreateInvoice,
"invoice-item": InvoiceItem,
},
data() {
return {
invoices: invoices,
status: null,
};
},
created: function () {
const invoices = localStorage.getItem("invoices");
if (invoices) {
this.invoices = JSON.parse(invoices);
}
},
computed: {
count: function () {
return this.filteredInvoices.length;
},
filteredInvoices: function () {
if (this.status) {
return this.invoices.filter((invoice) => invoice.status == this.status);
}
return this.invoices;
},
},
methods: {
saveInvoice(invoice) {
this.invoices.push(invoice);
},
invoiceUpdate(invoice) {
const index = this.invoices.findIndex((i) => i.number === invoice.number);
// this.invoices.splice(index, 1, invoice);
Vue.set(this.invoices[index], "client", invoice.client);
Vue.set(this.invoices[index], "amount", invoice.amount);
Vue.set(this.invoices[index], "status", invoice.status);
},
invoiceDelete(number) {
const index = this.invoices.findIndex((i) => i.number === number);
this.invoices.splice(index, 1);
},
},
watch: {
invoices: {
deep: true,
handler: function (invoices) {
localStorage.setItem("invoices", JSON.stringify(invoices));
},
},
},
template: `
<div>
<div class="row">
<div class="col-md-4 text-left">
<h1>Invoices</h1>
<p class="text-dark">There are {{count}} Total Invoices</p>
</div>
<div class="col-md-4 text-start" style="margin-top: 10px">
<label for="status">Filter</label>
<select name="filter" id="status" class="form-control" v-model="status">
<option value="Draft">Draft</option>
<option value="Paid">Paid</option>
<option value="Pending">Pending</option>
</select>
</div>
<div class="col-md-4 text-right" style="margin-top: 10px">
<button class="btn btn-primary mt-4" id="createInvoice">
New Invoice
</button>
</div>
</div>
<div class="col-md-12">
<div class="row mt-2 mb-2">
<div
class="invoice col-md-12"
>
<invoice-item
v-for="(n, index) in filteredInvoices"
:key="n.number"
:initial-client="n.client"
:initial-number="n.number"
:initial-amount="n.amount"
:initial-status="n.status"
#update-invoice="invoiceUpdate"
#delete-invoice="invoiceDelete"
></invoice-item>
</div>
<div class="text-center" v-if="filteredInvoices.length === 0"><p>No invoice found</p></div>
</div>
</div>
<create-invoice #saveInvoice="saveInvoice" ></create-invoice>
</div>
</div>
`,
});
I had tried, this.$set, Vue.set, Direct assigning to property, assingment using splice function, but none of working. It only works with the change of value of key in for loop. Which I do not want to update.
Jsfiddle
Any Help? Thanks in advance.

vuejs vue-multiselect can't display selected item pass value array of selected objects

I have a field component that is utilized by both edit or create component. in field component i used Vue-multiselect 2.1.4 plugin to show dropdown with multi-select options here is my code
<template>
<div class="col-md-12">
<div class="alert alert-danger alert-dismissible" v-if="errors.length && displayErrors">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4><i class="icon fa fa-ban"></i> Please correct the following error(s):</h4>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<div class="form-group">
<label>Title<span class='red'>*</span></label>
<input type="text" v-model="fields.title" class="form-control">
</div>
<div class="form-group">
<label>Description<span class='red'>*</span></label>
<input type="text" v-model="fields.description" class="form-control">
</div>
<div class="form-group">
<label>Categories<span class='red'>*</span></label>
<multiselect
v-model="fields.category"
:options="categories"
:value="prevGameCategory"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick some"
label="name"
track-by="id">
</multiselect>
</div>
<div class="form-group">
<label>Game Grade Levels<span class='red'>*</span></label>
<multiselect
v-model="fields.level"
:options="gameLevel"
:value="prevGameLevel"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick some"
label="name"
track-by="id">
</multiselect>
</div>
</div>
And here is my script code
<script type="text/javascript">
import router from '../../router';
import Multiselect from 'vue-multiselect'
import ClassicEditor from '#ckeditor/ckeditor5-build-classic'
import VueCkeditor from 'vue-ckeditor5'
export default {
props: [
'categories',
'gameLevel'
],
mounted() {
if (this.$route.params.id) {
this.isEdit = true
this.getGameById(this.$route.params.id)
}
},
data () {
return {
data: {},
prevGameLevel: [],
prevGameCategory: [],
baseUrl: window.BreakOut.baseUrl,
isEdit: false,
errors: [],
displayErrors: false,
image: '',
fields: {
title: null,
description: null,
category: [],
},
editors: {
classic: ClassicEditor
}
}
},
methods: {
async getGameById(game_id) {
let urlArr = _.split(window.BreakOut.routes.admin_game_edit, '/', 3)
let end_point = _.join(urlArr, '/')+'/'+game_id
let url = this.baseUrl+'/'+end_point
await axios.get(url).then((response) => {
this.data = response.data
this.fields.title = this.data.title
this.fields.description = this.data.description
if (_.isArray(this.data.game_category)) {
if (this.data.game_category.length > 0) {
_.forEach(this.data.game_category, (value, index) => {
this.prevGameCategory.push(_.pick(value.category, ['id', 'name']))
})
}
}
if (_.isArray(this.data.game_grade_level)) {
if (this.data.game_grade_level.length > 0) {
_.forEach(this.data.game_grade_level, (value, index) => {
this.prevGameLevel.push(_.pick(value.grade_level, ['id', 'name']))
})
}
}
// here i have get previous selected objects
console.log(this.prevGameLevel)
console.log(this.prevGameCategory)
}).catch((error) => {
this.$awn.alert(error)
})
},
}
}
In my code what am missing i almost follow plugin doc but the selected items were not displayed
You should not use both v-model and :value simultaneously. You can do:
<multiselect
v-model="fields.category"
:options="categories"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick some"
label="name"
track-by="id">
</multiselect>
and set this.fields value at the end of getting data function:
await axios.get(url).then((response) => {
this.data = response.data
this.fields.title = this.data.title
this.fields.description = this.data.description
if (_.isArray(this.data.game_category)) {
if (this.data.game_category.length > 0) {
_.forEach(this.data.game_category, (value, index) => {
this.prevGameCategory.push(_.pick(value.category, ['id', 'name']))
})
}
}
if (_.isArray(this.data.game_grade_level)) {
if (this.data.game_grade_level.length > 0) {
_.forEach(this.data.game_grade_level, (value, index) => {
this.prevGameLevel.push(_.pick(value.grade_level, ['id', 'name']))
})
}
}
// here i have get previous selected objects
console.log(this.prevGameLevel)
console.log(this.prevGameCategory)
this.fields = {
...this.fields,
category: this.prevGameCategory,
level: this.prevGameLevel
}
}).catch((error) => {
this.$awn.alert(error)
})

Multiple Dynamic Checkboxes with input groups in vue js

I am trying to make a form which contains an input group section, in this group, there are one select box and multiple checkboxes. Checkboxes are populated based on the select box selection. There is also an add and remove button to generate and remove input group. The select box is used with v-model to filtered the checkboxes. But when I generate a new input group and make changes, all checkboxes are changed.
I want them to be isolated. How can I achieve?
Here is my Template.
<template>
<form #submit.prevent="onSubmit">
<div v-for="(variationProduct, index) in variationProducts" :key="variationProduct.id">
<div class="from-group mb-4">
<label class="col-form-label"><b>Categories :</b></label>
<select class="form-control mr-2" ref="categories" v-model="category">
<option value="0">Please select category...</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
<div v-if="hasError">
<validation-errors v-if="errors['categories.'+index]" :errors="errors">
{{ errors['categories.'+index][0] }}
</validation-errors>
</div>
</div>
<div class="form-group mb-4">
<label class="col-form-lablel"><b>Variation Products :</b></label>
<div class="row">
<div v-for="filteredVariationProduct in filteredVariationProducts" :key="filteredVariationProduct.id">
<div class="col-12">
<input :id="filteredVariationProduct.id" :value="filteredVariationProduct.id" type="checkbox" ref="variationProducts">
<label :for="filteredVariationProduct.id">{{ filteredVariationProduct.name }}</label>
</div>
</div>
</div>
<div v-if="hasError">
<validation-errors v-if="errors['variationProducts.'+index]" :errors="errors">
{{ errors['variationProducts.'+index][0] }}
</validation-errors>
</div>
</div>
<div class="float-right">
<button #click.prevent="add" class="btn btn-success">Add</button>
<button #click.prevent="remove(index)" class="btn btn-danger">Remove</button>
</div>
<br>
<br>
<hr>
</div>
<input type="submit" class="btn btn-primary" value="Submit">
</form>
</template>
Here is my JS.
<script>
import ValidationErrors from './ValidationErrors.vue';
export default {
components: {
'validation-errors': ValidationErrors,
},
data () {
return {
variationProducts: [],
categories: [
{ id: 1, name: 'Technology'},
{ id: 2, name: 'Business'},
{ id: 3, name: 'Marketing'},
{ id: 4, name: 'Development'},
{ id: 5, name: 'Engineering'},
],
category: 0,
activeVariationProducts: [],
count: 1,
errors: {},
hasError: false,
}
},
methods: {
add: function() {
this.count++;
this.errors = {};
this.hasError = false;
this.variationProducts.push({ id: this.count });
},
remove: function (index) {
if ( this.variationProducts.length > 0 && index > -1) {
this.variationProducts.splice(index, 1);
} else {
alert('Must have at least one input!')
}
},
onSubmit: function() {
console.log(this.$refs.variationProducts.value);
},
generateVariationProducts: function(num) {
for(let i = 1; i <= num; i++) {
let activeVariationProduct = {
id: i,
name: 'Product '+ i,
category_id: i
};
this.activeVariationProducts.push(activeVariationProduct);
}
},
},
computed : {
filteredVariationProducts: function () {
let categoryId = parseInt(this.category);
if (categoryId !== 0) {
let filteredVariationProducts = this.activeVariationProducts.filter((variationProduct) => {
return variationProduct.category_id === categoryId;
});
return filteredVariationProducts;
} else {
let filteredVariationProducts = this.activeVariationProducts;
return filteredVariationProducts;
}
}
},
created () {
this.variationProducts.push({ id: this.count });
this.generateVariationProducts(10);
},
}
</script>
Here is a sample code. This code Shows how you can use multiple Checkboxes that is generated dynamically and how to make them isolated -
new Vue({
el : "#app",
data : {
Items : ["One", "Two", "Three"],
newCheckbox : "",
SelectedItems : {
'One' : "",
'Two' : "",
'Three' : "",
},
},
methods:{
add:function(){
Vue.set(this.SelectedItems, this.newCheckbox, false);
this.Items.push(this.newCheckbox);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js" type="text/javascript"></script>
<div id="app">
<div>
<label v-for="(item, index) in Items">
<input type="checkbox" v-bind:key="index" v-model="SelectedItems[item]"> {{ item }}
</label>
</div>
<div>
<input type="text" v-model="newCheckbox">
<button #click="add">Add</button>
</div>
<div>
Output : {{ SelectedItems }}
</div>
</div>
You can dynamically add new checkbox and still they are isolated

Is it possible to sync Vuejs components displayed multiple times on the same page?

I have a web page that displays items. For each items there is a button (vuejs component) which allow user to toggle (add/remove) this item to his collection.
Here is the component:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': dAdded, 'btn-primary': !dAdded}">{{ dText }}</button>
</template>
<script>
export default {
props: {
added: Boolean,
text: String,
id: Number,
},
data() {
return {
dAdded: this.added,
dText: this.text,
dId: this.id
}
},
watch: {
added: function(newVal, oldVal) { // watch it
this.dAdded = this.added
},
text: function(newVal, oldVal) { // watch it
this.dText = this.text
},
id: function(newVal, oldVal) { // watch it
this.dId = this.id
}
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.dId
}))
.then(response => {
this.dText = response.data.message
let success = response.data.success
this.dText = response.data.new_text
if (success) {
this.dAdded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.dId);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
}
}
</script>
For each item, the user can also open a modal, loaded by a click on this link:
<a href="#" data-toggle="modal" data-target="#popModal" #click="id = {{$pop->id}}">
<figure>
<img class="card-img-top" src="{{ URL::asset($pop->img_path) }}" alt="Card image cap">
</figure>
</a>
The modal is also a Vuejs component:
<template>
<section id="pop" class="h-100">
<div class="card">
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-1 flex-column others d-none d-xl-block">
<div class="row flex-column h-100">
<div v-for="other_pop in pop.other_pops" class="col">
<a :href="route('frontend.pop.collection.detail', {collection: pop.collection.slug, pop: other_pop.slug})">
<img :src="other_pop.img_path" :alt="'{{ other_pop.name }}'" class="img-fluid">
</a>
</div>
<div class="col active order-3">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
</div>
</div>
<div class="col-12 col-lg-6 content text-center">
<div class="row">
<div class="col-12">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
<div class="col-6 text-right">
<toggle-pop :id="pop.id" :added="pop.in_user_collection" :text="pop.in_user_collection ? 'Supprimer' : 'Ajouter'"></toggle-pop>
</div>
<div class="col-6 text-left">
<!-- <btnaddpopwhishlist :pop_id="propid" :added="pop.in_user_whishlist" :text="pop.in_user_whishlist ? 'Supprimer' : 'Ajouter'"></btnaddpopwhishlist> -->
</div>
</div>
</div>
<div class="col-12 col-lg-5 infos">
<div class="header">
<h1 class="h-100">{{ pop.name }}</h1>
</div>
<div class="card yellow">
<div class="card p-0">
<div class="container-fluid">
<div class="row">
<div class="col-3 py-2">
</div>
<div class="col-6 py-2 bg-lightgray">
<h4>Collection:</h4>
<h3>{{ pop.collection ? pop.collection.name : '' }}</h3>
</div>
<div class="col-3 py-2 bg-lightgray text-center">
<a :href="route('frontend.index') + 'collections/' + pop.collection.slug" class="btn-round right white"></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
id: Number
},
data() {
return {
pop: {
collection: {
}
}
}
},
ready: function() {
if (this.propid != -1)
this.fetchData()
},
watch: {
id: function(newVal, oldVal) { // watch it
// console.log('Prop changed: ', newVal, ' | was: ', oldVal)
this.fetchData()
}
},
computed: {
imgSrc: function() {
if (this.pop.img_path)
return 'storage/images/pops/' + this.pop.img_path
else
return ''
}
},
methods: {
fetchData() {
axios.get(route('frontend.api.v1.pops.show', this.id))
.then(response => {
// JSON responses are automatically parsed.
// console.log(response.data.data.collection)
this.pop = response.data.data
})
.catch(e => {
this.errors.push(e)
})
// console.log('fetchData')
}
}
}
</script>
Here is my app.js script :
window.Vue = require('vue');
Vue.component('pop-modal', require('./components/PopModal.vue'));
Vue.component('toggle-pop', require('./components/TogglePop.vue'));
const app = new Vue({
el: '#app',
props: {
id: Number
}
});
I would like to sync the states of the component named toggle-pop, how can I achieve this ? One is rendered by Blade template (laravel) and the other one by pop-modal component. But they are just the same, displayed at different places.
Thanks.
You could pass a state object as a property to the toggle-pop components. They could use this property to store/modify their state. In this way you can have multiple sets of components sharing state.
Your component could become:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': sstate.added, 'btn-primary': !sstate.added}">{{ sstate.text }}</button>
</template>
<script>
export default {
props: {
sstate: {
type: Object,
default: function() {
return { added: false, text: "", id: -1 };
}
}
},
data() {
return {};
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.sstate.id
}))
.then(response => {
this.sstate.text = response.data.message
let success = response.data.success
this.sstate.text = response.data.new_text
if (success) {
this.sstate.ddded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.sstate.id);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
};
</script>
Live demo
https://codesandbox.io/s/vq8r33o1w7
If you are 100% sure that all toggle-pop components should always have the same state, you can choose to not define data as a function. Just declare it as an object.
data: {
dAdded: this.added,
dText: this.text,
dId: this.id
}
In https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, it mentions
a component’s data option must be a function, so that each instance
can maintain an independent copy of the returned data object
If Vue didn’t have this rule, clicking on one button would affect the
data of all other instances
Since you want to sync the data of all toggle-pop component instances, you don't have to follow the data option must be a function rule.

Not able to update the data with #change in Vue js 2

I am not able to populate the updated data to the child component when doing on-change from a select box with local data/object. But I am able to load data to the child component everytime when I click the submit button with the help of API. I need help to refresh my child component with my updated data when I do the on-change from the select box. Find the below code which I'm trying. That too when I do on-change first time it was updating the props in child component, but when I do the same it is not going to the child component, it stops i the parent component itself.
Component-1:
<template>
<div>
<div class="row">
<select v-model="selectedemp" #change="filterempdata($event.target.value)">
<option value="">Select emp/option>
<option v-for="option in empfilterlist" v-bind:value="option.value" v-bind:key="option.value">{{ option.text }}</option>
</select>
</div>
<div class="compone">
<empView v-if='loaded' :empDetails='empData'></empView>
</div>
<div class="col-lg-6 col-md-6">
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="getEmpDetails">Fetch Data</button>
</div>
</div>
</template>
<script>
import updatedemp from './empView'
export default {
name: 'cluster',
components: {
'empView': updatedemp
},
data () {
return {
loaded: false,
emptData: {},
empfilterlist: [
{ text: 'Department', value: '1' },
{ text: 'Status', value: '2' },
],
selectedemp: '',
}
},
methods: {
filterempdata: function (selectedoption) {
console.log('Onchange value - ' + selectedOption)
Vue.set(this.empData, 'Department', selectedoption)
},
getEmpDetails: function () {
this.$http.get('http://localhost:7070/getemmdata')
.then((response) => {
this.empData = response.data
this.loaded = true
},
response => {
console.log('test' + JSON.stringify(response))
}
)
}
}
}
</script>
Component: 2
<template>
<div class="empView">
<div class="col-lg-6 col-md-6">
<h3>{{ empid }}</h3>
</div>
<div class="col-lg-6 col-md-6">
{{ empname }}
</div>
</div>
</template>
<script>
export default {
name: 'empView',
props: ['empDetails'],
data () {
return {
empid: this.empDetails.id,
empname: this.empDetails.name
}
},
watch: {
workflowDetails: function (changes) {
console.log('data updated ' + JSON.stringify(changes))
this.empid = changes.id
this.empname = changes.name
this.department = changes.Department
}
}
}
</script>
Your first problem is here:
filterempdata: function (selectedoption) {
console.log(')
this.empData.WorkflowFilter = selectedoption
this.empData = this.empData
}
By default, empData is:
data () {
return {
loading: false,
emptData: null,
So this.empData.WorkflowFilter = selectedoption should not work, as well as this.empData = this.empData does nothing.
Just make the default value an empty object and update it according to selection (just setting WorkflowFilter).
This should do the trick. Another weird this is the loading property. Your second component will be visible only if loading = true, which is odd. Maybe use loaded instead?