I want the user to open and close a modal when click a button. To organize my code I use single file components.
In my single file button component HamburgerButton.vue, I have:
<template>
<button #click.prevent="onClick"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path v-if="isOpen" d="M10"/>
<path v-if="!isOpen" d="M0"/>
</svg>
</button>
</template>
<script>
export default {
data() {
return {
isOpen: false
}
},
methods: {
onClick (evt) {
this.isOpen = !this.isOpen
this.$emit('click', evt)
component
}
}
}
</script>
The registered button in my template:
<hamburger-button #click="onClicked"></hamburger-button>
My modal html:
<div :class="isOpen ? 'block' : 'hidden'" class="static">
<!--Modal-->
</div>
In my app.js with my Vue instance I have:
new Vue({
el: '#menu',
data: {
isOpen: false,
},
methods: {
onClicked() {
this.isOpen = true;
}
},
components: {
HamburgerButton,
}
})
Everything is working except the modal is not closing when I press the button again?
I tried in my template the following:
<hamburger-button #click="onClicked = !onClicked"></hamburger-button>
But that's not working?
You should pass the click event to a parent component from HamburgerButton.
Something like this:
#click="onClick"
in a script:
...
methods: {
onClick (evt) {
this.isOpen = !this.isOpen
this.$emit('click', evt) // <--- passing the event to a parent component
}
}
Related
I have an object, and also I have a button, I gonna add an event listener over the button with #click which when the user clicked on the button, those three items be true
here my codes:
<template>
<div>
<button #click="isVisible">Click</button>
</div>
</template>
<script>
export default {
data() {
return {
isVisibleItems: {
isVisibleOne: false,
isVisibleTwo: false,
isVisibleThree: false,
},
};
},
methods:{
isVisible(){
// what should i put here?
}
}
};
</script>
You could loop over the isVisibleItems object keys and set their values to true :
methods:{
isVisible(){
Object.keys(this.isVisibleItems).forEach(key=>{
this.isVisibleItems[key]=true
}
}
}
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 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'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>
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.