How to concatenate/modify props in VueJS - vue.js

I'm trying to build an application in Vue2 I have a parent component where I'm passing a data as props to child component using v-for.
Here is my parent component:
<template>
<div class="row" v-for="(item, index) in variables">
<design-arch :details="item" :selectedFields="selectedFields"></design-arch>
</div>
</template>
props:['selectedFields'],
data() {
return {
variables:[
{id: 1, role: 2, specialisation: 13, name: 'ABC - spec 1', role_name: 'ABC', spec_name: 'spec 1'},
{id: 2, role: 2, specialisation: 24, name: 'ABC - spec 2', role_name: 'ABC', spec_name: 'spec 2'},
{id: 3, role: 2, specialisation: 27, name: 'ABC - spec 3', role_name: 'ABC', spec_name: 'spec 3'},
]
}
}
and below is my child component:
<template>
<table v-if="tableData && tableData.data.length">
<thead class="">
<tr>
<th>Sr No</th>
<th>Projects Count</th>
<th>Value</th>
<th>Area</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in tableData.data">
<td>{{tableData.meta.from + index}}</td>
<td>{{item.projects_count}}</td>
<td>{{item.value}}</td>
<td>{{item.area}}</td>
</tr>
</tbody>
</table>
</template>
props: ['details', 'selectedFields'],
data(){
return{
loading: false,
tableData:{},
filters: '',
}
},
methods:{
fetchData(){
this.filters = this.selectedFields;
this.filters['role'] = typeof this.selectedFields['role'] !== 'undefined' ? this.selectedFields['role'] : [{id: this.details.role, name: this.details.role_name}];
this.filters['specialisation'] = typeof this.selectedFields['specialisation'] !== 'undefined' ? this.selectedFields['specialisation'] : [{id: this.details.specialisation, name: this.details.spec_name}];
this.filters['sort_by_column'] = typeof this.selectedFields['sort_by_column'] !== 'undefined' ? this.selectedFields['sort_by_column'] : { column: 'projects_count', order: 'desc'};
console.log(this.filters)
//axios call... with payload as this.filters
}
},
In above code we need to concatenate or modify the prop - selectedFields and call the API to get the data. Since each component has specific modifications, we need to re-calculate in the child component.
Currently my filters are similar in each child component and the modifications are not reflected during the Axios call.
How we can modify the props element inside the local data. So that we can have different executions.
Any better approach is appreciated. Thanks.

I would suggest adding a Vue watch on the props details. Anytime details changes it will rerun fetchData(). See Vue watch.
watch: {
details: {
immediate: true,
deep: true,
handler (oldValue, newValue) {
this.fetchData()
}
}
}

Related

VueJS - Hide and Show based on form selection

I have a form select element and an array to display some device information on the page. I render all the devices with a v-for loop and I couldn't find a way to display them based on the selection from form-select element because I am not allowed to use v-if statements with v-for
Here is my form select;
<b-form-select v-model="selected" :options="options" size="sm" class="mr-10px"></b-form-select>
And here is how I display the devices in HTML;
<tr v-for="device in devices" :key="device.id">
<td style="width: 20px;">
<img :src="device.image"alt="..." />
</td>
<td>
<h6>{{device.name}}</h6></span>
<span>{{device.model}}</span>
</td>
<td>
<span
class="badge font-size-12"
:class="{
'bg-soft-danger': device.traffic >= '10' && device.traffic <= '30',
'bg-soft-warning': device.traffic >= '30' && device.traffic <= '60',
'bg-soft-success': device.traffic > '50',
}"
>
{{device.traffic}}
</span>
</td>
<td>
<span class="badge font-size-12" :class="[device.status == 'Active' ? 'bg-soft-success' : 'bg-soft-danger']">{{device.status}}</span>
</td>
</tr>
And now here is my javascript file for form-select options and device array...
export const devices = [
{
id: 1,
image: require("./mini.svg"),
name: "Username 1",
model: "Device Model",
traffic: "10mb",
status: "Active",
},
{
id: 2,
image: require("./mini.svg"),
name: "Username 2",
model: "Device Model 2",
traffic: "20mb",
status: "Active",
},
{
id: 3,
image: require("./mini.svg"),
name: "Username 3",
model: "Device Model 3",
traffic: "30mb",
status: "Deactive",
},
];
export const options = [
{
id: 1,
value: "All",
text: "All",
disabled: false,
},
{
id: 2,
value: "Location",
text: "Location",
disabled: true
},
{
id: 3,
value: "Traffic",
text: "Traffic",
disabled: false,
},
{
id: 4,
value: "Active",
text: "Active",
disabled: false,
},
{
id: 5,
value: "Deactive",
text: "Deactive",
disabled: false,
},
]
And here is how I import the javascript file and use these as data in my .vue file...
import { devices, options } from '../devices'
export default {
data() {
return {
devices: devices,
options: options,
selected: 'All',
};
},
};
Here is my question; how do I display these devices when the form-select is changed to Active or Deactive
I cant say v-if something equals to 'Active' because Vue doesn't let me use v-if with v-for. Is there any other way to do this?
Use computed property,
computed: {
computedDevices() {
// filter by conditions here
return this.devices.filter(device => device.status === 'Active')
}
}
then use computedDevices in the v-for instead of devices
<tr v-for="device in computedDevices" :key="device.id" >
...
</tr>
you can use v-if and v-for just don't do it in the same tag.
use v-if before or inside v-for
e.g.
<div v-for="i in 10">
<div v-if="i>
...
</div>
</div>
Vue does not support using v-if inline with v-for. You can create a parent v-for and child is v-if
Something like this
<template v-for="..." :key="...">
<div|tr|anytag v-if="...">
</div|tr|anytag>
</template>

vue2 list return mixed string or component

In loop like this, I mostly just iterate over item values as strings, but sometimes need to return rendered component, for example build link element, or dropdown menu, for that table cell - need to find a way to return other component output instead of raw html string
<tr class="listing-item listing-item-category">
<td v-for="td in headeritems">{{val(td.k)}}</td>
</tr>
Is that even possible? I've found no mention of this, how should the method code go to return other component output? I know I would have to use v-html, but how to get it?
Assume we have a list like this:
headerItems: [
{
type: 'text',
value: 'Some text'
},
{
type: 'img',
props: {
src: 'http://some-where....'
}
},
{
type: 'my-component',
value: 'v-model value',
props: {
prop1: 10,
prop2: 'Blah bla',
},
events: {
myEvt: () => console.log('myEvt has fired')
}
},
],
So, We can render it:
<tr>
<td
v-for="(item, i) in headerItems" :key="i"
>
<div v-if="item.type === 'text'"> {{ item.value }}</div>
<component
v-else
:is="item.type"
v-model="item.value"
v-bind="item.props"
v-on="item.events"
/>
</td>
</tr>

How to update a vue js array with user inputed value

i am creating a web app which has a table that displays information from an array i have created using vue.js. i wanted to be able to edit/update/delete the content of the array based on the needs of an admin account which would then change what the table is displaying.
my current array
var app2 = new Vue({
el: '#app',
data: {
search: '',
courses: [
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x',},
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x!', },
{ 'topic': 'x', 'description': 'x', },
{ 'topic': 'x', 'description': 'x!', }
]
}
i would like the "x" to be changeable based on what the user typed into the input field
<th scope="row"> 1</th>
<td>{{item.topic}}</td>
<td>{{item.description}}</td>
this isnt all the code the gist of it
It sounds like what you want is a component which you toggle to be either in a read-only mode, or in a mode which accepts input for your admin users. You can then use this in your v-for loop to bind to the array of data.
You can still use v-model with custom components to facilitate two-way binding as described here. Basically, v-model will supply the value to the component via the value property, and will listen for an input event from the component to receive updates to the value.
So, our component template could look like the below, which conditionally renders an input field if the prop edit is true, otherwise it renders a read-only span element. We bind the value of the input to a local mutable data item we initialise from the value prop v-model will pass in from the parent. On the input event as the user changes the value, it will in turn emit an event of the same name so the parent component can receive updates.
<input
v-if="edit"
type="text"
v-model="localValue"
#input="$emit('input', $event.target.value)"
class="form-control" />
<span v-else>{{ value }}</span>
With a backing script like this:
var customInputComponent = {
template: '#input-template',
props: ["edit", "value"],
data() {
return {
localValue: this.value
}
}
};
In the parent element, the usage of the component in the template would look like this, where we register the custom input component with the name appCustomInput. We use v-model to bind the component to the current item of the iteration and the property for that particular column. Finally, we bind the edit prop of the custom component to a data field editMode on the parent with :edit="editMode" (short cut for v-bind:edit="editMode").
It is important to set the :key="searchResult.id" to uniquely identify each entry, otherwise there will be problems when updating the DOM when the value changes. We can't use just the index for this, because if we remove an item from the courses array, it will no longer be correct. So, I've added a unique id property to each of the course objects.
<tr v-for="(searchResult, index) in searchCourses" :key="searchResult.id">
<th scope="row">
{{ index + 1 }}
</th>
<td>
<app-custom-input v-model="searchResult.topic" :edit="editMode" />
</td>
<td>
<app-custom-input v-model="searchResult.description" :edit="editMode" />
</td>
<td v-if="editMode">
<button
v-if="editMode"
class="btn btn-secondary"
#click="deleteRow(searchResult.id)">Delete</button>
</td>
</tr>
The backing instance code for this would look something like this:
new Vue({
el: "#app",
components: {
appCustomInput: customInputComponent
},
data: {
search: '',
editMode: false,
courses: [
{ topic: "English", description: "Universal language", id: 1 },
{ topic: "Maths", description: "Numbers etc.", id: 2 },
{ topic: "Physics", description: "Falling apples etc.", id: 3 },
{ topic: "Chemistry", description: "Periodic table etc.", id: 4 }
]
},
methods: {
deleteRow(sourceId) {
var index = this.courses.findIndex(a => a.id == sourceId);
this.courses.splice(index, 1);
}
},
computed: {
searchCourses() {
return this.courses
.filter((item) => {
return item.topic
.toLowerCase()
.includes(this.search.toLowerCase())
});
}
}
});
A complete code pen is available here for demonstration.
The {{ }} "mustache" syntax is used to display values, so the expression between the double braces will be evaluated and the output will replace it. This is a one-way process. You're most likely looking for a two-way binding which does not only change your UI if the property changs, but also change the property when a user changes it from the UI, hence the name.
Have a look at Input bindings. Where v-model is the key:
<input v-model="item.topic">

Binding model attribute to radio button with vuejs

I have a dataset that looks like this:
[
{id: 1, name: 'Foo', is_primary: false},
{id: 2, name: 'Bar', is_primary: true},
{id: 3, name: 'Baz', is_primary: false},
]
Only one of the entries is allowed to have is_primary = true. I'm displaying these items in a list, and I'm trying to display a radio button for each that the user can select to indicate that this is the primary one.
<tr v-for="item in items">
<td><input name="primary" type="radio" v-model="item.is_primary"></td>
</tr>
However, I don't think I'm understanding how this is supposed to work, because it's not working for me. Is this possible or am I supposed to handle this situation another way?
A set of radio inputs should v-model the same variable: a scalar that takes on the value associated with the selected radio.
To translate that back and forth into your item list, you can use a settable computed.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
name: 'Foo',
is_primary: false
},
{
id: 2,
name: 'Bar',
is_primary: true
},
{
id: 3,
name: 'Baz',
is_primary: false
},
]
},
computed: {
primaryItem: {
get() {
return this.items.find((i) => i.is_primary);
},
set(pi) {
this.items.forEach((i) => i.is_primary = i === pi);
}
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div v-for="item in items">
<input name="primary" type="radio" :value="item" v-model="primaryItem">
</div>
<pre>{{JSON.stringify(items, null, 2)}}</pre>
</div>

Vue slot-scope with v-for, data changed but not re-rendered

I have a question while I'm studying vuejs2
I made an example with slot-scope && v-for, but it has an error which I can't understand.
Here is example code
https://jsfiddle.net/eywraw8t/6839/
app.vue
<template id="list-template">
<div>
<ul>
<slot name="row" v-for="item in list" v-bind=item />
</ul>
</div>
</template>
<div id="app">
<list-component :list="list">
<li slot="row" slot-scope="item">
{{item.name}}
</li>
</list-component>
</div>
Vue.component('list-component', {
template: '#list-template',
props: {
list: {
type: Array
}
}
});
new Vue({
el: '#app',
data () {
return {
list: [
{id: 1, name: 'Apple'},
{id: 2, name: 'Banana'},
{id: 3, name: 'Cherry'},
{id: 4, name: 'Durian'},
{id: 5, name: 'Eggplant'}
]
}
},
methods: {
h (item) {
item.name = item.name.toUpperCase()
console.log('Changed!')
console.log(item)
}
}
});
Strange thing is, the method 'h' is triggered and then, the console said 'Changed!' and data also changed but, the view is not re-rendered.
What am I missing? I think slot-scoped object data is not referencing the original object data.
What should I do to modify the original data?
Thanks for reading this question.
You are directly trying to modify the value of an item in an array. Due to some limitations vue cannot detect such array modifications.
So update your code to use vm.$set() to make chamges to the array item.
methods: {
h (item) {
let i = this.items.findIndex((it) => it.id === item.id);
this.$set(this.list, i, {...item, name: item.name.toUpperCase()});
console.log(item.id)
}
Here is the updated fiddle