How to manage complex state behaviour on my Vue.js checkbox component? - javascript

I have a couple of challenges that I am trying to overcome in my 1 week of learning Vue.js. Please note that this example is in reality wrapped around a parent component called <question> which isn't that interesting, so I have kept my code simplified for this post.
How can I set certain items to be default checked on load?
Edit — I figured 1 out. Just had to do [ "Chicken", "Turkey", "Beef", "Fish", "Pork" ]
How can I uncheck certain items like if I select Vegan, meat options should be unchecked?
How do I have an Exclude and Include checkbox alongside my options?
Checkbox
<div id="questionnaire">
<checkbox v-model="form.meats" id="8a" option="Chicken"></checkbox>
<checkbox v-model="form.meats" id="8b" option="Turkey"></checkbox>
<checkbox v-model="form.meats" id="8c" option="Beef"></checkbox>
<checkbox v-model="form.meats" id="8d" option="Pork"></checkbox>
<checkbox v-model="form.meats" id="8e" option="Fish"></checkbox>
<checkbox v-model="form.meats" id="8f" option="Vegetarian Only"></checkbox>
<checkbox v-model="form.meats" id="8g" option="Vegan Only"></checkbox>
{{ form.meats }}
</div>
Vue.component('checkbox')
Vue.component('checkbox', {
template: `
<div>
<input type="checkbox" :id="id" :value="option" v-model="checked" #change="update">
<label :for="id">
{{ option }}
<slot></slot>
</label>
</div>
`,
data() {
return {
checkedProxy: false
}
},
computed: {
checked: {
get() {
return this.value
},
set(option) {
this.checkedProxy = option
}
}
},
methods: {
update: function(e) {
this.$emit('input', this.checkedProxy)
}
},
props: {
value: null,
option: null,
id: {
type: String,
required: true
}
}
});
new Vue({
el: "#questionnaire",
data: {
form: {
meats: [],
}
}
})

I think these are what you want.
<div id="questionnaire">
<check-group :chks="chks" v-model="form.meats"></check-group>
{{ form.meats }}
</div>
const groups = {
'1': {
tag: 'meats',
exclusiveGroups: [2]
},
'2': {
tag: 'vegan',
exclusiveGroups: [1]
}
}
const chks = {
'Chicken': {
groupIds: [1]
},
'Turkey': {
groupIds: [1]
},
'Beef': {
groupIds: [1]
},
'Pork': {
groupIds: [1]
},
'Fish': {
groupIds: [1]
},
'Vegetarian Only': {
groupIds: [2]
},
'Vegan Only': {
groupIds: [2]
}
}
Vue.component('checkbox', {
template: `
<div>
<label>
<input type="checkbox" ref="chk" :value="val" v-model="value" #change="update($event)">
{{ txt }}
<slot></slot>
</label>
<input type="checkbox" :checked="value.indexOf(val)<0" #change="reverseSelection($event)">
</div>
`,
data () {
return {
val: this.optValue || this.optText,
txt: this.optText || this.optValue
}
},
methods: {
update (e) {
this.$emit('input', this.value, e.target.value, e.target.checked)
},
reverseSelection () {
var e = document.createEvent("MouseEvents");
e.initEvent("click", true, true);
this.$refs.chk.dispatchEvent(e);
}
},
props: ['value','optValue','optText']
});
Vue.component('check-group',{
template: `
<div>
<checkbox v-for="item in chks" :opt-value="item.value" :opt-text="item.text" #input="update" v-model="value"></checkbox>
</div>
`,
props: {
value: {
required: true,
type: Array
},
chks: {
required: true,
type: Array
}
},
methods: {
update (val,curVal,checked) {
if(checked){//only checkbox be checked need to judge mutually-exclusive
chks[curVal].groupIds.forEach(id=>{//iterate all group of this checkbox
groups[id].exclusiveGroups.forEach(eid=>{//iterate all exclusiveGroups of this group
for(let i=0;i<val.length;i++){
let p = chks[val[i]].groupIds.indexOf(eid)
if(p>=0){//if this checkbox's group in exclusiveGroups then remove this item from val Array
val.splice(p,1)
i--
}
}
})
})
}
this.$emit('input',val)
},
}
})
new Vue({
el: "#questionnaire",
data: {
chks: Object.keys(chks).map(key=>({value: key,groupIds: chks[key]})),
form: {
meats: ['Chicken']
}
}
})
if you want to let vegan and vegetarian can't be both selected at once,
you can modify defining of groups and chks like this:
const groups = {
'1': {
tag: 'meats',
exclusiveGroups: [2] //means that when the checkbox of this group be checked,the checkbox whose group-index equals 2 where be unchecked
},
'2': {
tag: 'vegan',
exclusiveGroups: [1,3]
},
'3': {
tag: 'Vegetarian Only',
exclusiveGroups: [1,2]
}
}
const chks = {
'Chicken': {
groupIds: [1]
},
'Turkey': {
groupIds: [1]
},
'Beef': {
groupIds: [1]
},
'Pork': {
groupIds: [1]
},
'Fish': {
groupIds: [1]
},
'Vegetarian Only': {
groupIds: [3]
},
'Vegan Only': {
groupIds: [2]
}
}

Related

Vue2: Binding v-model via computed property?

I have this component that shows a whole bunch of different components. Like so:
computed: {
...mapGetters('forms', ['formErrors']),
input: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
component() {
const components = {
'ls-text-field': () =>
import('../../../../common/ls-text-field.vue'),
'simple-date-picker': () =>
import('../../../../common/simple-date-picker.vue'),
select: 'v-select',
combobox: 'v-combobox',
};
return components[this.setting.component];
},
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
},
};
return {
...attrs[this.setting.component],
'error-messages': this.formErrors(this.setting.key),
};
},
},
and the Template looks something like this:
<template>
<v-col md="4" cols="12">
<component
:is="component"
v-bind="attributes"
v-model="input"
:search-input.sync="searchSku"
/>
But you'll notice I had to do v-model in the template and not in the computed property. I suppose there is NO way to do this:
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
'v-model': this.item.info.someKey // This doesn't seem possible
},
};

How to use react-select-async-paginate library on change of different select?

I am trying to implement 2 select box
1st select box will be the simple select box.
2nd select box will have infinite scroller functionality and for this, I am using the react-select-async-paginate library.
Issue Explanation
AsyncPaginate is the component of react-select-async-paginate library. It uses loadOptions function attribute to load the option into the select box and it expect return value as {options: [], hasMore: false}.
In my code, in the loadOptions attribute, I am calling the loadHostOptions function to get the options. And on change of the first dropdown, I am calling loadHostOptions function. But in this case, correct options are not reflected in the 2nd dropdown.
Can anyone help me with how to load options on the change of the first dropdown?
Here is codesandbox
Code
import React from "react";
import { AsyncPaginate } from "react-select-async-paginate";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstSelectVal: "",
value: null
};
}
firstSelectChange = (event) => {
this.setState({ firstSelectVal: event.target.value });
if (event.target.value) {
this.loadHostOptions("java", []);
}
};
onPagiChange = (event) => {
this.setState({ value: event});
};
loadHostOptions = async (search, prevOptions) => {
if (search === "java") {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
} else {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
},
{
value: 2,
label: "C++"
},
{
value: 3,
label: "Python"
},
{
value: 4,
label: "Node"
},
{
value: 5,
label: "Go, Lang"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
}
};
render() {
return (
<div>
<h1>react-select-async-paginate</h1>
<h2>1st Selectbox</h2>
<select
id="lang"
onChange={this.firstSelectChange}
value={this.state.firstSelectVal}
>
<option value="">Select</option>
<option value="java">Java</option>
</select>
{this.state.firstSelectVal ? (
<>
<h2>2nd Selectbox</h2>
<AsyncPaginate
value={this.state.value}
loadOptions={(search, prevOptions) =>
this.loadHostOptions(search, prevOptions)
}
onChange={(e) => this.onPagiChange(e)}
/>
</>
) : (
""
)}
</div>
);
}
}
export default App;
You will have to change your onPagiChange function to
onPagiChange = (event) => {
this.setState({ value: event});
};
That is set the entire event as value instead of event.value
Update
The actual issue is whenever you click on the AsyncPaginate field it will call loadHostOptions function and pass the current value to the search argument. In that case whatever you type in will be the value of search.
So if you want to use the value of first Select box to filter option for the second one, you will have to directly use the this.state.firstSelectVal directly inside the loadHostOptions function. Like this
loadHostOptions = async (search, prevOptions) => {
if (this.state.firstSelectVal === "java") {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
} else {
const responseJSON = {
results: [
{
value: 1,
label: "Java"
},
{
value: 2,
label: "C++"
},
{
value: 3,
label: "Python"
},
{
value: 4,
label: "Node"
},
{
value: 5,
label: "Go Lang"
}
],
has_more: false
};
return {
options: responseJSON.results,
hasMore: responseJSON.has_more
};
}
};

How to checkbox filtering in reactjs and handle state? and show the available item after the checkbox

I want to make a filter system using multiple checkbox. But when i checked one checkbox it filter the state but when i unchecked it how i can get back the all data in state . Also if i select multiple checkbox then it will filter from the filtered item.
Here is my code.
state = {
restaurant : [
{name: 'La mesa', cuisine: ['italian', 'indian']},
{name: 'Red Bull', cuisine: ['chiness', 'french']}
{name: 'Purnima', cuisine: ['thai', 'arabic']}
]
cuisine: [
{id: 1, name: 'italian'},
{id: 2, name: 'indian'},
{id: 3, name: 'chiness'}
{id: 4, name: 'french'},
{id: 4, name: 'arabic'},
]
}
handleCuisineFilter = (e) => {
if (e.target.checked) {
const filter =
this.state.restaurant.length &&
this.state.restaurant.filter((rest) => rest.cuisine.includes(e.target.value));
this.setState({ restaurant: filter });
} else {
Now when unchecked how i can get previous state???
}
};
render() {
return (
<div>
{this.state.cuisine.length && this.state.cuisine.map(
cuisine=> (<li>
<input
id={cuisine.id}
type='checkbox'
onChange={this.handleCuisineFilter}
name='check'
value={cuisine.name}
/>
{cuisine.name } {here will be count of number of restaurant}
</li>
))}
{this.state.restaurant.length && this.state.restaurant.map(rest=> <h5>rest.name</h5>)}
</div>
I tried to explain best via my code . Help me please. Thank you in advance
You have to keep track of checked state for each filter and then filter against all filters at once every time.
Here is the solution
EDIT
import React, { Component } from "react";
import "./App.css";
class App extends Component {
state = {
restaurant: [
{ name: "La mesa", cuisine: ["italian", "indian"] },
{ name: "Red Bull", cuisine: ["chiness", "french"] },
{ name: "Purnima", cuisine: ["thai", "arabic"] },
],
// maintain a checked state for each filter
cuisine: [
{ id: 1, name: "italian", checked: false },
{ id: 2, name: "indian", checked: false },
{ id: 3, name: "chiness", checked: false },
{ id: 4, name: "french", checked: false },
{ id: 5, name: "arabic", checked: false },
],
};
setFilter = (cuisine, flag) => {
this.setState((prevState) => ({
cuisine: prevState.cuisine.map((c) =>
// check state for the selected cuisine
c.id === cuisine.id ? { ...c, checked: flag } : c
),
}));
};
handleCuisineFilter = (e, cuisine) => {
if (e.target.checked) {
this.setFilter(cuisine, true);
} else {
this.setFilter(cuisine, false);
}
};
filterRestaurants = (restaurant) => {
const checkedFilters = this.state.cuisine.filter((c) => c.checked);
const noFiltersChecked = checkedFilters.length === 0;
if (noFiltersChecked) {
return true;
} else {
// EDITED:
const tmp = checkedFilters.reduce(
(hasRestaurantAllTheseCuisines, nextCuisine) =>
(hasRestaurantAllTheseCuisines =
hasRestaurantAllTheseCuisines &&
restaurant.cuisine.includes(nextCuisine.name)),
true
);
return tmp;
}
};
render() {
return (
<div>
{this.state.cuisine.length &&
this.state.cuisine.map((cuisine) => (
<li key={cuisine.id}>
<input
id={cuisine.id}
type="checkbox"
onChange={(e) => this.handleCuisineFilter(e, cuisine)}
name="check"
value={cuisine.name}
/>
{cuisine.name} {/* here will be count of number of restaurant */}
</li>
))}
{/* Use .filter() with cuisine state */}
{this.state.restaurant.length &&
this.state.restaurant
.filter(this.filterRestaurants)
.map((rest) => <h5 key={rest.name}>{rest.name}</h5>)}
</div>
);
}
}
export default App;
Edited the code. The only change was the filter check here
...
const tmp = checkedFilters.reduce(
(hasRestaurantAllTheseCuisines, nextCuisine) =>
(hasRestaurantAllTheseCuisines =
hasRestaurantAllTheseCuisines &&
restaurant.cuisine.includes(nextCuisine.name)),
true
);
...

I have many checkbox it render as the description below I want to change the nested value its in blue circle in the pic

This is my state:
view screenshot
the part in the circle is the part of the state that I want to change
**This is my checkbox input **
{this.state.data.map((elm) => (
<div className={classes.rowContainer}>
<h3>{elm.name}</h3>
{elm.groupes.map((group) => (
<input
className={classes.checkBox}
name={elm.name}
groupes={group.groupName}
checked={group.value}
type='checkbox'
onChange={this.changeValue}
/>
))}
</div>
))}
This is the object passed to component:
const data = [
{
name: 'Créer un groupe',
groupes: [
{ name: 'commercial', value: true },
{ name: 'manager', value: false }
]
},
{
name: 'Détruire un groupe ',
groupes: [
{ name: 'commercial', value: false },
{ name: 'manager', value: false }
]
}
]
To update the specific boolean value for a given checkbox, you can make use of the .map() calls indexes from the arrays, and pass those to the onChange handler of the checkbox inputs to update the correct value in the state.
To update the state itself safely and without mutation, you'll need to deep copy the data array (using a round trip through JSON in this case), then update the right value using the indexes, then assign the new data to the state.
Here is a working snippet that illustrates how this works:
const data = [{
name: 'Créer un groupe',
groupes: [
{ name: 'commercial', value: true },
{ name: 'manager', value: false }
]
}, {
name: 'Détruire un groupe ',
groupes: [
{ name: 'commercial', value: false },
{ name: 'manager', value: false }
]
}];
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data };
}
changeValue = (sectionIndex, groupIndex) => {
const dataCopy = JSON.parse(JSON.stringify(this.state.data));
const group = dataCopy[sectionIndex].groupes[groupIndex];
group.value = !group.value;
this.setState({ data: dataCopy });
}
render() {
return (
<div>
{ this.state.data.map((elm, i) => (
<div key={`${i}`}>
<h3>{elm.name}</h3>
{ elm.groupes.map((group, j) => (
<input key={`${i}${j}`} name={elm.name}
checked={group.value} type="checkbox"
onChange={() => this.changeValue(i, j)} />
)) }
</div>
)) }
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

react-sortablejs - Setting the 'onChange' method on an object with nested arrays

I'm using the react-sortablejs library.
When trying to move cards within the list. I get the error:
Cannot read property 'map' of undefined
I have a dense structure and it gets lost here. How to handle onChange so that I can see in the console that the order of the notes within the list has changed.
Demo here
import Sortable from 'react-sortablejs';
// Functional Component
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
onChange={(order, sortable, evt) => {
console.log(order)
onChange(order);
}}
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};
class App extends React.Component {
state = {
item: {
id: "abc123",
name: "AAA",
lists: [
{
id: "def456",
list_id: "654wer",
title: 'List1',
desc: "description",
listItems: [
{
id: "ghj678",
title: "ListItems1",
listItemsId: "88abf1"
},
{
id: "poi098",
title: "ListItems2",
listItemsId: "2a49f25"
},
{
id: "1oiwewedf098",
title: "ListItems3",
listItemsId: "1a49f25dsd8"
}
]
},
{
id: "1ef456",
list_id: "654wer",
title: 'List 2',
desc: "description",
listItems: [
{
id: "1hj678",
title: "ListItems4",
listItemsId: "18abf1"
},
{
id: "1oi098",
title: "ListItems5",
listItemsId: "1a49f25"
},
{
id: "1oiwewe098",
title: "ListItems6",
listItemsId: "1a49f25dsd"
}
]
},
{
id: "2ef456",
title: 'List 3',
list_id: "254wer",
desc: "description",
listItems: [
{
id: "2hj678",
title: "ListItems7",
listItemsId: "28abf1"
},
{
id: "2oi098",
title: "ListItems8",
listItemsId: "234a49f25"
},
{
id: "df098",
title: "ListItems9",
listItemsId: "1asd8"
}
]
}
]
}
};
render() {
const c = this.state.item['lists'].map(item => { return item.listItems});
return (
this.state.item['lists'].map(item => {
return (<div>
{item.title}
<SortableList
key={uniqueId()}
items={item}
onChange={(item) => {
console.log(item)
this.setState({item});
}}
>
</SortableList>
</div>)
})
)
}
};
Thanks in advance.
You have to update few changes in your code.
Update the SortableList function as below.
First pass data-id={val.id} in li and after that in onChange method you will receive the order with id. So based on that we are sorting the records.
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
onChange={(order, sortable, evt) => {
items.listItems.sort(function(a, b){
return order.indexOf(a.id) - order.indexOf(b.id);
});
onChange(items);
}}
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val.id}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};
Update the onChange event of App component.
onChange={(item) => {
let itemObj = {...this.state.item};
itemObj.lists.map(x=>{
if(x.id === item.id) x = item;
});
this.setState({itemObj});
}}
That's it!
Here is the working demo for you
https://stackblitz.com/edit/react-sortablejs-blzxwd
When remove the onChange event in the Sortable list, Its works.
const SortableList = ({ items, onChange }) => {
return (
<div>
<Sortable
tag="ul"
>
{items.listItems.map(val => {
return <li key={uniqueId()} data-id={val}>List Item: {val.title}</li>})
}
</Sortable>
</div>
);
};

Categories