I’ve made a dialog using vu- material, everything works fine but I have a form inside the dialog which I want to clean every time I open the dialog, the problem is if I don’t change page, all my v-model inside the dialog keep the data I have before closing the dialog.
I tried using created / mounted / updated hook to clean my v-model but these hooks don’t get triggered when I don’t change page.
Here's a part of my code :
Toolbarnav.vue :
<template>
...
<md-dialog md-open-from="#uploadBtn" ref="uploadDialog">
<md-dialog-title>Upload file</md-dialog-title>
<md-dialog-content>
<upload-dialog></upload-dialog>
</md-dialog-content>
<md-dialog-actions class="md-align-center">
<md-button class="md-primary" #click.native="closeUploadDialog()">Fermer</md-button>
</md-dialog-actions>
</md-dialog>
...
</template>
<script>
...
openUploadDialog() {
this.$refs.uploadDialog.open();
},
closeUploadDialog() {
this.$refs.uploadDialog.close();
},
...
</script>
UploadDialog.vue :
<template>
...
<md-input-container>
<label>Nom</label>
<md-input id="nom" v-model="nom"></md-input>
</md-input-container>
<md-input-container>
<label>Prénom</label>
<md-input id="prenom" v-model="prenom"></md-input>
</md-input-container>
<md-input-container>
<label>Email</label>
<md-input id="email" v-model="email"></md-input>
</md-input-container>
...
</template>
<script>
...
name: upload-dialog',
data() {
return {
files: [],
nom: '',
prenom: '',
email: '',
};
},
...
created() {
this.files = [];
this.nom = '';
this.prenom = '';
this.email = '';
},
</script>
Any idea to do this ?
Thanks
You can use one of the opening/closing events:
md-opened
md-closed
And do the cleaning there, instead of in the created/mounted lifecycle events.
Example:
<md-dialog ... #md-opened="myOpenMethod" #md-closed="myCloseMethod">
new Vue({
...
methods: {
myOpenMethod() { console.log('opened'); },
myCloseMethod() { console.log('closed'); }
},
...
})
See demo CodePen here.
Forcing recreation of the dialog component
If you can only change Toolbarnav.vue, you can use :key to force the recreation of the <upload-dialog>. Steps below.
First create a uploadDialogKey: 1 in data:
data() {
return {
...
uploadDialogKey: 1
}
},
Add key to the declaration of the dialog:
<upload-dialog :key="uploadDialogKey"></upload-dialog>
Now everytime you change the uploadDialogKey property, Vue will recreate the component.
Since you want to recreate the dialog on openUploadDialog(), change it to:
openUploadDialog() {
this.uploadDialogKey++; // <=== added this line
this.$refs.uploadDialog.open();
},
Demo CodePen of this solution here.
Related
I’m using the following component for custom clic-edit input
<template>
<el-input
v-show="edit"
ref="inputField"
type="text"
placeholder="place"
v-model='value'
#blur.native="
local = $event.target.value;
edit = false;
$emit('input', local);
"
#keyup.enter.native="
local = $event.target.value;
edit = false;
$emit('input', local);"
/>
<span v-show="!edit" #click="startEdit" > {{ local }}</span>
</template>
<script>
export default {
props: ["value"],
data() {
return {
edit: false,
local: this.value,
};
},
watch: {
value: function () {
this.local = this.value;
},
},
methods: {
startEdit() {
this.edit = true;
this.$refs.inputField.focus();
},
},
};
</script>
I am then using it, everything's seems ok.
Text switch to input as expected.
But If I pass a null value then the rendered component disappears.
Why ? How can I avoid this ?
<template>
<ClickEdit :value="'velue-test '" />
</template>
When passing or validating an empty value, the component disappears.
Actually your components is being rendered (you can see in devtools console). But because you pass empty or null as value prop to component there is nothing to show for editing. So you must set a default text to show in case of empty value (for example Edit here...). Then after user update the input replace it.
You can see this codesandbox to understand what I mean.
Say you have 3 components:
<Modal>
<Navbar>
<Hero>
Your Modal component has data saying whether it's open or not, along with the appropriate methods:
data() {
return {
active: false,
}
},
methods: {
open() {this.active = true},
close() {this.active = false},
switch() {this.active ? this.close(): this.open()}
}
and you want a link in your Navbar component to be able to open it:
template:
/*html*/
`<nav class="navbar">
<router-link :to="etc.">Home</router-link>
<router-link :to="etc.">About</router-link>
<a #click="openSiblingModalSomehow">Contact</a> <!-- This one -->
</nav>`
As well as the Call to Action button on your Hero component:
template:
/*html*/
`<div class="hero">
<h1>Hello, World</h1>
<button #click="openSiblingModelSomehow">Contact Me</button>
</div>`
Assuming you DON'T want a global property to access this... For example, what if you want more than one type of modal?:
<ContactModal>
<SignUpModal>
<OtherModal>
<Navbar>
<Hero>
and knowing that the Modal also needs to be able to close itself,
How would you trigger a specific sibling element / component to open the Modal (in this case, let's say ContactModal) using Vue 3?
I thought about using a variable on the App itself, but it seems a bit hectic to change a globalProperty only for a specific component with it's own data.
I had a similar challenge at my project. My approach was to not use a Boolean property.
Step by step, first declare a empty string at the parent, that provides it for your modal boxes:
data() {
return {
active: ""
}
}
Declare a method, that handles that string:
methods: {
switchActive(string) {
if (string) {
this.active = string;
}
else {
this.active = ""
}
}
}
This would be one of your modal components:
<template>
<Dialog header="Header" footer="Footer" :visible="checkActive">
I am the modal dialog.
<button #click="this.$emit('close')">Close Me</button>
</Dialog>
</template>
<script>
export default {
name: "modal-123",
props: {
active: String
},
computed: {
checkActive() {
return this.active === this.$options.name;
}
}
}
</script>
And call this component:
<modal :active="active" #close="switchActive('')"></modal>
If you want to open one of your modal boxes, you call switchActive with the name property of your modal box.
So I pass value using [props] and stored it in child component's data. However, when passing [props] value changes from parent, it's not updating in child component's data. Is there a fix for this..?
Here is the link to w3 test (I tried to clarify the problem as much as possible here)
<div id='app'>
<div id='parent'>
<button #click='current_value()'>Click to see parent value</button>
<br><br>
<button #click='change_value($event)'>{{ txt }}</button>
<br><br>
<child-comp :test-prop='passing_data'></child-comp>
</div>
<br><br>
<center><code>As you can see, this methods is <b>NOT</b> reactive!</code></center>
</div>
<script>
new Vue({
el: "#parent",
data: {
passing_data: 'Value',
txt: 'Click to change value'
},
methods: {
current_value(){
alert(this.passing_data);
},
change_value(e){
this.passing_data = 'New Vaule!!';
this.txt = 'Now click above button again to see new value';
e.target.style.backgroundColor = 'red';
e.target.style.color = 'white';
}
},
components: {
"child-comp": {
template: `
<button #click='test()'>Click here to see child (stored) value</button>
`,
props: ['test-prop'],
data(){
return {
stored_data: this.testProp
}
},
methods: {
test(){
alert(this.stored_data);
}
},
watch: {
stored_data(){
this.stored_data = this.testProp;
}
}
}
}
});
Props have one way data flow, that's why it doesn't react when you update it from the parent component. Define a clone of your prop at data to make it reactive, and then you can change the value within the child component.
Short answer: you don't need stored_data. Use alert(this.testProp) directly.
Long answer: when child component is created, stored_data get it's value from this.testProp. But data is local, it won't change automatically. That's why you need to watch testProp and set it again. But is not working because of a simple mistake, your watch should be:
watch: {
testProp(){ // here was the mistake
this.stored_data = this.testProp;
}
}
I would like to retrieve all input values from my child components (client and advice, seen below), but not sure how to proceed.
client.vue
<template>
<div id="client">
<input type="text" v-model="client.name" />
<input type="text" v-model="client.code" />
</div>
</template>
<script>
export default {
data() {
return {
client: {
name: '',
code: '',
}
}
}
}
</script>
advice.vue
<template>
<div id="advice">
<input type="text" v-model="advice.foo" />
<input type="text" v-model="advice.bar" />
<div v-for="index in 2" :key="index">
<input type="text" v-model="advice.amount[index]" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
advice: {
foo: '',
bar: '',
amount:[]
}
}
}
}
</script>
Each component has more fields than the above example.
My home page (parent) looks as simple as:
<template>
<form id="app" #submit="printForm">
<clientInfo />
<advice />
<input type="submit" value="Print" class="btn" />
</form>
</template>
<script>
import clientInfo from "#/components/clientInfo.vue";
import advice from "#/components/advice.vue";
export default {
components: {
clientInfo,
advice
},
methods: {
printForm() {}
}
}
</script>
My first idea was to $emit, but not sure how to do that efficiently with more than 20 fields without attaching a #emitMethod="parentEmitMethod" to every single field.
My second idea was to have a Vuex store (as seen below), but I don't know how to save all the states at once and not sure if I should.
new Vuex.Store({
state: {
client: {
name:'',
code:''
},
advice: {
foo:'',
bar:'',
amount:[]
}
}
})
You could use FormData to get the values of the form's <input>s or <textarea>s that have a name attribute (anonymous ones are ignored). This works even if the form has nested Vue components that contain <input>s.
export default {
methods: {
printForm(e) {
const form = e.target
const formData = new FormData(form) // get all named inputs in form
for (const [inputName, value] of formData) {
console.log({ inputName, value })
}
}
}
}
demo
You could use v-model with your custom components. Let's say you want to use them like this:
<advice v-model="adviceData"/>
For this, you would need to watch for value changes on your input elements inside your advice component and then emit an input event with the values. This will update the adviceData binded property. One generic way to do this could be including a watcher inside your advice component, like this:
export default {
data() {
return {
advice: {
foo: '',
bar: '',
amount:[]
}
}
},
watch: {
advice: {
handler: function(newValue, oldValue) {
this.$emit('input', newValue);
},
deep: true,
}
},
}
This way you will not have to add a handler for each input field. The deep option must be included if we need to detect changes on nested data in the advice object.
I think you can achieve what you want is when the user writes something using#change this will trigger a method when the input value is changed, you could use a button instead or anything you want, like this:
The child component
<input type="text" v-model="advice.amount[index]" #change="emitCurrStateToParent ()"/>
You gotta add #change="emitCurrStateToParent ()" in every input you have.
emitCurrStateToParent () {
this.$emit("emitCurrStateToParent", this.advice)
}
Then in you parent component
<child-component v-on:emitCurrStateToParent="reciveDataFromChild ($event)"></child-component>
reciveDataFromChild (recivedData) {
// Do something with the data
}
I would use a button instead of #change, like a "Save" button idk, the same goes for vuex, you can use the #change event
saveDataAdviceInStore () {
this.$store.commit("saveAdvice", this.advice)
}
Then in the store
mutations: {
saveAdvice (state, advice) {
state.advice = advice
}
}
I'm using vue-js 2.3 and element-ui. Since the update 2.3 of vue-js and the reintroduction of sync, things have changed and I have had a hard time figuring out my problem.
Here is the jsfiddle : https://jsfiddle.net/7o5z7dc1/
Problem
The dialog is only opened once. When I close it I have 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:
"showDialog"
Questions
What am I doing wrong?
How can I fix the current situation?
EDIT
If I'm creating a data I manage to remove the error message but the dialog does not close anymore
<div id="app">
<left-panel v-on:show="showDialog = true">></left-panel>
<popup v-if="showDialog":show-dialog.sync="showDialog"></popup>
</div>
<template id="left-panel-template">
<button #click="$emit('show')">Show Component PopUp</button>
</template>
<template id="popup">
<el-dialog :visible.sync="showDialog" #visiblechange="updateShowDialog">I am the popup</el-dialog>
</template>
Vue.component('left-panel', {
template: '#left-panel-template',
methods: {
},
});
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
},
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
methods: {
}
});
You avoid the mutation warning by making a copy of the prop and mutating that instead of mutating the property directly.
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
},
},
});
I also changed your template to handle the visible-change event correctly.
<el-dialog :visible.sync="show" #visible-change="updateShowDialog">I am the popup</el-dialog>
Updated fiddle.