How to make sure all radiobox questions have been interacted with - javascript

A small app where user needs to select either Yes or No for each task. The user should NOT be allowed to leave the page unless they have answered all questions. I have added a button at the bottom of the form which simulates beforeRouterLeave behaviour.
My question is about the simulateBeforeRouteLeave() function. How can do I check if all questions have been answered to allow user to leave?
Ps. This is a snipped from a much larger project. The original data comes from a API and its NOT hard coded like in this example.
CodeSandbox
App.vue
<template>
<parent-component
v-for="task in tasks.data"
:key="task.id"
:taskData="task"
#update-form-data="handleChange"
>
</parent-component>
<button
class="my-2 border-2 border-red-500"
#click="simulateBeforeRouteLeave"
>
simulateBeforeRouteLeave
</button>
</template>
<script>
import parentComponent from "./components/parentComponent.vue";
export default {
components: {
parentComponent,
},
methods: {
simulateBeforeRouteLeave() {
//Need HELP Here! How do I write this IF statement?
if ("Options_NOT_Selected") {
alert("Please select all options");
} else {
console.log("Allow user to leave 'next()'");
}
},
handleChange(e) {
const objIndex = this.tasks.data.findIndex((obj) => obj.id === e.id);
this.tasks.data[objIndex].status = e.status;
},
},
data() {
return {
markAll: false,
tasks: {
data: [
{
id: 1,
name: "Task 1",
status: null,
},
{
id: 2,
name: "Task 2",
status: null,
},
{
id: 3,
name: "Task 3",
status: null,
},
],
},
};
},
};
</script>
parentComponent.vue
<template>
<div class="py-2">
<div>
<input
type="radio"
:id="taskData.id + 'Yes'"
:name="taskData.name"
:value="taskData.value"
#change="updateData"
/>
<label :for="taskData.id">{{ taskData.name }} Yes</label>
</div>
<div>
<input
type="radio"
:id="taskData.id + 'No'"
:name="taskData.name"
:value="taskData.value"
/>
<label :for="taskData.id">{{ taskData.name }} No</label>
</div>
</div>
</template>
<script>
export default {
props: ["taskData"],
methods: {
updateData(e) {
this.$emit("update-form-data", {
id: this.taskData.id,
status: 1,
});
},
},
data() {
return {};
},
};
</script>

Related

First element always checked

I have a code, when you click on a new task, the first one is crossed out, I can’t understand why.
When you click on a new task, the first one is crossed out, and the one I click on should be
<template>
<h4>Today Tasks</h4>
<div class="tasks">
<div
class="task"
:class="{ completed: task.isCompleted }"
v-for="task in tasks"
:key="task._id"
#click="completedHandler(task._id)"
>
<div
class="round"
:class="{
business: task.type === 'business',
personal: task.type === 'personal',
}"
></div>
<span>{{task.name}}</span>
</div>
</div>
<div class="add-task">
<input type="text" placeholder="Название задачи" v-model="taskName" />
<button #click="addTask">+</button>
</div>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
data(){
return {
taskName: "",
tasks: [{
_id: "wadawdawdwa",
name: "Create to do app in Vue js",
isCompleted: false,
type: "personal",
}
]
}
},
methods:{
completedHandler:function(taskid){
const currentTask=this.tasks.find((t)=> t._id=taskid);
currentTask.isCompleted = !currentTask.isCompleted;
},
addTask: function(){
this.tasks.push({
_id: Math.random().toString(36).substring(2, 7),
name: this.taskName,
isCompleted: false,
type: "personal",
});
},
},
};
</script>

How to iterate array value by comparing it with another array in Vuejs?

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

Vue.js find which component emitted an event

I'm trying to have a component representing a shopping item.
I'll have one of this component for every item in my shopping list.
I don't know how to update the parent data (the shopping list) when the child is edited (the shopping item)
Shopping List
<template>
<div id="app">
<shopping-item
v-for="(item, index) in shoppingList"
:key="index"
:propsName="item.name"
:propsQuantity="item.quantity"
#shoppingItemEdited="handleEdit"
></shopping-item>
</div>
</template>
<script>
import ShoppingItem from "./components/ShoppingItem.vue";
export default {
name: "App",
components: {
ShoppingItem,
},
data() {
return {
shoppingList: [
{ name: "apple", quantity: 8 },
{ name: "banana", quantity: 3 },
{ name: "kiwi", quantity: 7 },
{ name: "peach", quantity: 5 },
],
};
},
methods: {
handleEdit(itemEdited) {
// How to get the index of the shopping-item that has been updated ?
// shoppingList[???] = itemEdited
console.log(itemEdited);
// => {name: "white peach", quantity: "6"}
},
},
};
</script>
Shopping Item
<template>
<div>
<input v-model="name" placeholder="ex: banana" #change="updateParent" />
<input
v-model="quantity"
type="number"
placeholder="ex: 3"
#change="updateParent"
/>
</div>
</template>
<script>
export default {
data() {
return {
name: "",
quantity: null,
};
},
props: {
propsName: String,
propsQuantity: Number,
},
created() {
this.name = this.propsName;
this.quantity = this.propsQuantity;
},
methods: {
updateParent() {
this.$emit("shoppingItemEdited", {
name: this.name,
quantity: this.quantity,
});
},
},
};
</script>
So I have few questions:
How can I know witch component emited the event 'shoppingItemEdited' ? If I knew it, I could find out which shoppingList item I should update.
I red I should not update props in the child, so I create data based on props, is that a standard way of doing that ?
this.name = this.propsName;
this.quantity = this.propsQuantity;
Just pass an index to a handler: #shoppingItemEdited="handleEdit(index, $event)"
No it's not "standard" - created hook is called only once when component is created, so if value of prop changes later (from parent), data will not update. It's probably not a problem in your case but usually its better to use computed:
computed: {
name: {
get() { return this.propsName },
set(value) {
this.$emit("shoppingItemEdited", {
name: value,
quantity: this.quantity,
});
}
}
}
...handle event in parent and the change will propagate (by props) to a child

Live filter in custom select with input

I am trying to make a custom list filter on input using computed property. In one file I create a widget, in another I use it. Here is the code from the widget creation file:
<template>
<input value="Гарантийный случай"
v-model="searchText"
:class="{'w-autocomplete__input_completed': completed}"
ref="input"
#click="areOptionsVisible = !areOptionsVisible"/>
<div v-if="areOptionsVisible"
:style="{maxHeight: maxHeight, overflow: 'auto', zIndex: zIndex}"
class="w-autocomplete__items">
<div v-for="option in options" class="w-autocomplete__item_first" >
{{ option.name }}
<div v-for="item in option.children" class="w-autocomplete__item"
:class="{'w-autocomplete__item_active': currentIndex === item}"
#mouseenter="setActive(item)"
#keyup.up="changeCurrent('up', item)"
#keyup.down="changeCurrent('down', item)"
#click="doChoose(item)">
{{ item.name }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'dropdown',
props: {
placeholder: {
type: String,
required: false,
default: '',
},
options: {
type: Array,
default(){
return []
}
},
},
data: function () {
return {
counter: 0,
currentIndex: null,
completed: false,
chosenItem: null,
areOptionsVisible: false,
searchText: '',
data: [],
}
},
computed: {
options(){
return this.props.options.filter(elem => {
return elem.name.toLowerCase().includes(this.searchText.toLowerCase());
});
},
},
.......
}
</script>
This is how I pass the array to this list in another file:
<template>
........
<div class="complaint-form__line-item">
<div class="form-group">
<label>Гарантийный случай</label>
<dropdown :options="options" />
</div>
</div>
........
</template>
<script>
........
export default {
name: 'complaint-form',
components: {LockedImport, UploadFiles, CarNumberInput, Autocomplete, Dropdown},
props: {
......
}
},
data() {
const complaint = new Complaint();
return {
........
options: [
{name: 'Выход детали из строя в процессе эксплуатации', value: null,
children: [{name: 'Увеличение зазора, люфт (дробь/стуки)', value: 53},
{name: 'Обрыв детали', value: 54}]},
{name: 'Поломка при установке', value: null},
{name: 'Брак до установки', value: null,
children: [{name: 'Недокомплект', value: 55},
{name: 'Заводской брак (замятия, отсутствие резьбы, пробой пыльника и т.д.)',
value: 56}]},
],
}
},
Tell me please why computed doesn't work? Only I add computed and the list is not displayed at all when clicking on the field, but should. That is, it breaks down completely. I want to be filtered when entering text in input
Vue.js cannot have more than one element inside a <template> tag, so I would suggest that you enclose the whole code of the dropdown component within a <div> tag or something of the sort.
Also, and this is just a comment, I would suggest that you use the focus event for the input because with click it will still be showing even if you aren't focusing on the input.

Vuex dynamic checkboxes binding

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/

Categories