In vuejs, how to pass the html element when click a element? - vue.js

I want to change a button color when I click that button.
If at jquery, I might use $(this).
<button v-on:click="clicked($event)">Change my color</button>
in script
...
methods: {
clicked:function(event){
???
}
}
How can I use the element which I clicked?

You should think about states for that. Avoid thinking the jQuery way in these situations because it will make your component less reactive to its state.
You could have a class attached to the button that depends on the state of it. When clicked, you change the state and then it would change the class applied to it. Check out the example:
http://jsfiddle.net/taokbg7q/
<button :class="{clicked: btnClicked}" #click="clickBtn"> My Button </button>
// ...
data: {
btnClicked: false
},
methods: {
clickBtn() {
this.btnClicked = true
}
}
// ...
.clicked {
background-color: red;
}

you can use style and class as dynamicly:
<button v-on:click="clicked()" :style="{color:
myColor}">Change my color</button
data(){
return {
myColor: 'black'
}
}
methods: {
clicked (){
this.myColor = 'red';
}
}

Bind a dynamic inline style to button and change its value with an event handler.
Events handlers
Binding inline styles
HTML
<div id="app">
<button v-on:click="clicked" :style="{color: activeColor}">Change my color</button>
</div>
JavaScript
new Vue({
el: '#app',
data: {
activeColor: null
},
methods: {
clicked() {
this.activeColor = 'red'
}
}
});

Related

How could I change an image in a child page when pressing a button in its parent page?

I have a DefaultLayout component with a dark mode toggle button which is its own component. One if its children (DefaultLayout's) is About.vue where I want a specific image to change its src depending on a localStorage value that can be set to either 'dark' or 'light'.
I've managed to read the localStorage value but the image does not change unless I refresh the page.
I'm new to Vue so I'm lost on how I can create a method to do this in DefaultLayout and change a variable in its child. I've tried to use an emit with no luck.
Could anyone point me in the right direction?
Yes, the local storage is for keeping data not propagate events.
The simplest way for you is to make a prop in child component and pass the value by this prop. But if you want to implement it as global variable the suggested way is by Pinia.
Below is a simple example
Vue.component('About', {
name: 'About',
template: `<div>
<div v-if="mode==='dark'">Dark</div>
<div v-else>Light</div>
</div>
`,
data() {
return {
mode: 'light',
};
},
mounted() {
this.setMode('white'); // In realtime use `this.getMode()` instead of 'white'
},
methods: {
setMode(val) {
this.mode = val;
},
getMode() {
return JSON.parse(localStorage.getItem('mode'));
}
}
});
var app = new Vue({
el: "#app",
template: `<div>
<input type="checkbox" v-model="toggler" #input="setVal" />
<About ref="about" />
</div>`,
data() {
return {
toggler: false,
};
},
methods: {
setVal() {
const mode = this.toggler === false ? 'dark' : 'light';
// localStorage.setItem('mode', mode); // In realtime uncomment this line
this.$refs.about.setMode(mode);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>

Vue – pass variable from child template to App.vue

I read up on passing variables from child to parent using $emit but I can't fully figure it out yet.
In App.vue I have a <header/> component for the page header containing a button which controls the mobile navigation's visibility. On click it changes its class:
<button #click="toggleMobileNavigation" :class="isOpen ? 'is-open' : 'is-closed'">
The <header/>'s js:
export default {
data() {
return {
isOpen: false,
};
},
methods: {
toggleMobileNavigation() {
if(!this.isOpen) {
this.isOpen = true;
} else {
this.isOpen = false;
}
this.$emit(this.isOpen)
}
}
}
The App.vue:
<Header />
<main id="main" tabindex="-1" class="main" :class="isOpen">
This obviously this doesn't work and I can't figure out what the right way is to catch the $emit.
Thanks for any tips!
I would say you are on the right track, this child needs to emit some event to alert its parent of an important change.
But instead of doing this in your Header component:
this.$emit(this.isOpen)
Supply an event name:
this.$emit('opened', this.isOpen)
// or:
if (this.isOpen) {
this.$emit('opened');
} else {
this.$emit('closed');
}
The way you catch this event in the parent component (App.vue) should be:
<Header #opened="handleOpenedEvent"> // will call method handleOpenedEvent
// alternatively:
<Header #opened="menuStatus = $event"> // $event contains data you supply as second argument to your this.$emit(name, ...) call
// #[eventname] is one way of doing it, v-on is the same:
<Header v-on:opened="handleEvent">

Can a vue component know if a listener is listening?

Say I have a modal dialogue as a Vue component. Sometimes I want OK and Cancel. Sometimes, I just want OK. The cleanest way I can think to do this would be for my component to only display Cancel when it's caller is listening for cancel events. Can this be done?
jsfiddle
markup
<div id="vue-root">
<confirm-modal v-on:ok="handleOk"></confirm-modal>
<confirm-modal v-on:ok="handleOk" v-on:cancel="handleCancel"></confirm-modal>
</div>
code
Vue.component('confirm-modal',{
template : `
<div class="confirm-modal">
Are you sure<br>
<button v-on:click="$emit('ok',{})">OK</button>
<button v-if="'HOW DO I TEST IF CANCEL WILL BE CAPTURED???'" v-on:click="$emit('cancel',{})">Cancel</button
</div>
`,
})
vm = new Vue({
el : '#vue-root',
methods : {
handleOk : function(){
alert('OK already');
},
handleCancel : function(){
}
}
})
First you can emit an event without value and the parent will catch it. You dont need this empty Object.
What i understood from your question is this:
If you to track in confirm-modal component if the cancel button is clicked ?
Then do this.
Vue.component('confirm-modal',{
template : `
<div class="confirm-modal">
Are you sure<br>
<button v-on:click="$emit('ok',{})">OK</button>
<button v-if="isCaptured" v-on:click="cancelClick">Cancel</button
</div>
`,
data: function () {
return {
isCaptured: false,
};
},
methods: {
cancelClick: function() {
this.isCaptured = true;
// or this.$emit('cancel'); then pass prop from parent
},
}
})
vm = new Vue({
el : '#vue-root',
methods : {
handleOk : function(){
alert('OK already');
},
handleCancel : function(){
}
}
})
The easy way to do this, you can pass props from parent to child component when you want to show ok and cancel button.
Here you have some more about props
https://v2.vuejs.org/v2/guide/components.html#Passing-Data-with-Props

Does vue.js have boolean modifiers for events?

Is it possible in vue.js to do something like #click.not.ctrl to specify the event should only fire on click where the ctrl modifier is not true?
There is no such option in Vue.js as far as I know.
You can use a custom directive in order to achieve it: https://jsfiddle.net/wostex/63t082p2/35/
<div id="app">
<button v-noctrl:click="sayHi">Say hi</button>
</div>
new Vue({
el: '#app',
directives: {
'noctrl': {
bind(el,binding,vnode) {
if (binding.arg === 'click') {
el.addEventListener(binding.arg, function(event) {
if (!event.ctrlKey) {
event.preventDefault();
binding.value.call(this);
}
});
}
}
}
},
methods: {
sayHi() {
console.log('Hi');
}
}
});
No, we currently don't have such a functionality.
You have check yourself in your callback method.

Building reusable components with custom methods

I am trying to build a reusable tab component with vuejs. I am still learning some basic consepts of vue and hardly managed to finish tab generation and switch logic. It is OK to switch between tabs in component itself now. But I have some problems with making my component to listen outside triggers.
For now, I can switch my tabs outside of the component with the help of $refs. But as I am trying to make it reusable this method doesn't sound practical. What should I do?
Here is the JSFiddle
Vue.component('tabs', {
template: `
<div class="tabs">
<div class="tab-titles">
<a :class="{'active':tab.active}" v-for="tab in tablist" href="#" class="tab-title" #click.prevent="activateTab(tab)">{{tab.title}}</a>
</div>
<div class="tab-contents">
<slot></slot>
</div>
</div>
`,
data() {
return {
tablist: [],
}
},
methods: {
activateTab: function(tab) {
this.tablist.forEach(t => {
t.active = false;
t.tab.is_active = false
});
tab.active = true;
tab.tab.is_active = true;
},
activateTabIndex: function(index) {
this.activateTab(this.tablist[index]);
},
collectTabData: function(tabData) {
this.tablist.push(tabData)
}
},
});
//==============================================================================
Vue.component('tab', {
template: `
<div :class="{'active':is_active}" class="tab-content">
<slot></slot>
</div>
`,
data() {
return {
is_active: this.active
}
},
mounted() {
this.$parent.collectTabData({
tab: this,
title: this.title,
active: this.active,
is_active: this.is_active
});
},
props: {
title: {
type: String,
required: true
},
active: {
type: [Boolean, String],
default: false
},
}
});
//==============================================================================
Vue.component('app', {
template: `
<div class="container">
<tabs ref="foo">
<tab title="tab-title-1">
<h3>content-1</h3>
Initial content here
</tab>
<tab title="tab-title-2" active>
<h3>content-2</h3>
Some content here
</tab>
<tab title="tab-title-3">
<h3>content-3</h3>
Another content here
</tab>
</tabs>
<a href="#" #click='switchTab(0)'>switch to tab(index:0)</a>
</div>
`,
methods: {
switchTab: function () {
vm.$children[0].$refs.foo.activateTabIndex(0);
}
},
});
//==============================================================================
const vm = new Vue({
el: '#inner-body',
});
#import url('https://fonts.googleapis.com/css?family=Lato');
#inner-body{
font-family: 'Lato', sans-serif;
background-color:#ffffff;
padding:20px;
}
.tab-titles {
}
.tab-title {
display: inline-block;
margin-right: 10px;
color: #bbb;
text-decoration: none;
}
.tab-title.active {
color: #06cd92;
border-bottom:1px solid #06cd92;
}
.tab-contents{
border: 1px solid #ddd;
border-width:1px 0;
margin-top:-1px;
margin-bottom:20px;
}
.tab-content {
display: none;
padding-bottom:20px;
}
.tab-content.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.min.js"></script>
<div id="inner-body">
<app></app>
</div>
Vue Components are supposed communicate one-way down to children via Props (where only parents mutate the props on the children, never the children themselves) and children communicate to parents by emitting events. The makes nesting components much easier to reason about, and decouples components properly. So what do you do when the parent wants to change a tab? Let me walk you through a process:
1) Imagine we add a prop called activeTab to the tabs component (I'm not following your code in your question directly here, just basing loosely off it to demonstrate the process easier). The parent will change the value of this prop whenever it wants. The tabs component (aka child component in this case) should not alter the value of the activeTab prop. Instead, inside the tabs component, add a watcher for this prop:
in child component (ie. tabs)
props: {
/**
* Prop parent can use to set active tab
*/
'activeTab': {
type: Number
}
},
watch: {
/**
* Watch for changes on the activeTab prop.
*
* #param {boolean} val The new tab value.
* #param {boolean} oldVal The old tab value.
* #return {void}
*/
activeTab: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
// do something here to make the tabs change
// and never alter the prop itself.
this.switchTab(val)
}
},
2) Now on your parent, you should have a reactive data property that can be named the same as your prop if you want:
in parent component (ie. app)
data: {
activeTab: 0
}
3) Then we need to make it where when you alter the data property above, the prop gets altered too. Here's what it would look like on the tabs component tag:
in parent component (ie. app)
<tabs :activeTab="activeTab" ...
4) What do you do if you want to allow the tabs component to also alter the active tab sometimes? Easy, just emit an event whenever an active tab is changed:
in the child component (ie. tabs)
methods: {
switchTab (newActiveTabValue) {
// store the new active tab in some data property in this component, but not the "activeTab" prop, as mentioned earlier.
this.whatever = newActiveTabValue
// ... now emit the event
this.$emit('switched', newActiveTabValue)
}
5) Your parent should now listen for this emitted event and update its own data property:
in parent component (ie. app)
<tabs :activeTab="activeTab" #switched="activeTab = arguments[0]" ...
Seems a little bit more effort, but it's all worth it as your app grows in complexity and more things become nested. You can read more on Composing Components in the official docs here.