I have a problem with binding checkboxes using Vuex. On checkbox I use v-model with variable which has getter and setter to set or get value in store, the problem is that I get wrong data in store and I don't understand what cause the problem. Checkboxes bind to store property and this property must contain array of id's from checkboxes, but when I click checkbox more than one time it rewrite or remove store values. Can anyone help me to understand why does this happens? Link to jsFiddle.
The code
const store = new Vuex.Store({
state: {
checkboxes: {},
checked: {}
},
mutations: {
setCheckboxes(state, dataObj){
console.log(dataObj);
state.checkboxes = dataObj.data;
let firstElem = dataObj.data[Object.keys(dataObj.data)[0]];
state.checked[firstElem.parent_id] = [firstElem.id];
console.log(state.checked);
},
setTreeState(state, dataObj){
state.checked[dataObj.id] = dataObj.value;
console.log(state.checked);
}
}
});
Vue.component('checkboxTree', {
template: "#checkboxTree",
});
Vue.component('checkboxToggle', {
template: "#checkboxToggle",
data(){
return {
store
}
},
computed: {
value:{
get(){
return store.state.checked[this.checkbox.parent_id];
},
set(val){
store.commit({
type: 'setTreeState',
id: this.checkbox.parent_id,
value: val
});
},
},
},
props: ['checkbox']
});
const app = new Vue({
el: "#app",
store,
data: {
checkboxData: {
...
},
},
mounted(){
this.$store.commit({
type: 'setCheckboxes',
data: this.checkboxData
});
}
})
Template
<div id="app">
<checkbox-tree :checkboxData="checkboxData"></checkbox-tree>
</div>
<template id="checkboxTree">
<div>
<p>checkbox tree</p>
<form>
<ul>
<li v-for="checkbox in $store.state.checkboxes">
<checkbox-toggle :checkbox="checkbox"></checkbox-toggle>
</li>
</ul>
</form>
</div>
</template>
<template id="checkboxToggle">
<div>
<label>{{ checkbox.id }}</label>
<input type="checkbox"
:value="checkbox.id"
:id="'checkbox-' + checkbox.id"
:name="'checkbox-' + checkbox.id"
v-model="value"
>
</div>
</template>
Okay, assuming you want checked to contain ids of selected objects, I had to restructure your code significantly:
const removeFromArray = (array, value) => {
const newArray = [...array];
const index = newArray.indexOf(value);
if (index > -1) {
newArray.splice(index, 1);
return newArray;
}
return array;
}
const store = new Vuex.Store({
state: {
checkboxes: {},
checked: [],
},
mutations: {
addToChecked(state, id) {
state.checked.push(id);
},
removeFromChecked(state, id) {
const newArray = removeFromArray(state.checked, id);
state.checked = newArray;
},
setCheckboxes(state, data) {
state.checkboxes = data;
},
}
});
Vue.component('checkboxTree', {
template: "#checkboxTree",
computed: {
checkboxes() {
return this.$store.state.checkboxes;
},
},
});
Vue.component('checkboxToggle', {
template: "#checkboxToggle",
computed: {
value:{
get(){
return this.$store.state.checked.indexOf(this.checkbox.id) > -1;
},
set(val){
const mutation = val ? 'addToChecked' : 'removeFromChecked';
this.$store.commit(mutation, this.checkbox.id);
},
},
},
props: ['checkbox'],
});
const app = new Vue({
el: "#app",
store,
data: {
checkboxData: {
"5479": {
"id": 5479,
"title": "Место оказания услуг",
"type": "checkbox",
"dependencies": "",
"description": "",
"parent_id": 5478,
"npas": ""
},
"5480": {
"id": 5480,
"title": "Способы оказания услуг",
"type": "checkbox",
"dependencies": "",
"description": "",
"parent_id": 5478,
"npas": "50"
},
"5481": {
"id": 5481,
"title": "Объем и порядок содействия Заказчика в оказании услуг",
"type": "checkbox",
"dependencies": "",
"description": "",
"parent_id": 5478,
"npas": "54"
},
}
},
computed: {
stateRaw() {
return JSON.stringify(this.$store.state, null, 2);
},
},
mounted() {
this.$store.commit('setCheckboxes', this.checkboxData);
const firstElementKey = Object.keys(this.checkboxData)[0];
const firstElement = this.checkboxData[firstElementKey];
this.$store.commit('addToChecked', firstElement.id);
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<div id="app">
<checkbox-tree :checkboxData="checkboxData"></checkbox-tree>
<pre v-text="stateRaw"></pre>
</div>
<template id="checkboxTree">
<div>
<p>checkbox tree</p>
<form>
<ul>
<li v-for="checkbox in checkboxes">
<checkbox-toggle :checkbox="checkbox"></checkbox-toggle>
</li>
</ul>
</form>
</div>
</template>
<template id="checkboxToggle">
<div>
<label>{{ checkbox.id }}</label>
<input
type="checkbox"
:value="checkbox.id"
:id="'checkbox-' + checkbox.id"
:name="'checkbox-' + checkbox.id"
v-model="value">
{{value}}
</div>
</template>
Using this code as an example, you can populate checked however you want to.
Also, a jsfiddle link for you: https://jsfiddle.net/oniondomes/ckj7mgny/
Related
HelloWorld.vue
<template>
<div>
<div v-for="box in boxes" :key="box.sname">
<BaseAccordian>
<template v-slot:title>{{ box.sname }}</template>
<template v-slot:content>
<div v-for="paint in paints" :key="paint.tname" class="line">
<List :content="matchingdata" :sname="box.sname" />
</div>
</template>
</BaseAccordian>
</div>
</div>
</template>
<script>
import BaseAccordian from "./BaseAccordian.vue";
import List from "./List.vue";
export default {
name: "HelloWorld",
components: {
BaseAccordian,
List,
},
data() {
return {
boxes: [
{
sname: "apple",
},
{
sname: "bananna",
},
{
sname: "grapes",
},
{
sname: "choc",
},
],
paints: [
{
tname: "a",
},
{
tname: "b",
},
{
tname: "c",
},
{
tname: "d",
},
{
tname: "e",
},
],
matchingdata: [
{
matchid: "1",
OverallStatus: "ok",
sname: "choc",
},
{
matchid: "2",
OverallStatus: "notok",
sname: "grapes",
},
],
};
},
};
</script>
BaseAccordion.vue
<template>
<div class="wrapper">
<div class="accordion">
<input type="checkbox" #click="toggleItem" />
<h2 class="title">
<slot name="title"></slot>
</h2>
</div>
<div v-show="show" class="content">
<slot name="content"></slot>
</div>
</div>
</template>
<script>
export default {
components: {},
data: function () {
return {
show: false,
};
},
methods: {
toggleItem: function () {
this.show = !this.show;
},
},
};
</script>
List.vue
<template>
<div class="">
<div
v-for="match in matchingData"
:key="match.matchid"
:class="{
green: match.OverallStatus === 'ok',
red: match.OverallStatus === 'notok',
}"
>
{{ match.OverallStatus }}
</div>
</div>
</template>
<script>
export default {
components: {},
props: {
content: {
type: Array,
required: true,
},
sname: {
type: String,
required: true,
},
},
data: function () {
return {};
},
methods: {},
computed: {
matchingData() {
return this.content.filter((a) => {
if (a.sname === this.sname) {
return true;
} else {
return false;
}
});
},
},
};
</script>
<style scoped>
</style>
I three arrays called matchingdata,boxes,paints array based on this three arrays, i am trying to filter the array.(nested v-for)
Now, I want to iterate the matchingdata array by comparing it with sname in boxes array. and Common value between matchingdata and boxes array is ""sname""
I tried above logic, and struck with computed property.
Expected Output:-
In List.vue component , i have
{{ match.OverallStatus }} where that field , i want to show,(from the matchingdata array) when user clicked on checkbox.
Taking the ""sname"" the common value from the matchingdata array and the boxes array
code:- https://codesandbox.io/s/damp-pine-27s2kn?file=/src/components/List.vue
As you're passing the sname property as a string via a prop to your List.vue component, you'll just need to use that string in your filter function.
matchingData() {
return this.content.filter((a) => a.sname === this.sname)
},
I've tried this in your codesandbox link and it is giving some output - but I'm not clear enough on what you're trying to achieve to know if this is the intended outcome.
Just incase you're not aware the 'filter' function returns a new array. It's not going to return a 'true/false' which I feel you may be trying to do.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
My component state has an array named concessions with 35 objects, here's the structure of one of those objects:
{
address:"Some street"
brands: [{
id: 1,
name: 'fiat'
}]
city:"Paris"
contact_name:""
email:""
id:1
latitude:"11.11111"
longitude:"22.22222"
name:"AGORA Cars"
opening_hours:"something"
phone:"969396973"
phone2:""
zipcode:"19100"
}
Now, I have a list rendered with all car brands and a checkbox for each one like this:
<div class="brands-filter col-10">
<span v-for="brand in brands" :key="brand.key" class="brand-card">
<div>
<input
type="checkbox"
:value="brand.name"
v-model="search_filters"
#click="filterConcessions()"
/>
<label class="form-check-label">{{brand.name}}</label>
</div>
</span>
</div>
Basically, for each clicked checkbox, I'm adding the brand to searched_filters and after that I want to filter the concessions array based on those filters.
In that click method, #click="filterConcessions()", I'm doing this:
filterConcessions: function () {
let concessions = this.concessions;
let search_filters = this.search_filters;
let filteredConcessions = [];
filteredConcessions = concessions.filter((concession) =>
concession.brands.some((brand) => search_filters.includes(brand.name))
);
this.concessions = filteredConcessions;
}
But, no matter what, it gives me an empty array.
Any advice?
It's because you need to use the #change event instead of #click.
Otherwise, search_filters isn't populated before filterConcessions is run:
new Vue({
el: "#app",
data: {
search_filters: [],
concessions: [{
address: "Some street",
brands: [{
id: 1,
name: 'fiat'
}],
city: "Paris",
contact_name: "",
email: "",
id: 1,
latitude: "11.11111",
longitude: "22.22222",
name: "AGORA Cars",
opening_hours: "something",
phone: "969396973",
phone2: "",
zipcode: "19100"
}]
},
methods: {
filterConcessions: function() {
let concessions = this.concessions;
let search_filters = this.search_filters;
let filteredConcessions = concessions.filter((concession) =>
concession.brands.some((brand) => search_filters.includes(brand.name))
);
console.log(filteredConcessions)
this.concessions = filteredConcessions;
}
}
});
Vue.config.productionTip = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="brands-filter col-10" v-if="concessions[0]">
<span v-for="brand in concessions[0].brands" :key="brand.key" class="brand-card">
<div>
<input type="checkbox" :value="brand.name" v-model="search_filters" #change="filterConcessions()" />
<label class="form-check-label">{{brand.name}}</label>
</div>
</span>
</div>
</div>
After some search i figure how to solve this.
I've created a computed method:
computed: {
filteredConcessions() {
if (!this.search_filters.length) {
return this.concessions;
} else {
return this.concessions.filter((concession) =>
concession.brands.some((brand) =>
this.search_filters.includes(brand.name)
)
);
}
},
}
and at the for loop i iterate throw the "filteredConcessions":
<li v-for="concession in filteredConcessions" :key="concession.id" class="p-2">
And that solved my case!
I have an object like this and I need to filter the rules within each group item, however I need to also return the group name next to the filtered rule
{
"id": "rulesCompany",
"group": [
{
"name": "Cadastral",
"rule": [
{
"title": "Receita Federal",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
{
"title": "CNAE Primário - Alteração",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
]
},
{
"name": "Dados modelados",
"rule": [
{
"title": "Nível de Atividade - Alteração",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
{
"title": "Faturamento Presumido",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "medium"
}
]
},
]
}
For example, I need to return the group "Cadastral/Receita Federal" if I search for "Rece" in search field, but I don't know how to filter data inside data.
What I've done so far:
Module.vue
<template>
<div>
<slide-out :visible.sync="isVisible" :title="text.header">
<div slot="header">
<div>
<button class="neo-bt-icon--big slideout__close--button" #click="isVisible=false">
<svg><use xlink:href="/red/neo-assets/images/simple-line-icons.svg#arrow-2-left"></use></svg>
</button>
<h1 class="slideout__header--text">
{{ text.header }}
</h1>
<div class="neo-form-toggle-list__item neo-form-toggle neo-form-toggle--checkbox">
<input type="text" class="neo-form-field" placeholder="Buscar" v-model="searchQuery">
<input class="neo-form-toggle__field" :id="selectAllRules" #click="selectAllRules($event)" type="checkbox"/>
<label class="neo-form-toggle__label" :for="selectAllRules">selecionar tudo</label>
</div>
</div>
</div>
<div slot="content">
<div v-for="policyRule in filteredPolicyRules.group" :key="policyRule.name"
class="neo-form-group">
<li v-text="policyRule.name"></li>
<div class="neo-form-toggle-list__item neo-form-toggle neo-form-toggle--checkbox">
<input class="neo-form-toggle__field" :id="policyRule.name" #click="selectGroupRules(policyRule.rule, policyRule.name, $event)" type="checkbox" v-model="policyRules.name" />
<label class="neo-form-toggle__label" :for="policyRule.name">selecionar grupo</label>
</div>
<div class="neo-form-toggle-list neo-form-toggle-list--inline">
<div v-for="rule in policyRule.rule" :key="rule.title"
class="neo-form-toggle-list__item neo-form-toggle neo-form-toggle--checkbox">
<input class="neo-form-toggle__field" :id="rule.title" :value="rule" name="rule" type="checkbox" v-model="checkedRules"/>
<label class="neo-form-toggle__label" :for="rule.title">{{ rule.title }}</label>
<h6 class="neo-text-disabled-options">{{ rule.description }}</h6>
</div>
</div>
</div>
</div>
<div slot="footer">
<span>{{ checkedRules }}</span>
</div>
</slide-out>
</div>
</template>
<script>
import { mapState } from 'vuex';
import SlideOut from '#/components/shared/slideout/SlideOut.vue';
export default {
name: 'ModulePolicyRules',
props: [],
components: {
SlideOut,
},
data() {
return {
isVisible: false,
policyRules: [],
searchQuery: '',
checkedRules: [],
filteredRules: [],
};
},
computed: {
filteredPolicyRules() {
const me = this;
if (this.searchQuery) {
me.filteredRules.pop();
this.policyRules.group.filter((ruleGroup) => {
ruleGroup.rule.forEach((rule) => {
if (rule.title.startsWith(this.searchQuery)) {
console.log(me.filteredRules);
me.filteredRules.push(rule);
}
});
});
console.log(me.filteredRules);
return me.filteredRules;
// return this.policyRules.group.filter(item => item.name.startsWith(this.searchQuery));
}
return this.policyRules;
},
},
methods: {
async loadData() {
const rules = await this.$store.dispatch('policyrules/setPolicyRules');
this.policyRules = rules;
},
},
mounted() {
this.loadData();
},
};
</script>
<style lang="scss">
.neo-form-toggle__label {
text-transform: none;
font-weight: 600;
}
.neo-text-disabled-options {
text-transform: none;
}
</style>
Object expected result using "Rec" in search field:
{
"name": "Cadastral",
"rule": [
{
"title": "Receita Federal",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
]
},
Try this computed prop.
filteredPolicyRules() {
if (this.searchQuery) {
return this.policyRules.group.reduce((groups, { name, rule }) => {
const rules = [];
rule.forEach(r => {
if (r.title.startsWith(this.searchQuery)) {
rules.push(r);
}
});
if (rules.length > 0) {
groups.push({
name,
rules
});
}
return groups;
}, []);
}
return this.policyRules;
}
I'd suggest calling them groups and rules (plural) respectively, to avoid future confusion -- after all they are arrays.
Full demo:
const policyRules = {
"id": "rulesCompany",
"group": [{
"name": "Cadastral",
"rule": [{
"title": "Receita Federal",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
{
"title": "CNAE Primário - Alteração",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
]
},
{
"name": "Dados modelados",
"rule": [{
"title": "Nível de Atividade - Alteração",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "high"
},
{
"title": "Faturamento Presumido",
"description": "Fonte atualizada mensalmente.",
"homonym": false,
"criticality": "medium"
}
]
}]
};
new Vue({
el: '#app',
data() {
return {
searchQuery: '',
policyRules
}
},
computed: {
filteredPolicyRules() {
if (this.searchQuery) {
return this.policyRules.group.reduce((groups, { name, rule }) => {
const rules = rule.filter(this.matchFilter);
if (rules.length > 0) {
groups.push({
name,
rules
});
}
return groups;
}, []);
}
return this.policyRules;
}
},
methods: {
matchFilter(item) {
const
search = this.searchQuery.toLowerCase(),
term = (item.title || '').toLowerCase();
return term.includes(search);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="searchQuery" placeholder="Search..." />
<pre>{{ filteredPolicyRules }}</pre>
</div>
So first I've put your list into a map variable. Then I'm filtering that by checking if any of the wanted properties contain the search term. The array.filter Method returns a new array based on what entries returned true and what false.
I'm checking inside name, title and description. Also I made everything lower case so case doesn't matter.
Array.prototype.filter Reference
const term = 'CNE';
console.log(map.filter(e => {
const { name } = e;
if(contains(name, term)) return true;
for(const _r of e.rule) {
const { title, description } = _r;
if(contains(title, term)) return true;
if(contains(description, term)) return true;
}
return false;
}));
function contains(str, term) {
return str.toLowerCase().includes(term.toLowerCase());
}
And I would also suggest like Yom in his answer that you use groups and rules so you can name them better. So that then would be groups.filter(group => {[..]}) and for(const rule of group.rules)
I am trying to figure out how the this.$set (aka Vue.set) api works when using it to update an multidimensional array.
Given:
new Vue({
el: '#app',
data: {
rows:[{"id": "4", "edit": "true" }, { "id": "5", "edit": "false" }]
},
....
How will I use $this.set to do something like this:
this.rows[0].edit = false
I know this doesn't work:
this.$set(this.rows2, 0, false)
What is the correct way to use $this.set for a KV pair array ?
Since the edit properties in your rows objects are already set, you do not need to use Vue.set in this case. You can just set the property value and Vue will notice the change:
this.rows[0].edit = false;
Here's a simple example:
new Vue({
el: '#app',
data() {
return {
rows:[
{ "id": "4", "edit": true },
{ "id": "5", "edit": false }
],
}
},
methods: {
editNext() {
let index = this.rows.findIndex(r => r.edit);
this.rows[index].edit = false;
let next = ++index % this.rows.length;
this.rows[next].edit = true;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<div v-for="(row, i) in rows" v-if="rows[i].edit">
Editing Row ID {{ row.id }}
</div>
<button #click="editNext">Edit Next</button>
</div>
However, if the edit property of your row objects were not set initially (or if you just wanted to be safe), then you would need to use Vue.set in order to add the property and have it be reactive:
this.$set(this.rows[0], 'edit', false);
Here's an example of that case:
new Vue({
el: '#app',
data() {
return {
rows:[
{ "id": "4", "edit": true },
{ "id": "5" }
],
}
},
methods: {
editNext() {
let i = this.rows.findIndex(r => r.edit);
this.$set(this.rows[i], 'edit', false);
let next = ++i % this.rows.length;
this.$set(this.rows[next], 'edit', true);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<div v-for="row in rows" v-if="row.edit">
Editing Row ID {{ row.id }}
</div>
<button #click="editNext">Edit Next</button>
</div>
Here's the documentation on Vue's Change Detection Caveats.
I am trying to sort a JSON array from JSON file a.json.
The array is coming like this:
{"data":
[
{"exception":"",
"name":"Data Server1",
"alias":"TOR-Server",
"delayed":"NO",
},
{"exception":"",
"name":"Data Server2",
"alias":"FRA-Server",
"delayed":"NO",
}
]
I need to sort the data by the "alias name", coming from the JSON file with Vue JS.
This is my Javascript Code for the display in Vue JS
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
json: null
},
computed: {
sorted: function() {
setTimeout(function() {
var app = this.app.json
if (app.length >= 6)
return app;
}, 5000);
}
},
methods: {
toggleClass(){
if (this.failed == true){
this.failed = false;
}
else
{
this.failed = true;
}
}
}
But the Sorted function is not working and if I try to display the servers in the sorted array, I receive a blank page.
And my for loop in the HTML page is:
<div class="mainbutton" v-for="(server, index) in json.data ">
Hope this makes sense and I could get it working.
You can use Array.sort inside a computed, to return the sorted array.
For example, to sort the array's items by name property, you can call: this.json.sort((t1,t2) => t1.name < t2.name ? -1 : 1).
Here's a working snippet:
var app = new Vue({
el: '#app',
data: {
json: [
{
"exception": "",
"name": "Data Server3",
"alias": "TOR-Server",
"delayed": "NO",
},
{
"exception": "",
"name": "Data Server1",
"alias": "TOR-Server",
"delayed": "NO",
},
{
"exception": "",
"name": "Data Server2",
"alias": "FRA-Server",
"delayed": "NO",
}
]
},
computed: {
sortedJson: function() {
return this.json.sort((t1,t2) => t1.name < t2.name ? -1 : 1);
},
sorted: function() {
setTimeout(function() {
var app = this.app.json
if (app.length >= 6)
return app;
}, 5000);
}
},
methods: {
toggleClass() {
if (this.failed == true) {
this.failed = false;
} else {
this.failed = true;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<div class="mainbutton" v-for="(server, index) in sortedJson ">
{{server.name }}
</div>
</div>