How to take userinput from vue dialog/modal - javascript

I have a MyList.vue which gets directly imported by my app.vue. MyList.vue doesnt contain subcomponents, it only imports:
import store from "../store/store";
import { USER_FETCHLIST } from "../store/actions/user";
And the data looks like this:
export default {
data () {
return {
tableData: [],
tableheader: []
}
},
created: function(){
store.dispatch(USER_FETCHLIST).then((res) => {
this.tableData = res["data"]["tableData"]
this.tableHeader = res["data"]["tableHeader"]
})
},
methods: {
changeRecord: function(element){
console.log(element)
}
}
}
MyList.vue has the following markup for a bootstrap-vue modal:
<template v-for="(element, index) in tableData">
<tr>
//rest of the markup generating the columns carrying the data
<td>
<button v-on:click="changeRecord(element)" v-b-modal="`modal-${index}`">Aendern</button>
<b-modal :id="'modal-' + index" title="BootstrapVue">
<template v-for="(value, name) in element">
<template v-if="typeof value==='object'">
<template v-for="(nestedValue, nestedName) in value">
<span>{{nestedName}}</span>
<input type="text" :value="nestedValue" :class="'editFieldDivision-' + index">
</template>
</template>
<template v-else>
<span>{{name}}</span>
<input type="text" :value="value" :class="'editFieldDivision-' + index">
</template>
</template>
</b-modal>
</td>
</tr>
</template>
The endresult when clicking the button is this dialog:
https://imgur.com/4aOEjde
The dialog might have more or less inputfields, depending on the data it receives from the backend.
However, this dialog is supposed to allow the user to apply changes to the respective record from the list in the background.
Since I'm very new to vue, I don't know what the "vue-approach" to "grabbing" the user input would be. Should I use v-model? And if so, how do I do this, since the inserted data/observables are inserted dynamically. In the end, the data shall be put into a one-dimensional, where key-value has the "label" of the respective inputfield as key, and the value of the respective inputfield as value.
Furthermore, if the user discards the dialog, the changes inside the dialog shouldnt be applied to the datasets on the frontend.

Here's one way to accomplish what you're looking for.
Keep a reference to the original object, and create a copy.
You will then use the copy in your inputs inside the modal, this way you wont be modifying the original object.
Then on the hide event, check if the OK button was pressed, if it was you copy all the values from the copy to the original object.
If cancel is clicked (or the modal is closed in another way), you simply clear the selected object and the copy.
This solution uses the lodash.set method, so you will need to include this in your project.
I also moved your modal out of your table loop.
Since you can only edit one record at a time, you only really need one modal on your page.
new Vue({
el: "#app",
data() {
return {
data: [{
Internal_key: "TESTKEY_1",
extensiontable_itc: {
description_itc: "EXTENSION_ITC_1_1",
description_itc2: "EXTENSION_ITC_1_2",
},
extensiontable_sysops: {
description_sysops: "EXTENSION_SYSOPS_1"
}
},
{
Internal_key: "TESTKEY_2",
extensiontable_itc: {
description_itc: "EXTENSION_ITC_2_1",
description_itc2: "EXTENSION_ITC_2_2",
},
extensiontable_sysops: {
description_sysops: "EXTENSION_SYSOPS_2_1"
}
}
],
editingRecord: {
original: null,
copy: null
}
}
},
methods: {
onEditModalHide(event) {
if (event.trigger === "ok") {
// if OK is pressed, map values back to original object.
for(let fullKey in this.editingRecord.copy){
const copyObject = this.editingRecord.copy[fullKey]
/*
this uses lodash set funcktion
https://www.npmjs.com/package/lodash.set
*/
set(this.editingRecord.original, fullKey, copyObject.value)
}
}
this.editingRecord.original = null
this.editingRecord.copy = null;
},
changeRecord(record) {
const flatCopy = this.flattenObject(record);
this.editingRecord.original = record;
this.editingRecord.copy = flatCopy;
this.$nextTick(() => {
this.$bvModal.show('edit-modal')
})
},
flattenObject(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object' && ob[i] !== null) {
var flatObject = this.flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
console.log(x)
toReturn[i + '.' + x] = {
key: x,
value: flatObject[x].value
};
}
} else {
toReturn[i] = {
key: i,
value: ob[i]
};
}
}
return toReturn;
}
}
});
<link href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="//unpkg.com/bootstrap-vue#2.7.0/dist/bootstrap-vue.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/lodash.set#4.3.2/index.js"></script>
<div id="app" class="p-4">
<table class="table table-bordered">
<tr v-for="element in data">
<template v-for="field in element">
<template v-if="typeof field==='object'">
<td v-for="nestedObjectValue in field">
{{nestedObjectValue}}
</td>
</template>
<template v-else>
<td>
{{field}}
</td>
</template>
</template>
<td>
<button class="btn btn-primary" #click="changeRecord(element)">
Edit
</button>
</td>
</tr>
</table>
<b-modal id="edit-modal" v-if="editingRecord.copy" #hide="onEditModalHide">
<template v-for="obj in editingRecord.copy">
<label>{{ obj.key }}</label>
<input v-model="obj.value" class="form-control"/>
</template>
</b-modal>
</div>

Related

React to object property change in an Array in Vue 3

I have a Vue 3 app. In this app, I need to show a list of items and allow a user to choose items in the array. Currently, my component looks like this:
MyComponent.vue
<template>
<div>
<div v-for="(item, index) in getItems()" :key="`item-${itemIndex}`">
<div class="form-check">
<input class="form-check-input" :id="`checkbox-${itemIndex}`" v-model="item.selected" />
<label class="form-check-label" :for="`checkbox-${itemIndex}`">{{ item.name }} (selected: {{ item.selected }})</label>
</div>
</div>
<button class="btn" #click="generateItems">Generate Items</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
data() {
return itemCount: 0
},
methods: {
generateItems() {
this.itemCount = Math.floor(Math.random() * 25) + 1;
},
getItems() {
let items = reactive([]);
for (let i=0; i<this.itemCount; i++) {
items.push({
id: (i+1),
selected: false,
name: `Item #${i+1}`
});
}
return items;
}
}
}
</script>
When I click select/deselect the checkbox, the "selected" text does not get updated. This tells me that I have not bound to the property properly. However, I'm also unsure what I'm doing wrong.
How do I bind a checkbox to the property of an object in an Array in Vue 3?
If you set a breakpoint in getItems(), or output a console.log in there, you will notice every time that you change a checkbox selection, it is getting called. This is because the v-for loop is re-rendered, and it'll call getItems() which will return it a fresh list of items with selected reset to false on everything. The one that was there before is no longer in use by anything.
To fix this, you could only call getItems() from generateItems() for example, and store that array some where - like in data, and change the v-for to iterate that rather than calling the getItems() method.
Steve is right.
Here is a fixed version: Vue SFC Playground.
<template>
<div>
<div v-for="(item, index) in items" :key="`item-${index}`">
<div class="form-check">
<input type="checkbox" class="form-check-input" :id="`checkbox-${index}`" v-model="item.selected" />
<label class="form-check-label" :for="`checkbox-${index}`">{{ item.name }} (selected: {{ item.selected }})</label>
</div>
</div>
<button class="btn" #click="generateItems">Generate Items</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
data() {
return { items: [] }
},
methods: {
generateItems() {
this.itemCount = Math.floor(Math.random() * 25) + 1;
this.getRndItems();
},
getRndItems() {
this.items = reactive([]);
for (let i=0; i<this.itemCount; i++) {
this.items.push({
id: (i+1),
selected: false,
name: `Item #${i+1}`
});
}
}
}
}
</script>

splice() wrong component in data array

Let's say I have a form, and can add or remove a column by a click.
I use v-for to render the vue components, when I try to use splice() to delete a specific component, it always delete the last component in the array.
I can't figure out what I did wrong here, any hint will be very appreciated.
Here is a part of my code:
the problem is occured at removePerson method.
Parent Component
<template>
<div class="form-container">
<template>
<div v-for="(child, key) in otherPersonArray" :key="key" class="form-container">
<button #click="removePerson(key)" class="close">X</button>
<component :is="child"></component>
</div>
</template>
<button #click="addPerson" >+ Add Person</button>
</div>
</template>
<script>
import otherPerson from './OtherPerson';
export default {
components: {
otherPerson
},
data() {
return {
otherPersonArray: [],
}
},
methods: {
addPerson() {
if (this.otherPersonArray.length <= 10) {
this.otherPersonArray.push(otherPerson);
}
},
removePerson(key) {
this.otherPersonArray.splice(key, 1);
},
},
}
</script>
For example, when I try to delete the component which input value is 1, it delete the component which input value is 2.
otherPerson component
<template>
<div class="form-container">
<div class="person">
<div class="row">
<div class="form-group col-6">
<label for="inventor-last-name">Lastname of Person *</label>
<div class="input-container">
<input v-model="lastName" type="text" class="form-control form-control-lg">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
lastName: '',
}
},
}
</script>
You can use Array.prototype.filter
removePerson(key) {
this.otherPerson = this.otherPersonArray.filter((x, i) => i !== key);
}
there are couple of ways to achieve this but for now can you try these:
removePerson(key) {
this.otherPersonArray = this.otherPersonArray.filter(person => {
return person.key === key;
})
}
OR
const index = this.otherPersonArray.indexOf(ele by key); // get index by key person key
if (index > -1) {
this.otherPersonArray.splice(index, 1);
}
What I am understanding key is index here then you should follow this:
var filtered = this.otherPersonArray.filter(function(value, index){
return index !== key;
});
let me know if still it not working for you?
Here is example :

Vue - passing data via Event.$emit

I have a question about passing data through Event.$emit. I have a parent that has a click event (appSelected) like this
<template>
<v-app>
<v-container>
<v-select :items="results" item-text="name" item-value="id" v-model="selectedOption" #change="appSelected"
:results="results"
label="Choose Application"
dense
></v-select>
</v-container>
</v-app>
</template>
In that component I have a method that emits the data. This is the id - this.selectedOption
appSelected() {
//alert("This is the id" + this.selectedOption);
Event.$emit('selected', this.selectedOption);
In my child component I would like to pass and use the value but can't seem to get it to work. I have a v-if on the top level div that if it sees a true it will show all below. That works great, but I need the id that's being passed.
This works for showing the div, but need to pass and use the id also. How can I pass the and use the id?
Event.$on('selected', (data) => this.appselected = true);
<template>
<div v-if="appselected">
Choose the Client Source
<input type="text" placeholder="Source client" v-model="query"
v-on:keyup="autoComplete"
v-on-clickaway="away"
#keydown.esc="clearText" class="form-control">
<span class="instructiontext"> Search for id, name or coid</span>
<div class="panel-footer" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
{{ result.name + "-" + result.oid }}
</li>
</ul>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { directive as onClickaway } from 'vue-clickaway';
export default{
directives: {
onClickaway: onClickaway,
},
data(){
return {
add_id: '',
onSelecton: '',
input_disabled: false,
selected: '',
query: '',
results: [],
appselected: false
}
},
methods: {
getClient(name) {
this.query = name;
this.results = [];
},
clearText(){
this.query = '';
},
away: function() {
// Added for future use
},
autoComplete(){
this.results = [];
if(this.query.length > 2){
axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
this.results = response.data;
});
}
}
},
created() {
Event.$on('selected', (data) => this.appselected = true);
console.log(this.appselected);
}
}
</script>
Thanks for your help in advance!
Change your listener to do something with the emitted value. I see the parent already has a selected variable that you maybe intend to use for this purpose:
Event.$on('selected', (selectedOption) => {
this.selected = selectedOption;
this.appselected = true;
});
The selectedOption is the value emitted from the child.

Vue: child component won't change after receiving props from parents

So I have a problem with parent-child component communication with vue. The thing is, after i navigate to a component, it should call an ajax to get data from the server. After receiving the data, the parent component supposed to send it to all the child components through props, but the props data isn't showing. The child component only start to show the props data, only after i change my code on my editor.
So, here's the code for my parent component
<template>
<div id="single-product-container">
<product-header :name="singleProductName" :details="singleProductDetail" />
<product-spec :spec="singleProductSpec" />
</div>
</template>
<script>
import SingleProductHeader from '#/pages/SingleProductPage/single-product-header'
import SingleProductSpec from '#/pages/SingleProductPage/single-product-spec'
import singleProductApi from '#/api/product.api'
export default {
data () {
return {
singleProductData: null,
singleProductDetail: [],
singleProductName: '',
singleProductSpec: null
}
},
methods: {
getAllSingleProductDetail () {
const productName = this.$route.params.product
const location = this.location || 'jakarta'
let vehicleType = null
const path = this.$route.fullPath
let self = this
if (path.includes('motorcycle')) {
vehicleType = 'motorcycle'
} else if (path.includes('car')) {
vehicleType = 'car'
}
singleProductApi.getSingleProductRequest(location, productName, vehicleType)
.then(singleProductResponse => {
console.log(singleProductResponse)
let specObj = singleProductResponse.specification
self.singleProductDetail = singleProductResponse.detail
self.singleProductName = singleProductResponse.product_name
self.singleProductSpec = specObj
self.singleProductData = singleProductResponse
})
.catch(error => {
throw error
})
}
},
mounted () {
document.title = this.$route.params.product
},
created () {
this.getAllSingleProductDetail()
},
components: {
'product-header': SingleProductHeader,
'product-spec': SingleProductSpec
}
}
</script>
and this is my single-product-spec component that won't load the props data:
<template>
<div id="product-spec">
<div class="product-spec-title">
Spesifikasi
</div>
<div class="produk-laris-wrapper">
<div class="tab-navigation-wrapper tab-navigation-default">
<div class="tab-navigation tab-default" v-bind:class="{ 'active-default': mesinActive}" v-on:click="openSpaceTab(event, 'mesin')">
<p class="tab-text tab-text-default">Mesin</p>
</div>
<div class="tab-navigation tab-default" v-bind:class="{ 'active-default': rangkaActive}" v-on:click="openSpaceTab(event, 'rangka')">
<p class="tab-text tab-text-default">Rangka & Kaki</p>
</div>
<div class="tab-navigation tab-default" v-bind:class="{ 'active-default': dimensiActive}" v-on:click="openSpaceTab(event, 'dimensi')">
<p class="tab-text tab-text-default">Dimensi & Berat</p>
</div>
<div class="tab-navigation tab-default" v-bind:class="{ 'active-default': kapasitasActive}" v-on:click="openSpaceTab(event, 'kapasitas')">
<p class="tab-text tab-text-default">Kapasitas</p>
</div>
<div class="tab-navigation tab-default" v-bind:class="{ 'active-default': kelistrikanActive}" v-on:click="openSpaceTab(event, 'kelistrikan')">
<p class="tab-text tab-text-default">Kelistrikan</p>
</div>
</div>
<div id="tab-1" class="spec-tab-panel" v-bind:style="{ display: mesinTab }">
<table class="spec-table">
<tbody>
<tr class="spec-row" v-for="(value, name) in mesinData" :key="name">
<td> {{ name }} </td>
<td> {{ value }} </td>
</tr>
</tbody>
</table>
</div>
<div id="tab-2" class="spec-tab-panel" v-bind:style="{ display: rangkaTab }">
<table class="spec-table">
<tbody>
<tr class="spec-row" v-for="(value, name) in rangkaData" :key="name">
<td> {{ name }} </td>
<td> {{ value }} </td>
</tr>
</tbody>
</table>
</div>
<div id="tab-3" class="spec-tab-panel" v-bind:style="{ display: dimensiTab }">
<table class="spec-table">
<tbody>
<tr class="spec-row" v-for="(value, name) in dimensiData" :key="name">
<td> {{ name }} </td>
<td> {{ value }} </td>
</tr>
</tbody>
</table>
</div>
<div id="tab-4" class="spec-tab-panel" v-bind:style="{ display: kapasitasTab }">
<table class="spec-table">
<tbody>
<tr class="spec-row" v-for="(value, name) in kapasitasData" :key="name">
<td> {{ name }} </td>
<td> {{ value }} </td>
</tr>
</tbody>
</table>
</div>
<div id="tab-5" class="spec-tab-panel" v-bind:style="{ display: kelistrikanTab }">
<table class="spec-table">
<tbody>
<tr class="spec-row" v-for="(value, name) in kelistrikanData" :key="name">
<td> {{ name }} </td>
<td> {{ value }} </td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
location: String,
spec: Object
},
data () {
return {
mesinActive: true,
rangkaActive: false,
dimensiActive: false,
kapasitasActive: false,
kelistrikanActive: false,
mesinTab: 'block',
rangkaTab: 'none',
dimensiTab: 'none',
kapasitasTab: 'none',
kelistrikanTab: 'none',
mesinData: {},
rangkaData: {},
dimensiData: {},
kapasitasData: {},
kelistrikanData: {}
}
},
methods: {
openSpaceTab (evt, tab) {
if (tab === 'mesin') {
this.mesinActive = true
this.rangkaActive = false
this.dimensiActive = false
this.kapasitasActive = false
this.kelistrikanActive = false
this.mesinTab = 'block'
this.rangkaTab = 'none'
this.dimensiTab = 'none'
this.kapasitasTab = 'none'
this.kelistrikanTab = 'none'
} else if (tab === 'rangka') {
this.mesinActive = false
this.rangkaActive = true
this.dimensiActive = false
this.kapasitasActive = false
this.kelistrikanActive = false
this.mesinTab = 'none'
this.rangkaTab = 'block'
this.dimensiTab = 'none'
this.kapasitasTab = 'none'
this.kelistrikanTab = 'none'
} else if (tab === 'dimensi') {
this.mesinActive = false
this.rangkaActive = false
this.dimensiActive = true
this.kapasitasActive = false
this.kelistrikanActive = false
this.mesinTab = 'none'
this.rangkaTab = 'none'
this.dimensiTab = 'block'
this.kapasitasTab = 'none'
this.kelistrikanTab = 'none'
} else if (tab === 'kapasitas') {
this.mesinActive = false
this.rangkaActive = false
this.dimensiActive = false
this.kapasitasActive = true
this.kelistrikanActive = false
this.mesinTab = 'none'
this.rangkaTab = 'none'
this.dimensiTab = 'none'
this.kapasitasTab = 'block'
this.kelistrikanTab = 'none'
} else if (tab === 'kelistrikan') {
this.mesinActive = false
this.rangkaActive = false
this.dimensiActive = false
this.kapasitasActive = false
this.kelistrikanActive = true
this.mesinTab = 'none'
this.rangkaTab = 'none'
this.dimensiTab = 'none'
this.kapasitasTab = 'none'
this.kelistrikanTab = 'block'
}
}
},
created () {
this.mesinData = this.spec.mesin
this.rangkaData = this.spec.rangka
this.dimensiData = this.spec.dimensi
this.kapasitasData = this.spec.kapasitas
this.kelistrikanData = this.spec.kelistrikan
}
}
</script>
As I said, the only problem with my single-product-spec component isn't that it won't load the props data. The problem is, it only loads the props data, when I change the code in my text editor (it's strange, I know). I began to realize this when I start to debugging, and when I change my code in single-product-spec component, the props data then began start to load. And if i don't change my single-product-spec component code, the props data won't load no matter how long i wait.
OK, so let's step through what happens in order:
The parent component is created, triggering the created hook and initiating the data load from the server.
The parent component renders, creating the child components. The prop value for spec will be null as the data hasn't loaded yet and singleProductSpec is still null.
The created hook for single-product-spec runs. As this.spec is null I'd imagine this throws an error, though no error was mentioned in the question.
At some point in the future the data load completes, updating the value of singleProductSpec. It is a rendering dependency of the parent component, so that component will be added to the rendering queue.
The parent component will re-render. The new value of singleProductSpec will be passed as the spec prop to single-product-spec. A new instance of single-product-spec will not be created, it will just re-use the one it created it first rendered.
At that point nothing else will happen. The created hook of single-product-spec won't re-run as it hasn't just been created.
When you edit the source code of the child component it will trigger a hot-reload of that component. The exact effect of such a change will vary but often it will cause that child to be re-created without re-creating the parent. As the parent already has the data loaded from the server the newly created child will be have been passed the fully-populated spec value. This allows it to be read within the created hook.
There are a number of ways to solve this.
Firstly, we could avoid creating the single-product-spec until the data is ready:
<product-spec v-if="singleProductSpec" :spec="singleProductSpec" />
This will simply avoid creating the component during the initial render, so that when the child's created hook is run it has access to the data you want. This is probably the approach you should use.
A second way to do it would be to use a key. Keys are used to pair up components across re-renders so that Vue knows which old component matches which new component. If the key changes then Vue will throw away the old child component and create a new one instead. As a new component is created it will run the created hook. This probably isn't the best approach for your scenario as it isn't clear what the child component should do when passed a spec of null.
A third approach would be to use a watch in the child component. This would watch for when the value of spec changes and copy across the relevant values to the component's local data properties. While there are some occasions when using a watch like this is appropriate it usually indicates an underlying weakness in a component's design.
However, there are other problems in your code...
It isn't clear why you're copying the values from the prop into local data in the first place. You can just use the prop directly. If you're doing it just to give them shorter names then just use a computed property instead. The only legitimate reason for copying them like this is if the property values can be changed within the child and the prop is only used to pass an initial value. Even in that scenario you wouldn't use a created hook, you'd just do it inside the data function. See https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow.
You're duplicating everything 5 times for the 5 tabs. This should be implemented using an array of objects, with each object containing all the relevant details for a tab.
The properties mesinActive and mesinTab both represent the same underlying data. You shouldn't have both in data. At the very least one should be a computed property, though personally I'd probably just get rid of mesinTab altogether. Instead use CSS classes to apply the relevant styling and just use mesinActive to decide which classes to apply (as you have elsewhere). Obviously the same applies to the other xActive/xTab properties.
Your tabs are a form of single selection. Using 5 boolean values to represent one selection is not an appropriate data structure. The correct way to do this is to have a single property that identifies the current tab. The specifics can vary, it might hold the tab index, or the object representing the tab data, or an id representing the tab.
You don't need to use let self = this with arrow functions. The this value is preserved from the surrounding scope.
Correctly implemented the code for single-product-spec should collapse down to almost nothing. You should be able to get rid of about 80% of the code. I would expect the method openSpaceTab to be a one-liner if you just use the appropriate data structures to hold all of your data.
Update:
As requested, here is a rewrite of your component taking into account points 1-4 from the 'other problems' section of my answer.
const ProductSpecTitle = {
template: `
<div>
<div class="product-spec-title">
Spesifikasi
</div>
<div class="produk-laris-wrapper">
<div class="tab-navigation-wrapper tab-navigation-default">
<div
v-for="tab of tabs"
:key="tab.id"
class="tab-navigation tab-default"
:class="{ 'active-default': tab.active }"
#click="openSpaceTab(tab.id)"
>
<p class="tab-text tab-text-default">{{ tab.text }}</p>
</div>
</div>
<div
v-for="tab in tabs"
class="spec-tab-panel"
:class="{ 'spec-tab-panel-active': tab.active }"
>
<table class="spec-table">
<tbody>
<tr
v-for="(value, name) in tab.data"
:key="name"
class="spec-row"
>
<td> {{ name }} </td>
<td> {{ value }} </td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
`,
props: {
spec: Object
},
data () {
return {
selectedTab: 'mesin'
}
},
computed: {
tabs () {
const tabs = [
{ id: 'mesin', text: 'Mesin' },
{ id: 'rangka', text: 'Rangka & Kaki' },
{ id: 'dimensi', text: 'Dimensi & Berat' },
{ id: 'kapasitas', text: 'Kapasitas' },
{ id: 'kelistrikan', text: 'Kelistrikan' }
]
for (const tab of tabs) {
tab.active = tab.id === this.selectedTab
tab.data = this.spec[tab.id]
}
return tabs
}
},
methods: {
openSpaceTab (tab) {
this.selectedTab = tab
}
}
}
new Vue({
el: '#app',
components: {
ProductSpecTitle
},
data () {
return {
spec: {
mesin: { a: 1, b: 2 },
rangka: { c: 3, d: 4 },
dimensi: { e: 5, f: 6 },
kapasitas: { g: 7, h: 8 },
kelistrikan: { i: 9, j: 10 }
}
}
}
})
.tab-navigation-wrapper {
display: flex;
margin-top: 10px;
}
.tab-navigation {
border: 1px solid #000;
cursor: pointer;
}
.tab-text {
margin: 10px;
}
.active-default {
background: #ccf;
}
.spec-tab-panel {
display: none;
}
.spec-tab-panel-active {
display: block;
margin-top: 10px;
}
.spec-table {
border-collapse: collapse;
}
.spec-table td {
border: 1px solid #000;
padding: 5px;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<product-spec-title :spec="spec"></product-spec-title>
</div>

How set computed property of checked checkboxes via v-model?

I've looked several resources, but I don't find the solution:
https://v2.vuejs.org/v2/guide/computed.html
V-model with props & computed properties
vuejs v-model, multiple checkbox and computed property
I've a table with people that have roles, if I want to change the roles I open a modal.
<template>
<div>
<b-table small outlined striped hover :items="peoplePaginated.data" :fields="fields" >
<template slot="actions" slot-scope="row">
<b-button size="sm" class="my-2 my-sm-0 btn-outline-info" v-b-modal="'federation-role-modal'" #click.stop="change(row.item)">
edit
</b-button>
<b-button size="sm" #click.stop="info(row.item, row.index, $event.target)" class="btn btn-outline-success btn-sm">
details
</b-button>
</template>
</b-table>
<FederationRoleModal :roles="roles" />
</div>
</template>
data () {
return {
roles: [],
}
},
methods: {
info (item) {
this.$router.push({ name: 'person', params: { id: item.id }})
},
change (person) {
const roles = person.personRoles.map(el => el)
const allRoles = roles.map(el => el.roleCategory.name)
this.roles = allRoles
}
}
Then I've a list of checkboxes where the checkedRoles takes care of the checked ones. When I click on a new checkbox, I want the data property to be updated. However this updating does not happen.
In the modal:
<span v-for="value in allRoles" :key="value.id">
<input type="checkbox" :value="value" v-model="checkedRoles">
<span class="checkbox-label"> {{value}} </span> <br>
</span>
computed property: {
checkedRoles: {
get: function () {
// console.log(this.roles)
return this.roles
},
set: function (newValue) {
// console.log(newValue)
return newValue
}
}
}
this.roles comes from the parent component (array with roles).
I do see the console.log(newValue) output as a new array with an additional role, but this new array is not visible as the checkedRoles data property.
[option 2] I've also tried to add checkedRoles: this.roles, in the data () { return {... }. But when I open several times a modal, the roles of the previous clicked row.item are still in the data property.
Please advise
if I should be using a computed property, or dig deeper into [option 2]
how I get all checked checkboxes to be in the checkedRoles data property.
checkRoles should be an empty array in your data object like :
data(){
return{
checkedRoles:[]
...
}
}
<span v-for="value in allRoles" :key="value.id">
<input type="checkbox" :value="value" v-model="checkedRoles">
<span class="checkbox-label"> {{value}} </span> <br>
</span>
Solution included modifying the parent component data property:
In the parent component add:
<FederationRoleModal :checked-roles="roles" #update-role="onUpdateRole"/>
methods:
onUpdateRole(value) {
let rolesArray = this.roles
if (rolesArray.includes(value)){
var index = rolesArray.indexOf(value)
if (index > -1) {
return rolesArray.splice(index, 1);
}
} else {
return rolesArray.push(value)
}
}
in the child component
<span v-for="value in allRoles" :key="value.id">
<input type="checkbox" :value="value" v-model="roles" #click="$emit('update-role', value)">
<span class="checkbox-label"> {{value}} </span> <br>
</span>
data:
props: ['checkedRoles'],
computed:
roles: {
get: function () {
return this.checkedRoles
},
set: function (newValue) {
return newValue
}
}

Categories

Resources