how to uncheck answered question in child vue from parent - javascript

My application is similar to a quiz. each question may have radio button answers, integers, text etc.
Consider the case for multiple choice question. I have a vue for the Radio button options. in the parent Vue i have a reset button for each of the question. If I click on the reset, it should removed the selected answer for that particular question.
How can I achieve this given that the reset button is in Parent vue and the answer to be reset is in the child Vue?
Parent:
<template>
<div class="inputContent">
<p class="lead" v-if="title">
{{title}}
<span v-if="valueConstraints.requiredValue" class="text-danger">* .
</span>
</p>
<b-alert variant="danger" show v-else>
This item does not have a title defined
</b-alert>
<!-- If type is radio -->
<div v-if="inputType==='radio'">
<Radio :constraints="valueConstraints" :init="init"
:index="index" v-on:valueChanged="sendData" />
</div>
<!-- If type is text -->
<div v-else-if="inputType==='text'">
<TextInput :constraints="valueConstraints" :init="init" v-
on:valueChanged="sendData"/>
</div>
<div class="row float-right">
<b-button class="" variant="default" type=reset #click="reset">
Reset1
</b-button>
<b-button class="" variant="default" v-
if="!valueConstraints.requiredValue" #click="skip">
Skip
</b-button>
</div>
</div>
</template>
<style></style>
<script>
import { bus } from '../main';
import Radio from './Inputs/Radio';
import TextInput from './Inputs/TextInput';
export default {
name: 'InputSelector',
props: ['inputType', 'title', 'index', 'valueConstraints',
'init'],
components: {
Radio,
TextInput,
},
data() {
return {
};
},
methods: {
skip() {
this.$emit('skip');
},
// this emits an event on the bus with optional 'data' param
reset() {
bus.$emit('resetChild', this.index);
this.$emit('dontKnow');
},
sendData(val) {
this.$emit('valueChanged', val);
this.$emit('next');
},
},
};
</script>
the child vue:
<template>
<div class="radioInput container ml-3 pl-3">
<div v-if="constraints.multipleChoice">
<b-alert show variant="warning">
Multiple Choice radio buttons are not implemented yet!
</b-alert>
</div>
<div v-else>
<b-form-group label="">
<b-form-radio-group v-model="selected"
:options="options"
v-bind:name="'q' + index"
stacked
class="text-left"
#change="sendData"
>
</b-form-radio-group>
</b-form-group>
</div>
</div>
</template>
<style scoped>
</style>
<script>
import _ from 'lodash';
import { bus } from '../../main';
export default {
name: 'radioInput',
props: ['constraints', 'init', 'index'],
data() {
return {
selected: null,
};
},
computed: {
options() {
return _.map(this.constraints['itemListElement'][0]['#list'], (v) => {
const activeValueChoices = _.filter(v['name'], ac => ac['#language'] === "en");
return {
text: activeValueChoices[0]['#value'],
value: v['value'][0]['#value'],
};
});
},
},
watch: {
init: {
handler() {
if (this.init) {
this.selected = this.init.value;
} else {
this.selected = false;
}
},
deep: true,
},
},
mounted() {
if (this.init) {
this.selected = this.init.value;
}
bus.$on('resetChild', this.resetChildMethod);
},
methods: {
sendData(val) {
this.$emit('valueChanged', val);
},
resetChildMethod(selectedIndex) {
this.selected = false;
},
},
};
</script>

One way would be to use an event bus
in your main js add:
//set up bus for communication
export const bus = new Vue();
in your parent vue:
import {bus} from 'pathto/main.js';
// in your 'reset()' method add:
// this emits an event on the bus with optional 'data' param
bus.$emit('resetChild', data);
in your child vue
import {bus} from 'path/to/main';
// listen for the event on the bus and run your method
mounted(){
bus.$on('resetChild', this.resetChildMethod());
},
methods: {
resetChildMethod(){
//put your reset logic here
}
}

Related

VueJS warning when using dynamic components and custom-events

So, I get this warning:
" Extraneous non-emits event listeners (addNewResource) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option."
And I don't understand why. I am using dynamic components with 2 custom events. I've tried adding both of the emitted events to the emits object of both components.
App.vue
<template>
<AppHeader />
<NavigationBar #select-component="selectComponent" />
<keep-alive>
<component
:is="selectedComponent"
v-bind="componentProps"
#delete-resource="deleteResource"
#add-new-resource="addNewResource"
></component>
</keep-alive>
</template>
<script>
import AppHeader from "./components/SingleFile/AppHeader.vue";
import NavigationBar from "./components/NavigationBar.vue";
import LearningResources from "./components/LearningResources.vue";
import AddResource from "./components/AddResource.vue";
export default {
components: {
AppHeader,
NavigationBar,
LearningResources,
AddResource,
},
data() {
return {
selectedComponent: "learning-resources",
learningResources: [
{
name: "Official Guide",
description: "The official Vue.js documentation",
link: "https://v3.vuejs.org",
},
{
name: "Google",
description: "Learn to google...",
link: "https://www.google.com/",
},
],
};
},
methods: {
selectComponent(component) {
this.selectedComponent = component;
},
deleteResource(name) {
this.learningResources = this.learningResources.filter(
(resource) => resource.name !== name
);
},
addNewResource(newResourceObject) {
const newResource = {
name: newResourceObject.title,
description: newResourceObject.description,
link: newResourceObject.link,
};
this.learningResources.push(newResource);
},
},
computed: {
componentProps() {
if (this.selectedComponent === "learning-resources") {
return {
learningResources: this.learningResources,
};
}
return null;
},
},
};
</script>
AddResource.vue
<template>
<base-card>
<template #default>
<form #submit.prevent>
<div>
<label for="title">Title</label>
<input type="text" v-model="newResource.title" />
</div>
<br />
<div>
<label for="description">Description</label>
<textarea rows="3" v-model="newResource.description" />
</div>
<br />
<div>
<label for="link">Link</label>
<input type="text" v-model="newResource.link" />
</div>
<button #click="$emit('add-new-resource', newResource)">
Add Resource
</button>
</form>
</template>
</base-card>
</template>
<script>
import BaseCard from "./Base/BaseCard.vue";
export default {
components: {
BaseCard,
},
emits: ["add-new-resource"],
data() {
return {
newResource: {
title: "",
description: "",
link: "",
},
};
},
};
</script>
LearningResources.vue
<template>
<base-card v-for="resource in learningResources" :key="resource.name">
<template #header>
<h3>{{ resource.name }}</h3>
<button #click="$emit('delete-resource', resource.name)">Delete</button>
</template>
<template #default>
<p>{{ resource.description }}</p>
<p><a :href="resource.link">View Resource</a></p>
</template>
</base-card>
</template>
<script>
import BaseCard from "./Base/BaseCard.vue";
export default {
components: {
BaseCard,
},
props: {
learningResources: Array,
},
emits: ["deleteResource"],
};
</script>
Seems to be because <component> is being used for two separate components, neither of which emit the same event as the other.
One thing you can try is disabling each components' attribute inheritance:
export default {
...
inheritAttrs: false
...
}
If this doesn't suit your needs, you could refactor the logic to handle both emitted events, i.e. rename the events to a shared name like "addOrDeleteResource", then determine which event is being emitted in App.vue and handle it accordingly.

Vue.js - How to dynamically bind v-model to route parameters based on state

I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"

How to create a vue modal that sends user input to another component

I am trying to create a modal component that takes in user input, and upon saving that information, is displayed within another component. For example, a user is prompted to input their first and last name respectively in a modal component (Modal.vue). Once the user saves that data (a submit method on the modal), the data is displayed on another component (InputItem.vue).
Currently, I have a CreateEvent.vue component that houses the input elements, a modal.vue component that is the modal, an EventItem.vue component, that will display what is entered on once CreateEvent is executed, an EventsList.vue component that displays all the events that a user creates and finally, app.vue which houses the Modal and Events components.
I have been able to successfully get this CRUD functionality working without the modal component, but once I add the modal, I am getting confused.
If you could help lead me in the right direction, I would appreciate that!
Modal.vue
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
Create an Event
<button
type="button"
class="btn-close"
#click="close"
aria-label="Close modal"
>
x
</button>
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input
type="text"
placeholder="add an emoji?"
v-model="newEmoji"
/>
</div>
<button #click="doneEdit">Create Event</button>
<button #click="cancelEdit">Cancel</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
I'm the default footer!
<button
type="button"
class="btn-green"
#click="close"
aria-label="Close modal"
>
Close me!
</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'modal',
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
close() {
this.$emit('close')
},
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
CreateEvent.vue
<template>
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input type="text" placeholder="add an emoji?" v-model="newEmoji" />
</div>
</template>
<script>
export default {
props: {
todos: {
type: Array
}
},
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
EventItem.vue
<template>
<div class="todo-item">
<h3 class="todo-item--left">
<!-- <span v-if="!editing" #click="editTodo" class="todo-item--label">
{{ title }}
{{ emoji }}
</span> -->
<input
class="todo-item--edit"
type="text"
v-model="title"
#click="editTitle"
#blur="doneEdit"
/>
<input
class="todo-item--edit"
type="text"
v-model="emoji"
#click="editEmoji"
#blur="doneEdit"
/>
<!-- <button #click="doneEdit">Update</button>
<button #click="cancelEdit">Cancel</button> -->
</h3>
<button class="remove-item" #click="removeTodo(todo.id)">✘</button>
</div>
</template>
<script>
export default {
name: 'todo-item',
props: {
todo: {
type: Object,
required: true
}
},
data() {
return {
id: this.todo.id,
title: this.todo.title,
emoji: this.todo.emoji,
editing: this.todo.editing,
beforeEditCacheTitle: this.todo.title,
beforeEditCacheEmoji: this.todo.emoji
}
},
methods: {
editTitle() {
this.beforeEditCacheTitle = this.title
this.editing = true
},
editEmoji() {
this.beforeEditCacheEmoji = this.emoji
this.editing = true
},
doneEdit() {
if (this.title.trim() == '') {
this.title = this.beforeEditCacheTitle
}
if (this.emoji.trim() == '') {
this.emoji = this.beforeEditCacheEmoji
}
this.editing = false
this.$emit('finishedEdit', {
id: this.id,
title: this.title,
emoji: this.emoji,
editing: this.editing
})
},
cancelEdit() {
this.title = this.beforeEditCacheTitle
this.emoji = this.beforeEditCacheEmoji
this.editing = false
},
removeTodo(id) {
this.$emit('removedTodo', id)
}
}
}
</script>
Events.vue
<template>
<div>
<transition-group
name="fade"
enter-active-class="animated fadeInUp"
leave-active-class="animated fadeOutDown"
>
<EventItem
v-for="todo in todosFiltered"
:key="todo.id"
:todo="todo"
#removedTodo="removeTodo"
#finishedEdit="finishedEdit"
/>
</transition-group>
</div>
</template>
<script>
import EventItem from '#/components/EventItem'
export default {
components: {
EventItem
},
data() {
return {
filter: 'all',
todos: [
{
id: 1,
title: 'Eat sushi',
emoji: '💵',
editing: false
},
{
id: 2,
title: 'Take over world',
emoji: '👨🏽‍💻',
editing: false
}
]
}
},
computed: {
todosFiltered() {
if (this.filter == 'all') {
return this.todos
}
}
},
methods: {
removeTodo(id) {
const index = this.todos.findIndex(item => item.id == id)
this.todos.splice(index, 1)
},
finishedEdit(data) {
const index = this.todos.findIndex(item => item.id == data.id)
this.todos.splice(index, 1, data)
}
}
}
</script>
app.vue
<template>
<div id="app" class="container">
<button type="button" class="btn" #click="showModal">
Create Event
</button>
<Modal v-show="isModalVisible" #close="closeModal" />
<Events />
</div>
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
isModalVisible: false
}
},
methods: {
showModal() {
this.isModalVisible = true
},
closeModal() {
this.isModalVisible = false
}
}
}
</script>
The modal component should emit the values instead of pushing it into the todos array. When it emits it, the parent component (App.vue) listens for the emitted items.
I would do something like this
Modal.vue
<template>
...
// header
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
...
</div>
<button #click="handleModalSubmit">Create Event</button>
...
//footer
...
</template>
<script>
export default {
...
data() {
...
},
methods: {
...,
handleModalSubmit() {
this.$emit('todos-have-been-submitted', this.todos);
},
addTodo() {
...
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
...
}
}
}
</script>
App.vue
<template>
...
<Modal
#todos-have-been-submitted="handleTodoSubmission" //watch the 'todos-have-been-submitted" emission and trigger handleTodoSubmission method when the emission is detected
/>
<Events
:todos="todos" // pass todos as a prop to the Events component
/>
...
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
...,
todos: []
}
},
methods: {
...,
handleTodoSubmission(todos) {
this.todos = [...todos];
}
}
}
</script>

How do you target a button, which is in another component, with a method in App.vue?

I'm making a basic To Do app, where I have an input field and upon entering a task and pressing the "Enter" key the task appears in the list. Along with the task the TodoCard.vue component also generates a button, which I would like to use to delete the task.
I've added a #click="removeTodo" method to the button, but don't know where to place it, in the TodoCard component or in the App.vue file?
TodoCard component:
<template>
<div id="todo">
<h3>{{ todo.text }}</h3>
<button #click="removeTodo(todo)">Delete</button>
</div>
</template>
<script>
export default {
props: ['todo'],
methods: {
removeTodo: function (todo) {
this.todos.splice(this.todos.indexOf(todo), 1)
}
}
}
</script>
App.vue:
<template>
<div id="app">
<input class="new-todo"
placeholder="Enter a task and press enter."
v-model="newTodo"
#keyup.enter="addTodo">
<TodoCard v-for="(todo, key) in todos" :todo="todo" :key="key" />
</div>
</template>
<script>
import TodoCard from './components/TodoCard'
export default {
data () {
return {
todos: [],
newTodo: ''
}
},
components: {
TodoCard
},
methods: {
addTodo: function () {
// Store the input value in a variable
let inputValue = this.newTodo && this.newTodo.trim()
// Check to see if inputed value was entered
if (!inputValue) {
return
}
// Add the new task to the todos array
this.todos.push(
{
text: inputValue,
done: false
}
)
// Set input field to empty
this.newTodo = ''
}
}
}
</script>
Also is the code for deleting a task even correct?
You can send an event to your parent notifying the parent that the delete button is clicked in your child component.
You can check more of this in Vue's documentation.
And here's how your components should look like:
TodoCard.vue:
<template>
<div id="todo">
<h3>{{ todo.text }}</h3>
<button #click="removeTodo">Delete</button>
</div>
</template>
<script>
export default {
props: ['todo'],
methods: {
removeTodo: function (todo) {
this.$emit('remove')
}
}
}
</script>
App.vue:
<template>
<div id="app">
<input class="new-todo"
placeholder="Enter a task and press enter."
v-model="newTodo"
#keyup.enter="addTodo">
<TodoCard v-for="(todo, key) in todos" :todo="todo" :key="key" #remove="removeTodo(key)" />
</div>
</template>
<script>
import TodoCard from './components/TodoCard'
export default {
data () {
return {
todos: [],
newTodo: ''
}
},
components: {
TodoCard
},
methods: {
addTodo: function () {
// Store the input value in a variable
let inputValue = this.newTodo && this.newTodo.trim()
// Check to see if inputed value was entered
if (!inputValue) {
return
}
// Add the new task to the todos array
this.todos.push(
{
text: inputValue,
done: false
}
)
// Set input field to empty
this.newTodo = ''
}
},
removeTodo: function(key) {
this.todos.splice(key, 1);
}
}
</script>

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