v-on:click problem if the elements has another element inside - vue.js

I am dealing with the problem of my click event in vue js. I made a click even on element that has another element inside it.
Here's my code:
<span class="pull-down-controller" #click="pullDown($event)">
<span class="indicator">-</span> Controller
</span>
in the frontend it will show - Controller
if I click the word Controller it will call the specified function which is pullDown() but why is it whenever I click the indicator or the minus symbol, it will not do anything even if it is inside the <span> where I put the #click event?
The reason why I put a <span> inside it so I can change the symbol to + using jquery.
thanks!

No need for jQuery, Vue's reactivity provides all you need:
new Vue({
el: '#app',
template: `
<span #click="pullDown" style="font-size: 48px;">
<span>{{ indicator }}</span> Controller
</span>
`,
data () {
return {
expanded: false
}
},
computed: {
indicator () {
return this.expanded ? '+' : '-'
}
},
methods: {
pullDown (event) {
this.expanded = !this.expanded
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Related

How to use keyboard buttons to manipulate numbers

How to click a button not only directly clicking mouse, but pressing a button on a keyboard (in this case, its a keyboard button with a value "1" that hasevent.key` = 1)???
new Vue({
el: "#app",
data: {
one: 1
},
methods: {
add(){
this.one++;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button v-on:1 #click="add">One</button>
<span> {{ one }}</span>
</div>
If you want to listen for global keyboard events you'll need to add the listener to the window, otherwise you need focus on element that the event is dispatched from.
It's just plain vanila js from there:
new Vue({
el: "#app",
data: {
one: 1
},
created() {
const component = this;
this.handler = function (e) {
e.keyCode == 38 && component.add()
e.keyCode == 40 && component.remove()
}
window.addEventListener('keyup', this.handler);
},
beforeDestroy() {
window.removeEventListener('keyup', this.handler);
},
methods: {
remove() {
this.one--;
},
add(){
this.one++;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="add">One</button>
<span> {{ one }}</span>
</div>
This is covered in the VueJS documentation.
From the documentation:
Key Modifiers
When listening for keyboard events, we often need to check for
specific keys. Vue allows adding key modifiers for v-on when listening
for key events:
<!-- only call `vm.submit()` when the `key` is `Enter` --> <input
v-on:keyup.enter="submit">
You can directly use any valid key names exposed via KeyboardEvent.key
as modifiers by converting them to kebab-case.
<input v-on:keyup.page-down="onPageDown">
In the above example, the handler will only be called if $event.key is
equal to 'PageDown'.

How to binding only single class when using several v-on:click?

I'm newbie and studying Vue.js
I create v-on:click function and element toggle class when I click the button.
I am not good at English, I think it will be fast to show the code.
<button #click="bindA = !bindA">A</button>
<button #click="bindB = !bindB">B</button>
<span :class="[{ classA:bindA }, { classB:bindB }]"></span>
data: function() {
return {
bindA: true, // default
bindB: false
}
it's now. clicked bindA and B.
// browser
<span class="classA classB"></span>
but I want
// bindA click , remove classB
<span class="classA"></span>
// bindB click , remove classA
<span class="classB"></span>
It's simple in jquery, but difficult in vue.
It's very simple in vue as well.
Bind the class according to the conditions you want to see the data
:class="{'classA': (bindA== true), 'classB':(bindA== false)}"
Try adding a method to the #click - then you can build up a more complex logic, than simple "toggle".
new Vue({
el: "#app",
data: {
bindA: true,
bindB: false
},
methods: {
bind(btn) {
if ((btn === 'A' && !this.bindA) || (btn === 'B' && !this.bindB)) {
this.bindA = !this.bindA
this.bindB = !this.bindB
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="bind('A')">A</button>
<button #click="bind('B')">B</button>
<span :class="{ classA:bindA, classB:bindB }"></span>
</div>

Show child of this parent click vue

I want to show the immediate child after clicking on its parent. Its easy on jquery, hard on vue, how can I do it?
template:
<boxes inline-template>
<div class="white-box" #click="toggleTick">Purchase only
<div v-if="showTick"><i class="fas fa-check"></i></div>
</div>
</boxes>
js
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
},
methods: {
toggleTick () {
this.showTick = !this.showTick
}
}
})
var app = new Vue({
el: '#app',
data: {
}
})
At the moment I have multiple "white-box" div, it shows the child div for all of them, I just want to show the div for the clicked parent's child.
You should have behavior that you expect :
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
}
})
var app = new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 1</div>
</div>
</boxes>
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 2</div>
</div>
</boxes>
</div>
Your white-boxes are sharing the same showTick variable, so clicking on one fo them changes the value for all of them.
There are 2 available solutions to this:
Have multiple boxes components and not multiple white-boxes under the same boxes component. Something take ends up looking like this in the DOM
<boxes>...</boxes>
<boxes>...</boxes>
<boxes>...</boxes>
Use an array for showTick and use an index when calling toggleClick. Also note that for the changes in the array to be reactive you need to use Vue.set.
I recommend the former solution.

Conditional link behavior in VueJS

Couldn't find a proper name for the title, will be glad if someone figures out a better name.
I have a component which represents a product card. The whole component is wrapped in <router-link> which leads to product page.
However I have another case, when I do not need the component to lead to a product page, but instead I need to do some other action.
The only solution I found is to pass a callback function as a prop, and based on this, do something like:
<router-link v-if="!onClickCallback">
... here goes the whole component template ...
</router-link>
<div v-if="onClickCallback" #click="onClickCallback">
... here again goes the whole component template ...
</div>
How can I do this without copy-pasting the whole component? I tried to do this (real code sample):
<router-link class="clothing-item-card-preview"
:class="classes"
:style="previewStyle"
:to="{ name: 'clothingItem', params: { id: this.clothingItem.id }}"
v-on="{ click: onClick ? onClick : null }">
However I got this: Invalid handler for event "click": got null
Plus not sure if it's possible to pass prevent modificator for click and this just looks weird, there should be a better architectural solution
Commenting on the error, you could use an empty function instead of null, in the real code snippet
<router-link class="clothing-item-card-preview"
:class="classes"
:style="previewStyle"
:to="{ name: 'clothingItem', params: { id: this.clothingItem.id }}"
v-on="{ click: onClick ? onClick : null }">
This should works (replace a for "router-link" then insert right properties)
Further infos :
https://fr.vuejs.org/v2/guide/components-dynamic-async.html
v-bind is simply an Object where each keys is a props for your component, so here, I programmatically defined an object of properties depending on the wrapper (router link or a simple div). However we cannot do this for events (of course we could create our own event listener but it's a little bit tricky) so I simply but an handle method.
new Vue({
el: "#app",
data: {
products : [{onClickCallback : () => { alert("callback"); return true;}}, {}, {}]
},
methods : {
handleClick(product, event) {
if (!product.onClickCallback) return false
product.onClickCallback()
return true
},
getMyComponentName(product) {
if (product.onClickCallback) return "div"
return "a"
},
getMyComponentProperties(product) {
if (product.onClickCallback) return {is : "div"}
return {
is : "a",
href: "!#"
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<component
v-for="(product, index) in products"
:key="index"
v-bind="getMyComponentProperties(product)"
#click="handleClick(product, $event)"
>
<div class="product-card">
<div class="product-card-content">
<span v-show="product.onClickCallback">I'm a callback</span>
<span v-show="!product.onClickCallback">I'm a router link</span>
</div>
</div>
</component>
</div>
Do you have to use a <router-link>? If it can safely be a <div>, you could use something like
<div #click="handleClick" ...>
<!-- component template -->
</div>
and
methods: {
handleClick (event) {
if (this.onClickCallback) {
this.onClickCallback(event)
} else {
this.$router.push({ name: 'clothingItem', ... })
}
}
}
See https://router.vuejs.org/guide/essentials/navigation.html

Programmatically add v-on directives to DOM elements

<span #click="showModal = $event.target.innerHtml>Tag 1</span>
<span #click="showModal = $event.target.innerHtml>Tag 2</span>
<span #click="showModal = $event.target.innerHtml>Tag 3</span>
Clicking in any of the 3 spans will make this.showModal to have the value of each of the span content elements. But this code looks repetitive and unnecessary. I know I can create a component with v-for and have the data for the span contents somewhere else, but I want to know how to do this for very specific reasons. I'd like to have this:
<span>Tag 1</span>
<span>Tag 2</span>
<span>Tag 3</span>
And a function, e.g. in the hook mounted() of the component, that adds the v-on directive for click to each one of them.
Can you help me?
Thanks.
You could try something like this:
<template>
<span v-for="tag in tags" #click="showModal(tag)" v-text="tag"></span>
</template>
<script>
export default {
data() {
return {
tags: ['Tag 1', 'Tag 2', 'Tag 3']
}
},
methods: {
showModal(tag) {
console.log("Showing modal for tag:", tag)
}
}
}
</script>
Hope this helps!
You can add a method which is called on clicks that reads the element's HTML content.
The template:
<span #click="doStuff">Tag 1</span>
<span #click="doStuff">Tag 2</span>
<span #click="doStuff">Tag 3</span>
The method:
doStuff(e) {
this.showModal = e.target.innerHTML
}
You could set up a method to call when the tag is clicked and pass the id of the tag that was clicked through to handle appropriately.
Assuming that you have an array of the tag text:
data: function() {
return {
tagTotal: ['Tag 1', 'Tag 2', 'Tag 3'];
}
}
Then in the HTML section:
<span v-for="tag in tagTotal" #click="methodToCall(tag)">
{{ tag }}
</span>
Then in your mounted, methods, or created section you could add:
mounted: {
methodToCall: function(tag) {
showModal = tag;
// or 'this.showModal = tag' if showModal is a part of the componenet.
}
}
I've finally added the listeners manually with vanilla js, in order to save code:
mounted: function() {
let spans = document.querySelectorAll('span');
spans.forEach(el => {
el.addEventListener('click', this.clickTag);
})
}
methods: {
clickTag(event) { this.showModal = event.target.innerHTML }
}
It's important not using an arrow function for mounted because otherwise it won't bind the vue instance for this.
Thanks for your answers.
If direct-process Dom elements, custom directive will be one option.
Vue.config.productionTip = false
let vMyDirective = {}
vMyDirective.install = function install (_Vue) {
_Vue.directive('my-directive', {
inserted: function (el, binding, vnode) {
el.addEventListener('click', () => {
_Vue.set(vnode.context, binding.value.model, el.innerHTML)
}, false)
}
})
}
Vue.use(vMyDirective)
new Vue({
el: '#app',
data() {
return {
testValues: ['label a', 'label b'],
showModal: 'nothing!!!'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h2>showModal: {{showModal}}</h2>
<div>
<p v-for="(item, index) in testValues" v-my-directive="{'model': 'showModal'}">Test:<span>{{item}}</span></p>
</div>
</div>