VueJS: Sending data value from child component to parent - javascript

I have a Vue compoment rendering inside index.vue
In index.vue we have some data :
data(){
return {
splashIsActive: true,
tutorialIsActive: false,
gameIsActive: false
}
}
and the component inside index.vue :
<SplashBox v-if="splashIsActive"/>
Then, if we go to splashBox there's a button.
<button #click="debug">....
Is there any way to create a method that after clicking the button sends, or changes splashIsActive to false, instead of true as it is stored in index.vue ?

You can achieve this by emitting an event from the button.
<button #click="$emit('updateSplashState', false)">...</button>
Then, You can capture this event in parent component.
<SplashBox v-if="splashIsActive" #updateSplashState="splashIsActive = $event"/>
Live Demo :
Vue.component('splashbox', {
template: `<button #click="$emit('update-splash-state', false)">Update</button>`
});
var app = new Vue({
el: '#app',
data: {
splashIsActive: true
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<SplashBox v-if="splashIsActive" #update-splash-state="splashIsActive = $event"></SplashBox>
</div>

You need to emit an event to the parent.
<button #click="debug">....
methods: {
debug() {
this.$emit('setSplashUnActive', false)
}
}
In the parent component.
<SplashBox v-if="splashIsActive" #setSplashUnActive="setSplashUnActive" />
methods: {
setSplashUnActive(value) {
this.splashIsActive = value
}
}

You can use a prop to pass the value from parent to child. Within the child component on pressing the key you can emit an event to update its value. To do this it is necessary to indicate the prop as sync:
<SplashBox: splashUnActive.sync="initValue"></Checkbox>
and then inside SplashBox on click of the button:
this.$emit('update:splashUnActive.', newValue)

Related

why isn't this child event updating state in parent (Vue)

I have a component called customize-charts that includes a Vuetify drawer:
<template>
<v-col>
<v-btn style="float: right" class="mr-4 mt-2" small #click="toggleCustomize" v-if="!open">Customize Dashboard</v-btn>
<v-navigation-drawer
v-model="open"
temporary
absolute
right
style="width: 25vw"
>
<span>draw contents</span>
<v-divider />
</v-navigation-drawer>
</v-col>
</template>
<script>
export default {
props: {
data: {
type: Array,
default () { return [] }
},
open: {
type: Boolean,
default () { return false }
}
},
data () {
return {
draggingItem: null
}
},
methods: {
toggleCustomize () {
this.$emit('open')
}
}
}
</script>
As you can see, the boolean that the drawer is listening to is called "open" and it is passed from the parent:
<customize-charts v-if="chartCards.length" :data="chartCards" :open="customizePanel" #updateorder="updateOrder" #toggleshow="toggleShow" #open="customizePanel=!customizePanel"/>
The parent also has the following:
{
data () {
return {
customizePanel: false,
}
}
}
My problem is that when the custom event open is called (#open="customizePanel=!customizePanel"), the drawer opens, but when it closes (user clicks outself of drawer) it does not set customizePanel to false. How can I make this happen?
Problem is you are using prop open with v-model. Props are designed as one way data binding only (passing data from parent to child) and you should not modify it's value in child component (if you open browser Dev Tools, I'm sure you will see nice warning from Vue explaining exactly this) as the new value will be overwritten by parent value on next re-render...
just use computed property for v-model:
computed: {
isOpen: {
get() { return this.open }, // return prop value
set() { this.$emit('open') } // emit event and change prop value in parent's event handler - new value gets propagated back to child
}
}

Dynamically changing props

On my app, I have multiple "upload" buttons and I want to display a spinner/loader for that specific button when a user clicks on it. After the upload is complete, I want to remove that spinner/loader.
I have the buttons nested within a component so on the file for the button, I'm receiving a prop from the parent and then storing that locally so the loader doesn't show up for all upload buttons. But when the value changes in the parent, the child is not getting the correct value of the prop.
App.vue:
<template>
<upload-button
:uploadComplete="uploadCompleteBoolean"
#startUpload="upload">
</upload-button>
</template>
<script>
data(){
return {
uploadCompleteBoolean: true
}
},
methods: {
upload(){
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
}
</script>
Button.vue:
<template>
<button
#click="onClick">
<button>
</template>
<script>
props: {
uploadComplete: {
type: Boolean
}
data(){
return {
uploadingComplete: this.uploadComplete
}
},
methods: {
onClick(){
this.uploadingComplete = false
this.$emit('startUpload')
}
</script>
Fixed event name and prop name then it should work.
As Vue Guide: Custom EventName says, Vue recommend always use kebab-case for event names.
so you should use this.$emit('start-upload'), then in the template, uses <upload-button #start-upload="upload"> </upload-button>
As Vue Guide: Props says,
HTML attribute names are case-insensitive, so browsers will interpret
any uppercase characters as lowercase. That means when you’re using
in-DOM templates, camelCased prop names need to use their kebab-cased
(hyphen-delimited) equivalents
so change :uploadComplete="uploadCompleteBoolean" to :upload-complete="uploadCompleteBoolean"
Edit: Just noticed you mentioned data property=uploadingComplete.
It is easy fix, add one watch for props=uploadComplete.
Below is one simple demo:
Vue.config.productionTip = false
Vue.component('upload-button', {
template: `<div> <button #click="onClick">Upload for Data: {{uploadingComplete}} Props: {{uploadComplete}}</button>
</div>`,
props: {
uploadComplete: {
type: Boolean
}
},
data() {
return {
uploadingComplete: this.uploadComplete
}
},
watch: { // watch prop=uploadComplete, if change, sync to data property=uploadingComplete
uploadComplete: function (newVal) {
this.uploadingComplete = newVal
}
},
methods: {
onClick() {
this.uploadingComplete = false
this.$emit('start-upload')
}
}
})
new Vue({
el: '#app',
data() {
return {
uploadCompleteBoolean: true
}
},
methods: {
upload() {
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
},
changeStatus() {
this.uploadCompleteBoolean = !this.uploadCompleteBoolean
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button #click="changeStatus()">Toggle Status {{uploadCompleteBoolean}}</button>
<p>Status: {{uploadCompleteBoolean}}</p>
<upload-button :upload-complete="uploadCompleteBoolean" #start-upload="upload">
</upload-button>
</div>
The UploadButton component shouldn't have uploadingComplete as local state (data); this just complicates the component since you're trying to mix the uploadComplete prop and uploadingComplete data.
The visibility of the spinner should be driven by the parent component through the prop, the button itself should not be responsible for controlling the visibility of the spinner through local state in response to clicks of the button.
Just do something like this:
Vue.component('upload-button', {
template: '#upload-button',
props: ['uploading'],
});
new Vue({
el: '#app',
data: {
uploading1: false,
uploading2: false,
},
methods: {
upload1() {
this.uploading1 = true;
setTimeout(() => this.uploading1 = false, Math.random() * 1000);
},
upload2() {
this.uploading2 = true;
setTimeout(() => this.uploading2 = false, Math.random() * 1000);
},
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<upload-button :uploading="uploading1" #click="upload1">Upload 1</upload-button>
<upload-button :uploading="uploading2" #click="upload2">Upload 2</upload-button>
</div>
<template id="upload-button">
<button #click="$emit('click')">
<template v-if="uploading">Uploading...</template>
<slot v-else></slot>
</button>
</template>
Your question seems little bit ambiguë, You can use watch in that props object inside the child component like this:
watch:{
uploadComplete:{
handler(val){
//val gives you the updated value
}, deep:true
},
}
by adding deep to true it will watch for nested properties in that object, if one of properties changed you ll receive the new prop from val variable
for more information : https://v2.vuejs.org/v2/api/#vm-watch
if not what you wanted, i made a real quick example,
check it out hope this helps : https://jsfiddle.net/K_Younes/64d8mbs1/

Passing custom emited events as props to a new created component in VueJs

My app consists of:
A component named
<consl :output="output" #submit-to-vue><consl>
which contains an input that calls a submit() method when enter key is pressed.
<div>
<output v-html="output"></output>
<div id="input-line" class="input-line">
<div class="prompt">{{ prompt }}</div>
<div>
<input class="cmdline" autofocus
v-model.trim="command"
#keyup.enter="submit"
:readonly="submited" />
</div>
</div>
Then the method submit() emits an event #submit-to-vue to parent method submitv() that create an instance of the same component and adds it to the DOM.
//........
methods: {
submit: function () {
this.$emit('submit-to-vue')
this.submited = true
}
},
and
//......
methods: {
submitv: function () {
var ComponentClass = Vue.extend(consl)
var instance = new ComponentClass({
propsData: { output: this.output }
})
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)
What I want to accomplish ?
I want to create a new consl component and add it to the DOM every time the old one is submited. (I want my app to emulate a terminal)
The problem
When submitted the new created component does not contain the #submit-to-vue event listener, which make it unable to recall the submitv() method.
Questions
How can I solve this problem ?
Is this the proper way to do things in VueJs or is there a more elegent way ?
In parent component, declare one data property=childs, it will includes all childs already created.
So once parent component receives the event=submit-to-vue, then add one new child to this.childs
Finally uses v-for to render these child components.
The trick: always consider the data-driven way, doesn't manipulate dom directly as possible.
below is one simple demo :
Vue.config.productionTip = false
Vue.component('child', {
template: `
<div>
<div>Label:<span>{{output}}</span></div>
<div>Value:<span>{{command}}</span></div>
<div id="input-line" class="input-line">
<div class="prompt">{{ prompt }}</div>
<div>
<input class="cmdline" autofocus
v-model.trim="command"
#keyup.enter="submit"
:readonly="submited" />
</div>
</div>
</div>`,
props: ['output'],
data() {
return {
submited: false,
command: ''
}
},
computed: {
prompt: function () {
return this.submited ? 'Already submitted, input is ready-only now' : ''
}
},
methods: {
submit: function () {
this.$emit('submit-to-vue')
this.submited = true
}
}
})
app = new Vue({
el: "#app",
data: {
childs: [{'output':'default:'}]
},
methods: {
addChild: function () {
this.childs.push({'output': this.childs.length})
}
}
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div>
<ul>
<li v-for="(child, index) in childs" :key="index">
<child :output="child.output" #submit-to-vue="addChild()"></child>
</li>
</ul>
</div>
</div>

Vue 2.0 - How passing function to child component?

I have one issue. I want to pass function link to the child component. It's working but in HTML I get that code. It's correct how improve it?
I have Vue instance
app = new Vue({
... some code
data: {
onAppClose: null,
onAppSend: null
}
})
I want to add from global window any function. Or register function in Vue instance
app.onSend = () => console.log('data')
And pass this function to child
<div id="app">
<dynamsoft-component v-if="displayComponent"
:docs="docs"
:onAppSend="onSend"
:onAppClose="onClose"
></dynamsoft-component>
</div>
But I get this HTML template in console
<div id="app">
<div onappsend="()=>{}" onappclose="function (data) {
console.warn('dwdawad')
console.log('data')
}"></div>
</div>
You example code is not making a lot of sense - do you want to add a listener not a div or pass a function to a child component?`
I assume the latter. Vue has custom events for that .
Parent template:
<div v-on:appsend="someMethod" v-on:appclose="someOtherMethod"></div>
Parent component methods:
methods: {
someOtherMethod: function (data) {
console.warn('dwdawad')
console.log('data')
},
// ...
}
And then emit form the child:
this.$emit('appclose', {id: 'whatever'} /*pass data here*/)
Edit:
I still don't see how those functions would end up directly in the template, but the real problem is: HTML is not case-sensitive. so :onAppSend becomes :onappsend. You have to use kebap-case: :on-app-send. Vue will convert it to onAppSend in the component.
I have never used Vue.js before now..
But having a look at the how to on their site, this seems to work
In Vue style guide have recommendations about props naming
https://v2.vuejs.org/v2/style-guide/#Prop-name-casing-strongly-recommended
Vue.component('dynamsoft-component', {
props: ['onAppSend'],
template: '<button v-on:click="buttonclick">click me</button>',
methods: {
buttonclick(e){
// Check if onAppSend is defined.
if(Boolean(this.onAppSend)){
this.onAppSend();
}
}
}
})
new Vue({
el: '#app',
methods: {
onSend: function(){
console.log('child clicked');
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<dynamsoft-component :on-app-send="onSend"></dynamsoft-component>
</div>

Parent onChange sets child property to true

Form (parent) component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<slot></slot>
</form>`,
methods: {
onChange:function(){
console.log("something changed");
}
}
};
and a c-tool-bar component (child) that is (if existing) inside the c-form's slot
export default {
template: `
<div class="c tool bar m006">
<div v-if="changed">
{{ changedHint }}
</div>
<!-- some other irrelevant stuff -->
</div>`,
props: {
changed: {
type: Boolean,
default: false,
},
changedHint: String
}
};
What I want to achieve is, that when onChange of c-form gets fired the function
checks if the child c-tool-bar is present && it has a changedHint text declared (from backend) it should change the c-tool-bar's prop "changed" to true and the c-bar-tool updates itself so the v-if="changed" actually displays the changedHint. I read the vue.js docs but I don't really know where to start and what the right approach would be.
You can use the two components like this:
<parent-component></parent-component>
You will be passing in the :changed prop a variable defined in the parent component in data(). Note the :, we are using dynamic props, so once the prop value is changed in the parent it will update the child as well.
To change the display of the hint, change the value of the variable being passed in as prop to the child component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<child-component :changed="shouldDisplayHint"></child-component>
</form>`,
data() {
return {
shouldDisplayHint: false,
};
},
methods: {
onChange:function(){
this.shouldDisplayHint = true; // change the value accordingly
console.log("something changed");
}
}
};
One simple way is to use $refs.
When you will create your child component inside your parent component, you should have something like: <c-tool-bar ref="child"> and then you can use it in your parent component code:
methods: {
onChange:function(){
this.$refs.child.(do whatever you want)
}
}
Hope it helps but in case of any more questions: https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs

Categories

Resources