I'm trying to pass a prop from my drop down button component:
<template>
<div>
<p #click="toggleActive">Open Drop Down</p>
<drop-down :active="this.active"></drop-down>
</div>
</template>
<script>
export default {
data() {
return {
active: false,
}
},
methods: {
toggleActive() {
return this.active = ! this.active;
}
}
}
</script>
To my drop down component:
<template>
<div class="drop-down" v-if="this.passedActive">
<p #click="toggleActive">Close drop down</p>
....
<script>
export default {
props: ['active'],
data() {
return {
passedActive: this.active,
}
},
methods: {
toggleActive() {
return this.passedActive = ! this.passedActive;
}
}
}
</script>
The idea is that I can activate the drop down component from it's parent, and then inside the drop down component I can modify this prop and deactivate the drop down - as if someone is pressing an 'x' inside the component.
I've checked the docs and this does appear to be the correct way to do it, but for some reason it's not working.
The code below works. As noted in the comments under your question, passedActive is initialized once. The parent controls the initial state (only), and the child itself controls any subsequent state. If you start with it false, it never gets to become true, because the controller is never displayed.
That is a design flaw: there should be one data item that controls it, not two. The child component should rely on its prop, and its toggle function should emit an event that the parent handles.
new Vue({
el: '#app',
data: {
active: true
},
methods: {
toggleActive() {
console.log("Toggling");
this.active = !this.active;
}
},
components: {
dropDown: {
props: ['active'],
data() {
return {
passedActive: this.active,
}
},
methods: {
toggleActive() {
return this.passedActive = !this.passedActive;
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<p #click="toggleActive">Open Drop Down {{active}}</p>
<drop-down :active="active" inline-template>
<div class="drop-down" v-if="this.passedActive">
<p #click="toggleActive">Close drop down</p>
</div>
</drop-down>
</div>
Related
When a button is clicked I wish to push it's name to an array. When the button is not clicked I want to remove it's name from an array.
I know how to do this with an #click that pushes/splices the array.
I would like to know if there's a simple way of binding the clicks of the button to the array, just like how a checkbox works with v-model. I understand you cannot use v-model on a button but if we were to make the button it's own component and use v-model on that...
<custom-button v-model="myArray"></custom-button>
Is there a way to make this work?
I would create the structure for the custom-button components like:
...,
props: {
originalArray: {
required: true
}
},
data(){
return {
modifiedArray: this.originalArray.map(x => ({...x}))
}
},
methods: {
yourMethod()
{
//do your logic on the modifiedArray
this.$emit('changed',this.modifiedArray);
}
}
then you could use it like:
<custom-button :original-array="this.myArray" #changed="newArray => this.myArray = newArray" />
I would do it like this:
const CBtn = {
template: '#c-btn',
props: ['array', 'label'],
data(){
return {
ncTimeout: -1
}
},
computed:{
arr_proxy: {
get(){
// shallow copy to not modify parent array indices
return this.array.slice()
}
}
},
methods: {
update(){
this.notClicked()
if(!this.arr_proxy.includes(this.label))
this.$emit('update:array', this.arr_proxy.concat(this.label))
},
notClicked(){
clearTimeout(this.ncTimeout)
this.ncTimeout = setTimeout(()=>{
let index = this.arr_proxy.findIndex(v => v === this.label)
if(index>=0){
this.arr_proxy.splice(index, 1)
this.$emit('update:array', this.arr_proxy)
}
}, 1000)
}
}
}
new Vue({
components: {
CBtn
},
template: '#main',
data(){
return {arr: []}
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="c-btn">
<button
#click="update"
v-on="$listeners"
v-bind="$attrs"
>
{{label}}
</button>
</template>
<template id="main">
<div>
<c-btn label="1" :array.sync="arr" ></c-btn>
<c-btn label="2" :array.sync="arr" ></c-btn>
<c-btn label="3" :array.sync="arr" ></c-btn>
{{arr}}
<div>
</template>
<div id="app"></div>
So yes you can use v-model with in model option defined value: [propName] and event: [eventName] or the .sync modifier with 'update:[propName]' event.
I have a beginner question about sync modifier in vuejs. In my example, i want to change the value of inputs depending on focus event. The value is an Object inputsData and i'm getting it from the app. In parent i'm passing it to the child where it is rendering. I set an timer because i want to emit an server request. As you can see in the handleFocusFromChild Methode it change me the dataToBeChanged with newData (se also log after 4 seconds). As i understood from vue guid it should to update also the input value but it doens't, and i don't understand why, because dataToBeChanged have now the new values from newData. Can someone explain me why and how should i do to make it work?
Here i'm using the the Parent:
import Parent from "./parent.js";
Vue.component("app", {
components: {
Parent
},
template: `
<div>
<parent :inputsData="{
'firstElement':{'firstInputValue':'Hi there'},
'secondElement':{'secondInputValue':'Bye there'}
}"></parent>
</div>
`
});
Here is the Parent:
import Child from "./child.js";
export default {
name: "parent",
components: {
Child
},
props: {
inputsData: Object
},
template: `
<div>
<child #focusEvent="handleFocusFromChild"
:value.sync="inputsData.firstElement.firstInputValue"></child>
<child #focusEvent="handleFocusFromChild"
:value.sync="inputsData.secondElement.secondInputValue"></child>
</div>
`,
computed: {
dataToBeChanged: {
get: function() {
return this.inputsData;
},
set: function(newValue) {
this.$emit("update:inputsData", newValue);
}
}
},
methods: {
handleFocusFromChild: function() {
var newData = {
firstElement: { firstInputValue: "Hi there is changed" },
secondElement: { secondInputValue: "Bye there is changed" }
};
setTimeout(function() {
this.dataToBeChanged = newData;
}, 3000);
setTimeout(function() {
console.log(this.dataToBeChanged);
}, 4000);
}
}
};
Here is the child:
export default {
template: `
<div class="form-group">
<div class="input-group">
<input #focus="$emit('focusEvent', $event)"
v-model="value">
</div>
</div>
`,
props: {
value: String
}
};
you child component should emit "this.$emit('update:value', newValue)" as event
take a look over the docs: https://br.vuejs.org/v2/guide/components-custom-events.html
Also a way to do it is like this:
export default {
template: `
<div class="form-group">
<div class="input-group">
<input #focus="$emit('focusEvent', $event)"
v-model="valueProp">
</div>
</div>
`,
props: {
value: String
},
computed: {
valueProp:{
get(){
return this.value
},
set(val){
return this.$emit("update:value", val);
}
},
}
methods: {
handleFocus() {
this.$emit("focusEvent");
}
}
};
I have a complex modal that I've put in it's own component. It works on a copy of the passed model, to allow the user to cancel the action.
<template>
<b-modal
v-if="itemCopy"
v-model="shown"
#cancel="$emit('input', null)"
#ok="$emit('input', itemCopy)"
>
<!-- content -->
</b-modal>
</template>
<script>
export default {
props: {
value: Object
},
data() {
return {
shown: false,
itemCopy: null
};
},
watch: {
value(itemToDisplay) {
this.shown = !!itemToDisplay;
this.initValue();
},
item(it) {
this.initValue();
}
},
methods: {
initValue() {
this.itemCopy = _.cloneDeep(this.value);
}
}
};
</script>
The Idea to communicate with it is to pass an object in the v-model and, if set, the modal will be shown using that data, and when done, the new state will be communicated back over the v-model as well.
That is, if the user cancel/closed the modal, the v-modal variable will be null, otherwise it will be a new Model that will replace the one in v-modal.
<template>
<!-- omitted for brevity -->
<ItemModal v-model="modalItem" />
<template>
<script>
//...
export default {
data() {
return {
itemNumber: null
};
},
computed: {
modalItem:{
get() {
if (this.itemNumber != null) return this.entries[this.itemNumber];
},
set(newItem) {
if (newItem && this.itemNumber) {
//splice, etc.
}
// in any clase reset the selection to close the modal
this.itemNumber = null;
}
},
//...
<script>
The Problem I have is with the events from b-modal. I can use the #ok but there's no #notOk.
For example #cancel won't be thrown if the user click outside of the modal.
How can this be achieved? Is there another more easier way of doing this?
b-modal emits a generic hide event, which receives as its first argument the trigger that closed the modal (i.e. ok, cancel, esc, backdrop, etc):
<template>
<b-modal
v-if="itemCopy"
v-model="shown"
#hide="handleHide"
>
<!-- content -->
</b-modal>
</template>
<script>
export default {
// ...
methods: {
// ...
handleHide(bvEvt) {
if (bvEvt.trigger === 'ok') {
// User clicked OK button
this.$emit('input', this.itemCopy)
} else {
// The modal was closed not via the `ok` button
this.$emit('input', null)
}
}
}
};
</script>
<template>
<b-modal
:id="id"
#ok="$emit('ok', item)"
>
<!-- content -->
</b-modal>
</template>
<script>
export default {
props: {
item: Object,
id: String
}
};
</script>
<template>
<!-- omitted for brevity -->
<ItemModal :item="modalItem" :id="modalId" #ok="onModalOk" />
<template>
<script>
//...
export default {
data() {
return {
modalId: "myItemModal"
itemNumber: null
modalItem: null
};
},
methods: {
showItemModal(itemNumber) {
this.itemNumber = itemNumber
this.modalItem = _.cloneDeep(this.entries[itemNumber])
this.$bvModal.show(this.modalId)
},
onModalOk(newItem) {
if (newItem && this.itemNumber) {
//splice, etc.
}
}
}
//...
<script>
I have component, inside it I am doing emiting:
methods: {
sendClick(e)
{
bus.$emit('codechange', this.mycode);
console.log(this.selectedLanguage);
this.$parent.sendCode();
this.$parent.changeView();
}
}
In the parent component I am hadling data:
var app = new Vue({
el: '#app',
data: {
currentView: 'past-form',
mycode: ''
},
methods:
{
changeView()
{
console.log(this.mycode);
},
sendCode()
{
console.log("Code is sended to server");
this.currentView = 'view-form';
bus.$emit('codechange', this.mycode);
}
},
created()
{
bus.$on('codechange', function(mycode){
console.log("test"); // this works
this.mycode = mycode; // this works
}.bind(this));
}
})
Handling in parent work fine. But on clicking on sendCode() I want to send data to third component. The third component code:
Vue.component('view-form', {
template: `
<div class="ViewCodeContainer">
<div class="ViewCode">my code here</div>
<code> {{mycode}} </code>
<div class="ViewCodeMenu">my menu here</div>
</div>`,
data() {
return {
mycode: ''
}
},
created()
{
bus.$on('codechange', function(mycode){
console.log("hererere");
console.log(mycode);
this.mycode = mycode;
}.bind(this));
console.log("test");
}
})
But handling of code does not working. Block console.log("hererere"); is not executed. What I am doing wrong?
#Wostex is correct in this case. Essentially, your view-form component doesn't exist when the event is emitted. It doesn't exist until you change the view, which you are doing in the event handler. So there is no way for it to receive the event because your handler doesn't exist.
If your dynamic component is a child of the parent, just pass the code as a property.
<component :is="currentView" :mycode="mycode"></component>
And update your view-form component.
Vue.component('view-form', {
props:["mycode"],
template: `
<div class="ViewCodeContainer">
<div class="ViewCode">my code here</div>
<code> {{code}} </code>
<div class="ViewCodeMenu">my menu here</div>
</div>`,
data() {
return {
code: this.mycode
}
}
})
Here is a working example.
I'm building a SPA with a scroll navigation being populated with menu items based on section components.
In my Home.vue I'm importing the scrollNav and the sections like this:
<template>
<div class="front-page">
<scroll-nav v-if="scrollNavShown" #select="changeSection" :active-section="activeItem" :items="sections"></scroll-nav>
<fp-sections #loaded="buildNav" :active="activeItem"></fp-sections>
</div>
</template>
<script>
import scrollNav from '.././components/scrollNav.vue'
import fpSections from './fpSections.vue'
export default {
data() {
return {
scrollNavShown: true,
activeItem: 'sectionOne',
scrollPosition: 0,
sections: []
}
},
methods: {
buildNav(sections) {
this.sections = sections;
console.log(this.sections)
},
changeSection(e) {
this.activeItem = e
},
},
components: {
scrollNav,
fpSections
}
}
</script>
this.sections is initially empty, since I'm populating this array with data from the individual sections in fpSections.vue:
<template>
<div class="fp-sections">
<keep-alive>
<transition
#enter="enter"
#leave="leave"
:css="false"
>
<component :is="activeSection"></component>
</transition>
</keep-alive>
</div>
</template>
<script>
import sectionOne from './sections/sectionOne.vue'
import sectionTwo from './sections/sectionTwo.vue'
import sectionThree from './sections/sectionThree.vue'
export default {
components: {
sectionOne,
sectionTwo,
sectionThree
},
props: {
active: String
},
data() {
return {
activeSection: this.active,
sections: []
}
},
mounted() {
this.buildNav();
},
methods: {
buildNav() {
let _components = this.$options.components;
for(let prop in _components) {
if(!_components[prop].hasOwnProperty('data')) continue;
this.sections.push({
title: _components[prop].data().title,
name: _components[prop].data().name
})
}
this.$emit('loaded', this.sections)
},
enter(el) {
twm.to(el, .2, {
autoAlpha : 1
})
},
leave(el, done) {
twm.to(el, .2, {
autoAlpha : 0
})
}
}
}
</script>
The buildNav method loops through the individual components' data and pushes it to a scoped this.sections array which are then emitted back to Home.vue
Back in Home.vue this.sections is populated with the data emitted from fpSections.vue and passed back to it as a prop.
When I inspect with Vue devtools the props are passed down correctly but the data does not update.
What am I missing here? The data should react to props when it is updated in the parent right?
:active="activeItem"
this is calld "dynamic prop" not dynamic data. You set in once "onInit".
For reactivity you can do
computed:{
activeSection(){ return this.active;}
}
or
watch: {
active(){
//do something
}
}
You could use the .sync modifier and then you need to emit the update, see my example on how it would work:
Vue.component('button-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
props: ['counter'],
watch: {
counter: function(){
this.$emit('update:counter',this.counter)
}
},
})
new Vue({
el: '#counter-sync-example',
data: {
foo: 0,
bar: 0
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="counter-sync-example">
<p>foo {{ foo }} <button-counter :counter="foo"></button-counter> (no sync)</p>
<p>bar {{ bar }} <button-counter :counter.sync="bar"></button-counter> (.sync)</p>
</div>