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

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
}
}

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>

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.

Check / Uncheck a Checkbox in a child component with vuejs

Context :
I am using VueJS in a project for the first time so I'm potentially not using it the right way.
I have a parent and a child component :
Parent : ResearchProducts.vue : Displays a list of products that can be filtred by their categories. The filters are checkboxes related to the category of the products.
Child : CategoryDisplay.vue : This is the component that handle the view and the methods of a category row.
My Question :
When I click on a category checkbox inside of my child component and if the checkbox is checked, the category is added to a list of filters in the parent. This works.
When I uncheck the checkbox, the category is removed from this list. This works too.
Now I've showed all my categories as buttons in the parent component to make the filters visible more easily for the user. And my problem is that I want this button to uncheck the related checkbox inside the child component when I click on it.
Here is my actual code :
ResearchProducts.vue :
<template>
<div>
<template v-for="category in categories">
<category-display
:category="category"
:key="'category_'+category.id"
:checked="checked"
#categorySelected="categorySelected"
></category-display>
</template>
<button
v-for="filter in listFilters"
:key="'filter_'+filter.slug"
class="btn btn-light btn-sm mr-2 mb-2"
#click="removeFilter(filter)"
>
{{ filter.name }}
</button>
</div>
</template>
<script>
export default {
data() {
return {
categories: { // HERE A COLLECTION CONTAINING ALL MY CATEGORIES },
selectedCategories: [],
listFilters: []
};
},
methods: {
categorySelected(category) {
var newCategory = {
type: "category",
slug: category.slug,
name: category.name
};
if (category.checked) {
if (!this.selectedCategories.includes(category.slug)) {
this.selectedCategories.push(category.slug);
this.listFilters.push(newCategory);
}
} else {
if (this.selectedCategories.includes(category.slug)) {
const index = this.selectedCategories.indexOf(category.slug);
if (index > -1) {
this.selectedCategories.splice(index, 1);
}
}
this.listFilters = this.listFilters.filter(function(item) {
for (var key in newCategory) {
if (item[key] === undefined || item[key] == newCategory[key])
return false;
}
return true;
});
}
},
removeFilter(filter) {
// THERE, I NEED TO UNCHECK THE RELATED CHECKBOX IN CHILD COMPONENT
this.listFilters = this.listFilters.filter(function(item) {
for (var key in filter) {
if (item[key] === undefined || item[key] == filter[key]) return false;
}
return true;
});
}
}
};
</script>
CategoryDisplay.vue :
<template>
<b-form-checkbox-group class="w-100">
<b-form-checkbox :value="category.slug" class="w-100" #input="selection" v-model="selected" ref="checked">
{{ category.name }}
<span class="badge badge-secondary float-right">{{ category.products_count }}</span>
</b-form-checkbox>
</b-form-checkbox-group>
</template>
<script>
export default {
props: {
category: {
type: Object,
required: true
}
},
data() {
return {
selected: false
}
},
methods: {
selection() {
var response = false;
if(this.selected.length !== 0){
response = true;
}
this.$emit('categorySelected', {slug: this.category.slug, name: this.category.name, checked: response});
}
}
};
</script>
Here is a simplified sample for your reference. You can use the sync modifier to achieve a two-way-binding between the parent and child. That together with a computed property in the child with a setter and getter
So passing all categories to child, and sync the selected categories:
<HelloWorld :cats.sync="selectedCategories" :categories="categories"/>
Child component takes the categories, iterates and shows checkboxes. Here we use the computed property, and when a checkbox is clicked, the setter emits the change to the parent:
<label v-for="c in categories" :key="c.id">
<input v-model="temp" type="checkbox" :value="c">
{{ c.name }}
</label>
script:
computed: {
temp: {
get: function() {
return this.cats;
},
set: function(newValue) {
this.$emit("update:cats", newValue);
}
}
}
And the parent just simply iterates the selectedCategories and as you wish, when a change happens in parent for selectedCategories, child will be automatically aware of when an item is deleted.
Here's a full sample for your reference: SANDBOX

How to take userinput from vue dialog/modal

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>

uncheck all checkboxes from child component Vue.js

I have the following problem: I have parent component with a list of checkboxes and two inputs. So when the any of those two inputs has been changed I need to uncheck all checkboxes. I would appreciate if you can help me to solve this.
I wanted to change checkedItem to trigger watch in child and then update all children, but it doesn't work.
parent.vue
<template>
<div class="filter-item">
<div class="filter-checkbox" v-for="item in filter.items">
<checkbox :item="item" v-model="checkedItem"> {{ item }} </checkbox>
</div>
<div class="filter-range">
<input v-model.number="valueFrom">
<input v-model.number="valueTo">
</div>
</div>
</template>
<script>
import checkbox from '../checkbox.vue'
export default {
props: ['filter'],
data() {
return {
checkedItem: false,
checkedItems: [],
valueFrom: '',
valueTo: '',
}
},
watch: {
'checkedItem': function () {},
'valueFrom': function () {},
'valueTo': function () {}
},
components: {checkbox}
}
</script>
child.vue
<template>
<label>
<input :value="value" #input="updateValue($event.target.value)" v-model="checked" class="checkbox"
type="checkbox">
<span class="checkbox-faux"></span>
<slot></slot>
</label>
</template>
<script>
export default {
data() {
return {
checked: ''
}
},
methods: {
updateValue: function (value) {
let item = this.item
let checked = this.checked
this.$emit('input', {item, checked})
}
},
watch: {
'checked': function () {
this.updateValue()
},
},
created: function () {
this.checked = this.value
},
props: ['checkedItem', 'item']
}
</script>
When your v-for renders in the parent component, all the rendered filter.items are bound to the same checkedItem v-model.
To correct this, you would need to do something like this:
<div class="filter-checkbox" v-for="(item, index) in filter.items">
<checkbox :item="item" v-model="item[index]> {{ item }} </checkbox>
</div>
To address your other issue, updating the child component list is as easy as updating filter.items.
You don't even need a watcher if you dont want to use one. Here is an alternative:
<input v-model.number="valueFrom" #keypress="updateFilterItems()">
And then:
methods: {
updateFilterItems () {
// Use map() or loop through the items
// and uncheck them all.
}
}
Always ask yourself twice if watch is necessary. It can create complexity unnecessarily.

Categories

Resources