move dialog to a compontent in Vuetify - javascript

here's my code:
<v-btn #click="showDialog = true" >
<v-dialog
v-model="showDialog"
max-width="600"
>
<v-btn #click="showDialog = false">save</v-btn>
</v-dialog>
</v-btn>
I know that this is fairly simple code, but I would like to extract whole v-dialog to a component. I don't know how to manage showDialog. Should I use prop? I now I shouldn't modify prop from inside of the component. What's the other way?

You can use a function as a prop for changing value of the showDialog in the another component . ( To avoid getting error avoid mutating a prop directly )
exampleComponent.vue
<template>
<v-dialog v-model="showDialog" max-width="600">
<v-btn #click="hideDialog">save</v-btn>
</v-dialog>
</template>
<script>
export default {
props: {
showDialog : Boolean ,
hideDialog: Function,
},
};
</script>
mainFile.vue
<template>
<example :hideDialog="hideMethod" :showDialog="showDialog"></example>
</template>
<script>
export default {
data() {
return {
showDialog : false
}
},
methods: {
hideMethod() {
this.showDialog = false;
},
},
};
</script>

I've never been a fan of driving the visibility of a dialog with a reactive v-model value. Normally, dialogs need to do some amount of setup and state related things before displaying and before hiding.
So, what I do is I move showDialog to be a hidden internal value to the component itself, I put a ref= on the component, I implement an open() method on the component and call that when I want to show it.
This pattern feels more natural when a dialog is performing more complicated tasks than just showing static information.
So in your case:
<script id="myDialog" type="text/x-template">
<v-dialog
v-model="showDialog"
max-width="600"
>
<v-btn #click="save">save</v-btn>
</v-dialog>
</script>
[...]
<v-btn #click="openMyDialog">
<myDialog ref="myDialog">
</myDialog>
</v-btn>
On myDialog:
data: function () {
return {
[ other attributes ]
showDialog: false
}
},
methods: {
[ other methods ]
open: function (initializationData) {
[ initialization code ]
this.showDialog = true;
},
save: function (event) {
[ save code ]
this.showDialog = false;
}
}
On the parent component:
methods: {
[ other methods ]
openMyDialog: function (event) {
this.$refs.myDialog.open([ initialization data ]);
}
}

Related

VueJs Dialog Reusable Component with buttons different action

I am trying to build a reusable component that called v-dialog.
The idea is that I when the the dialog pop up there will be consist of 2 buttons which is called submit and cancel.
For the submit button of dialog component will be linked with different actions based on user clicks which button.
For example button A will call function name A and button B will call function name B and so on when user clicks on submit button of the dialog.
Let's say this is a component file I called DialogReusable.vue
<v-dialog
v-model="dialog"
persistent
max-width="300"
>
<v-card>
<v-card-title
class="text-h5"
style="word-break: break-word"
>
Title
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
color="green darken-1"
text
#click="dialog = false"
>
Cancel Button
</v-btn>
<v-btn
class="primary"
#click="functionSubmits()"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
And this is the parent file that I called MainParent.vue
For this file it has like 3 buttons which link to different function.
When user clicks on each button the Dialog should appears and when user clicks on the submit button of the dialog then it will call the respective function name that I set #click on each button.
<v-btn
v-if="condition"
color="primary"
dark
#click="functionA()"
>
Function A
</v-btn>
<v-btn
v-if="condition"
class="primary"
#click="functionB()"
>
Function B
</v-btn>
<v-btn
v-if="condition"
class="primary"
#click="functionC()"
>
Function C
</v-btn>
That is the concept of passing the functions through props attribute in Vue.
Let's me show you the first example of passing the function from parent into child component.
Getting a value from the parent
If you want a child component to access a parent's method, it seems obvious to just pass the method straight down as a prop.
<!-- Parent -->
<template>
<ChildComponent :method="parentMethod" />
</template>
// Parent
export default {
methods: {
parentMethod() {
// ...
}
}
}
And you child component would be like:
// Child
export default {
props: {
method: { type: Function },
},
mounted() {
// Use the parent function directly here
this.method();
}
}
Getting a value from the child
Other case you might want to get a value from child into parent component, for example the function in parent component has a param name.
<!-- Parent -->
<template>
<ChildComponent :method="parentMethod" />
</template>
// Parent
export default {
methods: {
parentMethod(valueFromChild) {
// Do something with the value
console.log('From the child:', valueFromChild);
}
}
}
Where in the child component you pass the value in when calling it:
// Child
export default {
props: {
method: { type: Function },
},
mounted() {
// Pass a value to the parent through the function
this.method("some param name");
}
}
Regarding you question, I think you can achieve by the following:
Let's say you have 3 submit buttons and each button has different action.
First let's create the reusable pop up dialog that acts like a child component.
<template>
<v-dialog
:value="dialog"
persistent
max-width="300"
#input="$emit('input', $event)"
>
<v-card>
<v-card-title
class="text-h5"
style="word-break: break-word"
>
Title Dialog
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
color="green darken-1"
text
#click="close"
>
Cancel
</v-btn>
<v-btn
class="primary"
#click="onBtnSubmit"
>
Confirm
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'Dialog',
props: {
dialog: {
type: Boolean,
required: true
},
buttonActionName: {
type: String,
required: true
},
method: {
type: Function,
required: true
}
},
methods: {
close() {
this.$emit('close-dialog')
},
onReportBtnSubmit() {
if (this.buttonActionName.length > 0) {
this.method(this.buttonActionName)
} else {
this.method()
}
this.close()
}
}
}
</script>
<style scoped>
</style>
I assumed that you understand the concept of passing the functions through props attribute in Vue.
// This dialog will pass function `firstBtnSubmit` without param name
<Dialog
:dialog.sync="firstBtnDialog"
:method="firstBtnSubmit"
button-action-name=""
#close-dialog="firstBtnDialog= false;"
/>
<v-btn
#click="showDialogFirstBtn">
firstBtnClick
</v-btn>
// This dialog will pass function `secondBtnSubmit` with param name
<Dialog
:dialog.sync="secondBtnDialog"
:method="secondBtnSubmit"
button-action-name="id1"
#close-dialog="secondBtnDialog = false;"
/>
<v-btn
#click="showDialogSecBtn">
secondBtnClick
</v-btn>
// you can create a third button below by yourself.
export default {
data() {
return {
firstBtnDialog: false,
secondBtnDialog: false,
thirdBtnDialog: false,
},
methods: {
firstBtnSubmit() {
// Do your stuff
},
secondBtnSubmit(param) {
// do your staff with param
},
thirdBtnSubmit () {
// do your stuff
},
// To trigger dialog to pop up
showDialogFirstBtn() {
this.firstBtnDialog = true;
},
showDialogSecBtn() {
this.secondBtnDialog= true;
},
showDialogThirdBtn() {
this.thirdBtnDialog= true;
}
}
}
Note
I do NOT own all contents that I wrote here.
All contents I got from different site
Concept to pass function as props
How to use reusable component in Vue: Stackoverflow

What does v-on="..." syntax mean in VueJS?

I came across a Vuetify example for the v-dialog component which has the scoped slot called activator defined as follows:
<template v-slot:activator="{ on }">
<v-btn
color="red lighten-2"
dark
v-on="on"
>
Click Me
</v-btn>
</template>
I understand the purpose of scoped slots from VueJS docs and the concept of destructuring slot props but I don't understand what the meaning of v-on="on" is in this example. In particular what it means when the event is not specified with the v-on directive?
The VueJS docs on v-on only show its usage in combination with an event name explicitly specified (eg. v-on:click="...") but there is no explanation of just using it as v-on="...".
Can someone explain this syntax and its usage in the Vuetify example?
TLDR:
basic usage
<!-- object syntax (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>]
So basically #click="..." equals v-on:click="..." equals v-on="{click:...}"
TLDR:
vuetify implementation:
genActivator () {
const node = getSlot(this, 'activator', Object.assign(this.getValueProxy(), {
on: this.genActivatorListeners(),
attrs: this.genActivatorAttributes(),
})) || []
this.activatorNode = node
return node
}
Some insight:
It is useful if you want to abstract components and pass down multiple listeners at once instead of writing multiple lines of assignments.
Consider a component:
export default {
data() {
return {
on: {
click: console.log,
contextmenu: console.log
},
value: "any key value pair"
}
}
}
<template>
<div>
<slot name="activator" :on="on" :otherSlotPropName="value" >
<defaultComponent v-on="on" />
</slot>
</div>
</template>
Given the component above, you can access the slot properties and pass them into your custom component:
<ExampleComponent>
<template v-slot:activator="{ on, otherSlotPropName }">
<v-btn
color="red lighten-2"
dark
v-on="on"
>
Click Me
</v-btn>
</template>
<ExampleComponent />
Somethimes its easier to see it in plain javascript:
Comparing the component from above - with render function instead of template:
export default {
data() {
return {
on: {
click: console.log,
contextmenu: console.log
},
value: "any key value pair"
}
},
render(h){
return h('div', [
this.$scopedSlots.activator &&
this.$scopedSlots.activator({
on: this.on,
otherSlotPropName: this.value
})
|| h('defaultComponent', {
listeners: this.on
}
]
}
}
In the source:
In case of a blank v-on="eventsObject" the method bindObjectListener will be called resulting in the assignment of the events to data.on.
This happens in the createComponent scope.
Finaly the listeners are passed as VNodeComponentOptions and updated by updateListeners.
Where Vue extends - the Vuetify implementation inspected:
When taking into account that one can join and extend vue instances, one can convince himself that any component can be reduced to a more atomic version.
This is what vuetify utilizes in the e.g. v-dialog component by creating a activator mixin.
For now one can trace down the content of on mounted by the activatable:
const simplyfiedActivable = {
mounted(){
this.activatorElement = this.getActivator()
},
watch{
activatorElement(){
// if is el?
this.addActivatorEvents()
}
},
methods: {
addActivatorEvents(){
this.listeners = this.genActivatorListeners()
},
genActivatorListeners(){
return {
click: ...,
mouseenter: ...,
mouseleave: ...,
}
},
genActivator () {
const node = getSlot(this, 'activator', Object.assign(this.getValueProxy(), {
on: this.genActivatorListeners(),
attrs: this.genActivatorAttributes(),
})) || []
this.activatorNode = node
return node
},
}
}
With above snippet all there is left is to implement this into the actual component:
// vuetify usage/implemention of mixins
const baseMixins = mixins(
Activatable,
...other
)
const sympliefiedDialog = baseMixins.extend({
...options,
render(h){
const children = []
children.push(this.genActivator())
return h(root, ...options, children)
}
})

passing a callback function via props to child component is undefined in child vue

I have a Vue component that passes a callback function to another child component via props. However, it is the only piece that is undefined in the child.
I have created a repo for this so the files can be looked at. In the file brDialog.vue, I am passing button to the function click(), which should have access to the buttons callback that was passed within props from App.vue, however it is undefined within brDialog while the other two things passed with it are present(label and data).
I'll post the brDialog file, and will post the others if needed, but figured it would be easier to link a repo than post all the different files. I'm a bit new to Vue, so possibly something I'm missing in the documentation.
If you run the repo and click the Form Test button in the header, this is where the issue is.
brDialog.vue
<template>
<v-container>
<v-layout row wrap>
<v-flex xs12>
<v-dialog
v-model="show"
width="500"
persistent
>
<v-card>
<v-card-title> {{ title }} </v-card-title>
<slot name="content"></slot>
<v-card-actions>
<v-btn
v-for="button in buttons"
:key="button.label"
small
#click.native="click(button)"
>
{{ button.label }}
</v-btn>
<v-btn
v-if="showCloseButton"
small
#click.native="closeDialog()"
>
{{ closeButtonLabel }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import { props } from './props.js'
export default {
name: 'brForm',
components: {
brTextField: () => import('#/controls/brTextField/brTextField.vue'),
brTextArea: () => import('#/controls/brTextArea/brTextArea.vue'),
brSelectList: () => import('#/controls/brSelectList/brSelectList.vue')
},
props: props,
data () {
return {
}
},
methods: {
async click (button) {
const response = await button.callback(button.data)
if (response.close) {
this.closeDialog()
}
},
closeDialog () {
this.$emit('close')
}
},
computed: {
}
}
</script>
<style>
</style>
Maybe this is something I'm missing with an $emit in Vue or something, but it seems it should be working. Can someone point out why the callback is undefined after being passed to brDialog?
callback is undefined because you define your data property (App.vue from your repo) with an arrow function and loose the Vue context on this:
data: () => {
return {
testingForm: {
//...
dialog: {
props: {
buttonCallback: this.testingFormSave, //<-- here
buttons: [
{
label: 'Save',
data: {},
callback: this.testingFormSave //<-- and here
}
]
}
}
}
}
},
To fix your issue, change data: () => {...} to data () {...}

Cannot make vue.js element-ui's dialog work while it's inside a child component

Here is the parent component:
<template lang="pug">
.wrapper
el-button(type="primary", #click="dialogAddUser = true") New User
hr
// Dialog: Add User
add-edit-user(:dialog-visible.sync="dialogAddUser")
</template>
<script>
import * as data from '#/components/partials/data'
import AddUser from './partials/AddUser'
export default {
name: 'users',
components: { AddUser },
data () {
return {
users: data.users,
dialogAddUser: false
}
}
}
</script>
Here is the child component:
<template lang="pug">
el-dialog(width="75%", title="New User", :visible.sync="dialogVisible", top="5vh")
div 'el-dialog-body' - content goes here
</template>
<script>
export default {
name: 'add-user',
props: {
dialogVisible: Boolean
}
}
</script>
I am able to open the dialog but when close the dialog using top right button inside the dialog then I am getting this error:
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"dialogVisible"
Later I tried to play and did something like below, but now I cannot even open the dialog:
<template lang="pug">
el-dialog(width="75%", title="New User", :visible.sync="visibleSync", top="5vh")
div 'el-dialog-body' - content goes here
</template>
<script>
export default {
name: 'add-user',
props: {
dialogVisible: Boolean
},
watch: {
visibleSync (val) {
this.$emit('update:dialogVisible', val)
}
},
data () {
return {
visibleSync: this.dialogVisible
}
}
}
</script>
If visible.sync works, the component is emitting an update:visible event.
So, to not mutate in the child and, instead, propagate the event to the parent, instead of:
:visible.sync="dialogVisible"
Do
:visible="dialogVisible", v-on:update:visible="visibleSync = $event"
Full code:
<template lang="pug">
el-dialog(width="75%", title="New User", :visible="dialogVisible", v-on:update:visible="visibleSync = $event", top="5vh")
div 'el-dialog-body' - content goes here
</template>
<script>
export default {
name: 'add-user',
props: {
dialogVisible: Boolean
},
watch: {
visibleSync (val) {
this.$emit('update:dialogVisible', val)
}
},
data () {
return {
visibleSync: this.dialogVisible
}
}
}
</script>
As another alternative, you could emit directly from the v-on listener and do without the visibleSync local property:
<template lang="pug">
el-dialog(width="75%", title="New User", :visible="dialogVisible", v-on:update:visible="$emit('update:dialogVisible', $event)", top="5vh")
div 'el-dialog-body' - content goes here
</template>
<script>
export default {
name: 'add-user',
props: {
dialogVisible: Boolean
}
}
</script>
I think a nice way to handle this is to:
Use a prop to pass the visible state from the parent to the child.
Forward el-dialog's close event from the child to the parent.
In the parent, handle the close event to set the prop to false.
Child:
<el-dialog :visible="visible" #close="$emit('close')">
export default {
props: {
visible: Boolean
},
...
Parent (assuming you store the open state in state.modalOpen):
<el-button #click="state.modalOpen = true">Open Modal</el-button>
<child-component :visible="state.modalOpen" #close="state.modalOpen = false" />

Why does V-select value changes on second click instead of first?

I have a V-select like below, and when I load the page it gets filled with data from my Vuex-store. I then have a computed property to get the currently selected company. My problem is that the value from the currently selected company only updates after I click on it Twice. Have I done something wrong in the implementation?
So when a user changes value in the V-select I want to update the list of users being shown, but this only works if the user clicks twice on the v-select selection.
<template>
<v-container fluid fill-height>
<v-layout child-flex>
<v-card>
<v-card-title>
<h3>Användare</h3>
<v-spacer></v-spacer>
<v-select
v-bind:items="listOfCompanys"
v-model="selectedCompany"
item-value="customerid"
item-text="name"
single-line
bottom
v-on:change="onChangeCompany"
autocomplete></v-select>
</v-card-title>
<UserTable></UserTable>
</v-card>
</v-layout>
</v-container>
</template>
<script>
import { FETCH_COMPANY_LIST, FETCH_USER_LIST } from '../store/actions.type'
import UserTable from './UserTable.vue'
export default {
name: 'Users',
data: () => ({
selectedCompany: 0
}),
components: {
UserTable
},
methods: {
onChangeCompany () {
this.$store.dispatch(FETCH_USER_LIST, this.currentCompany)
}
},
mounted: function () {
this.$store.dispatch(FETCH_COMPANY_LIST)
},
computed: {
listOfCompanys () {
return this.$store.state.users.companyList
},
currentCompany () {
return this.selectedCompany
}
}
}
</script>
Don't do both v-model and v-on:change. You're sending this.currentCompany, which I think is supposed to be selectedCompany. If the idea is to send the value when it changes, put a watch on the value, not on the widget. The widget feeds the value, the value feeds the store.

Categories

Resources