Indeterminate checkboxes with Vue.js - javascript

I just started out working with Vue and I'm trying to visualise a nested list.
The list-items should contain triple-state checkboxes:
When a child item is checked, the parent item's checkbox should become 'indeterminate'. When all child-checkboxes are checked, the parent checkbox should also become checked.
When a parent item checkbox is checked, all child item checkboxes (also the ones nested deeper) should be selected too.
I kind of have a working solution (check out this pen or the code below) but the checkbox-logic is still flawed. For this example, checked boxes are green, indeterminate ones are orange and unchecked ones are red.
I've run out of ideas how to fix it. Could someone shed some light on how to accomplish this in Vue?
'use strict';
Vue.component("book-chapter", Vue.extend({
name: "book-chapter",
props: ["data", "current-depth"],
data: function() {
return {
checked: this.data.checked,
indeterminate: this.data.indeterminate || false
};
},
methods: {
isChecked: function() {
return this.checked && !this.indeterminate;
},
isIndeterminate: function(){
return this.indeterminate;
},
toggleCheckbox: function(eventData) {
if (this.currentDepth > 0){
if (!this.data.children) {
this.checked != this.children
} else {
this.indeterminate = !this.indeterminate;
}
}
if (eventData) {
// fired by nested chapter
this.$emit('checked', eventData);
} else {
// fired by top level chapter
this.checked = !this.checked;
this.$emit('checked', {
data: this.data
});
}
},
isRootObject: function() {
return this.currentDepth === 0;
},
isChild: function() {
return this.currentDepth === 2;
},
isGrandChild: function() {
return this.currentDepth > 2;
}
},
template: `
<div class='book__chapters'>
<div
class='book__chapter'
v-bind:class="{ 'book__chapter--sub': isChild(), 'book__chapter--subsub': isGrandChild() }"
v-show='!isRootObject()'>
<div class='book__chapter__color'></div>
<div
class='book__chapter__content'
v-bind:class="{ 'book__chapter__content--sub': isChild(), 'book__chapter__content--subsub': isGrandChild() }">
<div class='book__chapter__title'>
<span class='book__chapter__title__text'>{{data.title}}</span>
</div>
<div class='book__chapter__checkbox triple-checkbox'>
<div class='indeterminatecheckbox'>
<div
class='icon'
#click.stop="toggleCheckbox()"
v-bind:class="{'icon--checkbox-checked': isChecked(), 'icon--checkbox-unchecked': !isChecked(), 'icon--checkbox-indeterminate': isIndeterminate()}">
</div>
</div>
</div>
</div>
</div>
<book-chapter
ref='chapter'
:current-depth='currentDepth + 1'
v-for='child in data.children'
key='child.id'
#checked='toggleCheckbox(arguments[0])'
:data='child'>
</book-chapter>
</div>
`
}));
Vue.component("book", Vue.extend({
name: "book",
props: ["data"],
template: `
<div class='book'>
<book-chapter
:data='this.data'
:currentDepth='0'>
</book-chapter>
</div>
`
}));
var parent = new Vue({
el: "#container",
data: function() {
return {
book: {}
};
},
mounted: function() {
this.book = {
"title": "Book",
"children": [{
"title": "1 First title",
"children": [{
"title": "1.1 Subtitle"
}, {
"title": "1.2 Subtitle"
}]
}, {
"title": "2 Second title",
"children": [{
"title": "2.1 Subtitle",
"children": [{
"title": "2.1.1 Sub-Sub title"
}, {
"title": "2.1.2 Another sub-sub title"
}]
}]
}]
}
}
});

Update: fixed a bug found by #PhillSlevin. See pen here
Check this pen, is it what you want to achieve?
I think you can use either eventbus or vuex to solve this problem,
if you treated every 's section as a component.
'use strict';
var bus = new Vue();
var book = {
"title": "Book",
"children": [{
"title": "1 First title",
"children": [{
"title": "1.1 Subtitle"
}, {
"title": "1.2 Subtitle"
}]
}, {
"title": "2 Second title",
"children": [{
"title": "2.1 Subtitle",
"children": [{
"title": "2.1.1 Sub-Sub title"
}, {
"title": "2.1.2 Another sub-sub title"
}]
}]
}]
};
Vue.component('book', {
template: `
<div class="book__chapter">
<p :class="'book__title ' + status" #click="clickEvent">{{title}} {{parent}}</p>
<book v-for="child in children" :key="child" :info="child"></book>
</div>
`,
props: ['info'],
data() {
return {
parent: this.info.parent,
title: this.info.title,
children: [],
status: this.info.status,
};
},
created() {
const info = this.info;
if(info.children) {
info.children.forEach(child => {
child.status = "unchecked";
// use title as ID
child.parent = info.title;
});
this.children = info.children;
}
},
mounted() {
const vm = this;
bus.$on('upside', (payload) => {
const targetArr = vm.children.filter((child) => child.title === payload.from);
if (targetArr.length === 1) {
const target = targetArr[0];
target.status = payload.status;
if (vm.children.every(ele => ele.status === 'checked')) {
vm.status = 'checked';
} else if (vm.children.every(ele => ele.status === 'unchecked')) {
vm.status = 'unchecked';
} else {
vm.status = 'indeterminate';
}
bus.$emit('upside', {
from: vm.title,
status: vm.status,
});
}
});
bus.$on('downside', (payload) => {
if (payload.from === this.parent) {
if (payload.status === 'checked') {
vm.status = 'checked';
vm.children.forEach(child => child.status = 'checked');
} else if (payload.status === 'unchecked') {
vm.status = 'unchecked';
vm.children.forEach(child => child.status = 'unchecked')
}
bus.$emit('downside', {
from: vm.title,
status: vm.status,
})
}
});
},
methods: {
clickEvent() {
if (this.status === 'checked') {
this.status = 'unchecked';
this.children.forEach(child => child.status = 'unchecked');
} else {
this.status = 'checked';
this.children.forEach(child => child.status = 'checked');
}
const vm = this;
bus.$emit('upside', {
from: vm.title,
status: vm.status,
});
bus.$emit('downside', {
from: vm.title,
status: vm.status,
});
},
}
});
var parent = new Vue({
el: "#container",
data: function() {
return {
book
};
},
});
.book__title.unchecked::after {
content: '□';
}
.book__title.indeterminate::after {
content: '△';
}
.book__title.checked::after {
content: '■';
}
.book__chapter {
display: block;
position: reletive;
margin-left: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.js"></script>
<div id="container">
<book :info="book" :parent="'container'"></book>
</div>

Related

Computed property in a v-loop workaround

I'm trying to make the code below work knowing that computed properties can't take parameters. Do you have any idea ? I'm exploring the use of watchers on functions but I was wondering if there was not an easier solution to do this.
var app = new Vue({
el: '#app',
data() {
return {
sessions: {
"156": {
tickets: {
"01": {
available: true,
},
"02": {
available: false,
},
}
},
},
tickets: {
"01": {
attr: "somestring",
},
"02": {
attr: "someotherstring",
},
},
},
};
},
computed: {
sessionTickets(session) {
let _this = this;
let sessionTickets = {};
$.each(_this.session.tickets, function(ticketId, sessionTicket) {
if(sessionTicket.available) {
sessionTickets[ticketId] = _this.tickets[ticketId];
}
});
return sessionTickets;
},
},
});
<div v-for="session in sessions">
<div v-for="sessionTicket in sessionTickets(session)">
{{ sessionTicket.attr }}
</div>
</div>
Thanks to "WallOp" for making me realize that my computed property is in a sessions loop and so it can normally become a class method and be refreshed on sessions refresh !
I think you can use computed property. Just filter tickets of sessions. Like this:
var app = new Vue({
el: '#app',
data() {
return {
sessions: {
"156": {
tickets: {
"01": {
available: true,
},
"02": {
available: false,
},
}
},
},
tickets: {
"01": {
attr: "somestring",
},
"02": {
attr: "someotherstring",
},
},
},
};
computed: {
filteredSessions() {
return this.sessions.map( session => {
let tickets = {};
for(key in session.tickets) {
if(session.tickets[key].available && this.tickets.hasOwnProperty(key)) {
tickets[key] = this.tickets[key];
}
}
session.tickets = tickets;
return session;
});
},
},
});
<div v-for="session in filteredSessions">
<div v-for="ticket in session.tickets">
{{ ticket.attr }}
</div>
</div>

I have some problems when making two siblings communicate with each other through event bus

I want to click a button in Servers, and currentStatus in ServerDetails will be changed to status property in server array, but my currentStatus in ServerDetails is still "Server Details are currently not updated"
Please help
main.js
export const eventBus = new Vue({
methods: {
changeStatus(stat) {
this.$emit("statChanges", stat);
}
}
});
Servers.vue
<li class="list-group-item" v-for="elem in server" :key="elem.id">
<button #click="updateStatus(elem.status)">Server #{‌{elem.id}}</button>
<p>{‌{serverstatus}}</p>
</li>
<script>
import { eventBus } from "../../main";
export default {
props: ["serverstatus"],
data: function() {
return {
server: [
{ id: 1, status: "Level 1" },
{ id: 2, status: "Level 2" },
{ id: 3, status: "Level 3" },
{ id: 4, status: "Level 4" },
{ id: 5, status: "Level 5" }
]
};
},
methods: {
updateStatus(x) {
this.serverstatus = x;
eventBus.changeStatus(this.serverstatus);
}
}
};
</script>
ServerDetails.vue
<p>{‌{currentStatus}}</p>
<script>
import { eventBus } from "../../main";
export default {
data: function() {
return {
currentStatus: "Server Details are currently not updated"
};
},
create() {
eventBus.$on("statChanges", stat => {
this.currentStatus = stat;
});
}
};
</script>
There is no lifecycle hook create in Vue. You should use created instead.

how to filter multi array list in reactjs

I have tried to implement a search functionality from a list that has multi-array. So I want to search the particular keyword from the array. I have tried but it throws an error.
Array to search from:
const listComponent = [{
id: 0,
title: "Common",
subMenu: [{
image: "",
secTitle: "",
subTitle: ""
}]
},
{
id: 1,
title: "Compute",
subMenu: [{
image: require("../../assets/images/scaling.png"),
secTitle: "one comp",
subTitle: ""
},
{
image: require("../../assets/images/ec2.png"),
secTitle: "two comp",
subTitle: ""
},
{
image: require("../../assets/images/lambda.png"),
secTitle: "three comp",
subTitle: ""
},
{
image: require("../../assets/images/zone.png"),
secTitle: "four comp",
subTitle: ""
}
]
},
{
id: 2,
title: "Second",
subMenu: [{
image: "",
secTitle: "",
subTitle: ""
}]
},
{
id: 3,
title: "Third",
subMenu: [{
image: "",
secTitle: "",
subTitle: ""
}]
},
{
id: 4,
title: "Fourth",
subMenu: [{
image: "",
secTitle: "",
subTitle: ""
}]
}
];
Class Declaration:
constructor(props) {
super(props);
this.state = {
components: listComponent,
filterResult: listComponent
};
this.filterComponents = this.filterComponents.bind(this);
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
components: nextProps.components
});
}
Fiter logic:
filterComponents = event => {
let value = event.target.value;
let components = this.state.components;
let filterResult = components.filter(
component =>
component.subMenu.findIndex(sub =>
sub.secTitle.toLowerCase().includes(value)
) !== -1
);
console.log(filterResult);
this.setState({ filterResult });
};
JSX:
<ul className="listAll">
{this.state.filterResult.map((items, key) => (
<li key={key}>
<div className="compTitle" onClick={()=> this.openCloseComponent(items.id)} >
<p>{items.title}</p>
{this.state.isComOpen === items.id && this.state.isOpen ? (
<KeyboardArrowDown className="compArrow" /> ) : (
<KeyboardArrowUp className="compArrow" /> )}
</div>
<ul className={ this.state.isComOpen===i tems.id && this.state.isOpen ? "displayBlock secondDrop" : "displayNone" }>
{items.subMenu.map((submenu, i) => (
<li key={i}>
<img src={submenu.image} alt="" />
<div>
<p className="secTitle">{submenu.secTitle}</p>
<p className="subTitle">{submenu.subTitle}</p>
</div>
</li>
))}
</ul>
</li>
))}
</ul>
{this.state.filterResult.length === 0 && (
<p className="noComp">No components found</p>
)}
Problem
I want to search secTitle in subMenu. The keyword that matches any secTitle should be displayed and other should be hidden. The about code throws an error.
So how do I fix it, any answers will be appreciated!
If I search one comp the output should be
{
id: 1,
title: "Compute",
subMenu: [
{
image: require("../../assets/images/scaling.png"),
secTitle: "one comp",
subTitle: ""
}
]
}
but now the output is,
{
id: 1,
title: "Compute",
subMenu: [
{
image: require("../../assets/images/scaling.png"),
secTitle: "one comp",
subTitle: ""
},
{
image: require("../../assets/images/ec2.png"),
secTitle: "two comp",
subTitle: ""
},
{
image: require("../../assets/images/lambda.png"),
secTitle: "three comp",
subTitle: ""
},
{
image: require("../../assets/images/zone.png"),
secTitle: "four comp",
subTitle: ""
}
]
}
instead of
filterResult = components.filter((component, index) => {
console.log(component.subMenu[index].secTitle);
return component.subMenu[index].secTitle.toLowerCase().search(value) !== -1;
});
The code should look something like this:
const listComponent = [
{
id: 0,
title: "Common",
subMenu: [
{
image: "",
secTitle: "abc",
subTitle: ""
}
]
},
{
id: 1,
title: "Common",
subMenu: [
{
image: "",
secTitle: "def",
subTitle: ""
}
]
}
]
let value = "abc";
let filterResult = listComponent.filter((component) => {
return component.subMenu.findIndex((sub) => {
return sub.secTitle === value;
}) >= 0;
});
console.log(filterResult);
try changing the key for subMenu. Ideally, you should not use an index as a key as the length and the order of subMenu might change. As Keys help React identify which items have changed, are added, or are removed.
trying changing to this.
...
{items.subMenu.map((submenu, i) => (
<li
key={`${submenu.secTitle}-${i}`}
>
<img src={submenu.image} alt="" />
<div>
<p className="secTitle">{submenu.secTitle}</p>
<p className="subTitle">{submenu.subTitle}</p>
</div>
</li>
))}
...
// For case-insensitive search
const result = [];
listComponent.forEach((component, index)=> {
const comp = {...component};
if(Array.isArray(comp.subMenu)) {
comp.subMenu = comp.subMenu.filter((subMenu)=> {
return subMenu.secTitle.toLowerCase().includes(value.toLowerCase());
});
if( comp.subMenu.length > 0) {
result.push(comp);
}
}
});
// For case-sensitive search
const result = [];
listComponent.forEach((component, index)=> {
const comp = {...component};
if(Array.isArray(comp.subMenu)) {
comp.subMenu = comp.subMenu.filter((subMenu)=> {
return subMenu.secTitle.includes(value);
});
if( comp.subMenu.length > 0) {
result.push(comp);
}
}
});
Okay here's the solution
Change your filterComponents method as this
const filterComponents = (e) => {
let value = e.target.value;
let components = [ ...this.state.components];
const filterData = []
components.map(item => {
item.subMenu.reduce((acc, cur) => {
if (cur.secTitle === value) {
acc.push(cur)
}
}, filterData)
})
this.setState({ filterResult: filterData });
};
filterData will have filtered result
Hope it helps
To solve your problem you have first find the top-level item with the item that you want:
From Glen K answer:
let aux = listComponent.filter((component) => {
return component.subMenu.findIndex((sub) => {
return sub.secTitle === value;
}) >= 0;
});
Then you filter the subMenu on the filtered list.
aux = aux.map(item => {
item.subMenu = item.subMenu.filter((component) => {
return component.secTitle === value;
})
return item;
})
Then save it to your state
filterResult = [...aux];
this.setState({ filterResult });
You are only filtering the Components, so run the filter again on the subMenus to filter the submenus:
let value = event.target.value;
let components = JSON.parse(JSON.stringify(this.state.components));
// This filter only targets the components themselves...
let filterResult = components.filter(
component =>
component.subMenu.findIndex(sub =>
sub.secTitle.toLowerCase().includes(value)
) !== -1
);
// ...so, run the filter again, but this time filter the subMenus
for (let i = 0; i < filterResult.length; i++) {
filterResult[i].subMenu = [ ...filterResult[i].subMenu];
filterResult[i].subMenu = filterResult[i].subMenu.filter(
sub =>
sub.secTitle.toLowerCase().includes(value)
);
}
console.log(filterResult);
Other answers provide cleaner solutions, but I feel like you're misunderstanding how the filter function works. So, I simply reuse your filter code on the subMenus -- hopefully this illustrates the concept that you are missing.
The Snippet below WORKS! So, please take a look... (I had to make a few assumptions about the rest of your code).
'use strict';
function require(url){ return url; };
const listComponent = [
{
id: 0,
title: "Common",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
},
{
id: 1,
title: "Compute",
subMenu: [
{
image: require("../../assets/images/scaling.png"),
secTitle: "one comp",
subTitle: ""
},
{
image: require("../../assets/images/ec2.png"),
secTitle: "two comp",
subTitle: ""
},
{
image: require("../../assets/images/lambda.png"),
secTitle: "three comp",
subTitle: ""
},
{
image: require("../../assets/images/zone.png"),
secTitle: "four comp",
subTitle: ""
}
]
},
{
id: 2,
title: "Second",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
},
{
id: 3,
title: "Third",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
},
{
id: 4,
title: "Fourth",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
}
];
function KeyboardArrowUp(props) {
return <span>up</span>;
}
class AppComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
components: listComponent,
filterResult: listComponent
};
this.filterComponents = this.filterComponents.bind(this);
}
filterComponents = event => {
let value = event.target.value;
let components = JSON.parse(JSON.stringify(this.state.components));
// This filter only targets the components themselves...
let filterResult = components.filter(
component =>
component.subMenu.findIndex(sub =>
sub.secTitle.toLowerCase().includes(value)
) !== -1
);
// ...so, run the filter again, but this time filter the subMenus
for (let i = 0; i < filterResult.length; i++) {
filterResult[i].subMenu = [ ...filterResult[i].subMenu];
filterResult[i].subMenu = filterResult[i].subMenu.filter(
sub =>
sub.secTitle.toLowerCase().includes(value)
);
}
console.log(filterResult);
this.setState({ filterResult });
};
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
components: nextProps.components
});
}
openCloseComponent(id) {
console.log(id);
}
render() {
return <div><input type="text" onChange={this.filterComponents} /><br/><ul className="listAll">
{this.state.filterResult.map((items, key) => (
<li key={key}>
<div
className="compTitle"
onClick={() => this.openCloseComponent(items.id)}
>
<p>{items.title}</p>
{this.state.isComOpen === items.id && this.state.isOpen ? (
<KeyboardArrowDown className="compArrow" />
) : (
<KeyboardArrowUp className="compArrow" />
)}
</div>
<ul
className={
this.state.isComOpen === items.id && this.state.isOpen
? "displayBlock secondDrop"
: "displayNone"
}
>
{items.subMenu.map((submenu, i) => (
<li
key={i}
>
<img src={submenu.image} alt="" />
<div>
<p className="secTitle">{submenu.secTitle}</p>
<p className="subTitle">{submenu.subTitle}</p>
</div>
</li>
))}
</ul>
</li>
))}
</ul></div>
{this.state.filterResult.length === 0 && (
<p className="noComp">No components found</p>
)};
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(<AppComponent />, domContainer);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app">
</div>
Use a combination of Array.filter and Array.findIndex to get the desired result.
Use String.includes to match the substring in secTitle
filterResult = filterResult.filter((component) => {
const hasMatchingSubMenu = component.subMenu.findIndex((sub) => sub.secTitle.includes(value)) !== -1;
if(hasMatchingSubMenu) {
component.subMenu = component.subMenu.filter((sub) => sub.secTitle.includes(value));
}
return hasMatchingSubMenu;
});
First use filter to filter the components:
let filterResult = components.filter(c => c.subMenu.filter(hasRequiredSecTitle).length > 0);
And then filter the subMenu collection of filtered components:
filterResult = filterResult.map(c => ({...c, subMenu: c.subMenu.filter(hasRequiredSecTitle)}))
So complete function will be:
filterComponents = event => {
let value = event.target.value;
let components = this.state.components;
const hasRequiredSecTitle = (subMenu) => subMenu.secTitle === value;
let filterResult = components.filter(c => c.subMenu.filter(hasRequiredSecTitle).length > 0);
filterResult = filterResult.map(c => ({...c, subMenu: c.subMenu.filter(hasRequiredSecTitle)}))
console.log(filterResult);
this.setState({ filterResult });
};
There are some good answers here! Figured I would chime in with mine..
This allows for case insensitive searches, and is fairly straight forward..
const { Component } = React;
const { render } = ReactDOM;
class App extends Component {
state = {
filtered: [],
listComponents: this.props.listComponent || [],
searchText: this.props.searchText || ""
};
componentDidMount = () =>
this.props.searchText &&
this.filterComponents();
handleChange = event =>
this.setState({
searchText: event.target.value
}, () => {
this.state.searchText
? this.filterComponents()
: this.setState({ filtered: [] })
});
filterComponents = () => {
const { searchText, listComponents } = this.state;
const searchExp = new RegExp(searchText, "i");
const listCopy = JSON.parse(JSON.stringify(listComponents));
const result = listCopy.filter(item => {
item.subMenu = item.subMenu.filter(sub => searchExp.test(sub.secTitle));
return item.subMenu.length && item;
});
this.setState({ filtered: result })
};
render() {
const { filtered, listComponents, searchText } = this.state;
return (
<div>
<input onChange={this.handleChange} value={searchText} type="text" />
<pre>{JSON.stringify(filtered, null, 2)}</pre>
</div>
);
}
}
const listComponent = [
{
id: 0,
title: "Common",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
},
{
id: 1,
title: "Compute",
subMenu: [
{
image: 'require("../../assets/images/scaling.png")',
secTitle: "one comp",
subTitle: ""
},
{
image: 'require("../../assets/images/ec2.png")',
secTitle: "two comp",
subTitle: ""
},
{
image: 'require("../../assets/images/lambda.png")',
secTitle: "three comp",
subTitle: ""
},
{
image: 'require("../../assets/images/zone.png")',
secTitle: "four comp",
subTitle: ""
}
]
},
{
id: 2,
title: "Second",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
},
{
id: 3,
title: "Third",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
},
{
id: 4,
title: "Fourth",
subMenu: [
{
image: "",
secTitle: "",
subTitle: ""
}
]
}
];
let index = <App listComponent={listComponent} searchText="oNe" />
render(index, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

Having trouble with multiple active states in ReactJS

Happy Monday fellow stackOverflowers! I would just like to know if there are any ways to keep active states independent of each other when clicked. I cant seem to figure out how to make them independent of each other. I have the state as a toggle. Each button's active status is false by default. When clicked it will toggle the status to opposite. The problem is when one of the button is clicked and the status is toggled, so do the status of other buttons. Please see below for the code:
import React, {Component} from "react";
import { Button, Progress } from 'reactstrap';
import "../src/Questions.css";
const items = [
{
question:"Category",
buttonList: [
'Games',
"Business",
'Education',
'Lifestyle',
"Entertainment",
"Utility",
"Social",
"Other"
],
multiple: false,
answers: [],
},
{
question:"Platform",
buttonList: [
'Android',
'iOS',
'Both'
],
multiple: false,
answers: [],
},
{
question:"Design Type",
buttonList: [
'Barebones',
'Stock',
'Custom'
],
multiple: false,
answers: [],
},
{
question:"Development Type",
buttonList: [
'Native',
'Web',
'Hybrid'
],
multiple: false,
answers: [],
},
{
question:"Does the app connect to a server",
buttonList: [
'Yes',
'No'
],
multiple: false,
answers: [],
},
{
question:"Does the app require users to sign in",
buttonList: [
'Yes',
'No'
],
multiple: false,
answers: [],
},
{
question:"Additional Features",
buttonList: [
'Audio',
'Camera',
'3rd party API',
"Dashboard",
"Social login",
"Task list",
"Search",
"Rating system",
"Chat",
"Privacy Settings",
"Gallery",
"QR code",
"Calendar",
"Social sharing",
"Activity feed",
"Push notifications",
"Ads",
"Multilangual",
"Feedback",
"Data collection",
"Geo location",
"Office capabilities",
"Activity tracking",
"Augmented reality",
"Virtual reality",
"Data export",
"Maps",
"Backup"
],
multiple: true,
answers: [],
},
{
question:"Last Step",
buttonList: [
'Games3',
'Business3',
'Education3'
],
multiple: false,
answers: [],
},
];
function checkElementIsSelected(array, element) {
if (array.length === 0) {
return false;
}
return(array.find(item => {
return item === element;
}));
}
class Questions extends React.Component {
constructor(props) {
super(props);
this.state = {
value:0,
count:0,
display: items[0]['question'],
active:false
}};
handleClickNext = (e) => {
e.preventDefault();
this.setState({
value:this.state.value +10,
count: this.state.count + 1,
display: items[this.state.count]['question'],
email: ''
})
console.log('+++', this.state.count);
}
handleClickAnswer = (e) => {
e.preventDefault();
const question = this.state.count;
if (!items[this.state.count].multiple) {
items[this.state.count].answers = [e.target.value];
} else {
if (!checkElementIsSelected (items[this.state.count].answers, e.target.value)) {
items[this.state.count].answers.push(e.target.value);
}
}
console.log('--- answers: ', items[this.state.count].answers);
this.setState({
active:!this.state.active
})
console.log("True or False Active",this.state.active );
}
handleSubmit = (e) => {
e.preventDefault();
this.props.history.push("/Success");
console.log('***', items);
}
handleEmail = (e) => {
e.preventDefault();
items[items.length - 1].answers = [e.target.value];
console.log("$$$Emails: '", items[items.length - 1].answers);
}
render() {
let element;
let next;
if (this.state.count === items.length) {
element = <input type="text" onChange={this.handleEmail}/>
next = <Button color='primary' onClick={this.handleSubmit}>Get my estimate</Button>
} else {
element = items[this.state.count].buttonList.map(btn => {
return (
<div>
<Button outline color="primary" value={btn} onClick={this.handleClickAnswer} active={this.state.active}>{btn}</Button>
</div>
)
});
next = <Button onClick={this.handleClickNext} color="primary" size="lg" value='Next'>Next</Button>;
}
return(
<div>
<div><Progress value={this.state.count * (100 / items.length)} /></div>
<div className="howMuchText">How much does it cost to build an app</div>
<div className="questionTitle">{this.state.display}</div>
<div className="buttonChoices">
{
element
}
</div>
<div className="nextButton">
{next}
</div>
</div>
)
}
}
export default Questions;
Any help greatly appreciated. Thanks!!!!
You've got only one boolean value as a flag for active in this.state.active.
Change this.state.active to an object with keys corresponding to btn names and store there a boolean flag for each button.
The problem is this active={this.state.active}
You manage all the button with only one state.active. U need to make one state per button.
Here and example
state {
btnA:false,
btnB:false,
btnC:false
}
changeState = (btn) => {
if(btn === 'a'){
this.setState( prevState => {
return {
btnA: !prevState.btnA
}
})
}
.... same for btnB and btn C
}
...
return (
<div>
<button onClick={this.state.changeState('a')} >{this.state.btnA?'Btn A is active':'Btn A is not active'}<button>
<button onClick={this.state.changeState('b')} >{this.state.btnB?'Btn B is active':'Btn B is not active'}<button>
<button onClick={this.state.changeState('c')} >{this.state.btnB?'Btn C is active':'Btn C is not active'}<button>
</div>
)
this is just an example about how to manage buttons status with just one state now adapt this example to your code

Dialogue.Directive Not Working When emiting the handler ?

After A dialogue is completed and its confirmationStatus is changed to confirmed than i emit another dialogue directive ./ intent but its directive dosent work and it directly jumps to emit and ends
code :-
const handlers = {
'LaunchRequest': function () {
this.response.speak(welcomeOutput).listen(welcomeReprompt);
var userID = this.event.session.user.userID;
console.log(userID);
this.emit(':responseReady');
},
'createOrder': function () {
var filledSlots = delegateSlotCollection.call(this);
this.emit(':tell','Create Order Ended');
},
'addOrder': function () {
var filledSlots = delegateSlotCollectionSecond.call(this);
},
'AMAZON.HelpIntent': function () {
speechOutput = "";
reprompt = "";
this.response.speak(speechOutput).listen(reprompt);
this.emit(':responseReady');
},
'AMAZON.YesIntent': function () {
this.emit("Yes Triggered");
},
'AMAZON.NoIntent': function () {
this.emit("NO Triggered");
},
'AMAZON.CancelIntent': function () {
speechOutput = "Okay Your Request is Cancelled";
this.response.speak(speechOutput);
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
speechOutput = "";
this.response.speak(speechOutput);
this.emit(':responseReady');
},
'SessionEndedRequest': function () {
var speechOutput = "";
this.response.speak(speechOutput);
this.emit(':responseReady');
},
'AMAZON.FallbackIntent': function () {
console.log('AMAZON FALL BACKINTENT');
},
'Unhandled': function () {
console.log("Unhandled");
},
};
exports.handler = (event, context) => {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
// To enable string internationalization (i18n) features, set a resources object.
//alexa.resources = languageStrings;
alexa.registerHandlers(handlers);
alexa.execute();
};
// END of Intent Handlers {} ========================================================================================
// 3. Helper Function =================================================================================================
function delegateSlotCollection() {
console.log("in delegateSlotCollection");
console.log("current dialogState: " + this.event.request.dialogState);
if (this.event.request.dialogState === "STARTED") {
var updatedIntent = this.event.request.intent;
this.emit(":delegate", updatedIntent);
} else if (this.event.request.dialogState !== "COMPLETED") {
console.log("in not completed");
this.emit(":delegate")
} else {
if (this.event.request.intent.confirmationStatus === 'CONFIRMED'){
this.emit('addOrder');
}
return this.event.request.intent;
}
}
function delegateSlotCollectionSecond() {
console.log("in delegateSlotCollection");
console.log("current dialogState: " + this.event.request.dialogState);
if (this.event.request.dialogState === "STARTED") {
var updatedIntent = this.event.request.intent;
this.emit(":delegate", updatedIntent);
} else if (this.event.request.dialogState !== "COMPLETED") {
console.log("in not completed");
this.emit(":delegate")
} else {
if (this.event.request.intent.confirmationStatus === 'CONFIRMED'){
Console.log("Vegeta");
console.log(this.event.request.intent.confirmationStatus);
}
return this.event.request.intent;
}
}
This Is the code that i am using so when first createOrder Dialogue is completed it ask for confirmation and when i say yes than add order is emited but its dialogue directive didnt work it directly emits the statement so how to solve tghis problem ?
'createOrder': function () {
this.emit(':ask','tell me item name');
},
'productIntent': function(){
this.event.request.intent.slots.product.value //have an intent and slot for product
this.attributes['anyName'] = "product"; put product in session
this.emit(':ask','tell me quantity');
}
'quantityIntent': function(){
this.event.request.intent.slots.quantity.value //have an intent and slot for quality
this.attributes['anyName'] = "quantity"; put quantity in session
this.emit(':ask','do you want to add more item');
}
'Amazon.yesIntent': function () {
this.emit("createOrder"); //repeat
},
//handle no intent by retrieving all data and making your order
let retriveddata = this.attributes['anyName'];
You get the idea.
This way you won't lose the data between intents unless the session ends.
{
"interactionModel": {
"languageModel": {
"invocationName": "hello order",
"intents": [
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "CreateOrder",
"slots": [],
"samples": []
},
{
"name": "ProductIntent",
"slots": [
{
"name": "productType",
"type": "products"
}
],
"samples": [
"{productType}"
]
},
{
"name": "QuanityIntent",
"slots": [
{
"name": "quantiyValue",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"{quantiyValue}"
]
},
{
"name": "AMAZON.YesIntent",
"samples": []
},
{
"name": "AMAZON.NoIntent",
"samples": []
}
],
"types": [
{
"name": "products",
"values": [
{
"name": {
"value": "burger"
}
},
{
"name": {
"value": "pizza"
}
}
]
}
]
}
}
}

Categories