Vuejs v-on click doesn't work inside component - vue.js

I use VueJs and I create the following component with it.
var ComponentTest = {
props: ['list', 'symbole'],
data: function(){
return {
regexSymbole: new RegExp(this.symbole),
}
},
template: `
<div>
<ul>
<li v-for="item in list"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
`,
methods: {
replaceSymbole: function(name){
return name.replace(this.regexSymbole, '<span v-on:click="test">---</span>');
},
test: function(event){
console.log('Test ...');
console.log(this.$el);
},
}
};
var app = new Vue({
el: '#app',
components: {
'component-test': ComponentTest,
},
data: {
list: [{"id":1,"name":"# name1"},{"id":2,"name":"# name2"},{"id":3,"name":"# name3"}],
symbole: '#'
},
});
and this my html code
<div id="app">
<component-test :list="list" :symbole="symbole"></component-test>
</div>
When I click on the "span" tag inside "li" tag, nothing append.
I don't have any warnings and any errors.
How I can call my component method "test" when I click in the "span" tag.
How implement click event for this case.

You cannot use vue directives in strings that you feed to v-html. They are not interpreted, and instead end up as actual attributes. You have several options:
Prepare your data better, so you can use normal templates. You would, for example, prepare your data as an object: { linkText: '---', position: 'before', name: 'name1' }, then render it based on position. I think this is by far the nicest solution.
<template>
<div>
<ul>
<li v-for="(item, index) in preparedList" :key="index">
<template v-if="item.position === 'before'">
<span v-on:click="test">{{ item.linkText }}</span>
{{ item.name }}
</template>
<template v-else-if="item.position === 'after'">
{{ item.name }}
<span v-on:click="test">{{ item.linkText }}</span>
</template>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(question) {
if (question.indexOf("#") === 0) {
return {
linkText: "---",
position: "before",
name: question.replace("#", "").trim()
};
} else {
return {
linkText: "---",
position: "after",
name: question.replace("#", "").trim()
};
}
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
}
}
};
</script>
You can put the click handler on the surrounding li, and filter the event. The first argument to your click handler is the MouseEvent that was fired.
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id" v-on:click="clickHandler"
v-html="replaceSymbole(item.name)">
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["list", "symbole"],
data() {
return {
regexSymbole: new RegExp(this.symbole)
};
},
computed: {
preparedList() {
return this.list.map(item => this.replaceSymbole(item.name));
}
},
methods: {
replaceSymbole: function(name) {
return name.replace(
this.regexSymbole,
'<span class="clickable-area">---</span>'
);
},
test: function(event) {
console.log("Test ...");
console.log(this.$el);
},
clickHandler(event) {
const classes = event.srcElement.className.split(" ");
// Not something you do not want to trigger the event on
if (classes.indexOf("clickable-area") === -1) {
return;
}
// Here we can call test
this.test(event);
}
}
};
</script>
Your last option is to manually add event handlers to your spans. I do not!!! recommend this. You must also remove these event handlers when you destroy the component or when the list changes, or you will create a memory leak.

Related

#click bound to each item within v-for loop executed many times when clicking

I have a #click bound to individual items within a v-for loop. In the resulting render, I should have one #click for each item, so clicking one item should trigger the function bound to the item once.
Yet it triggers it as many times as there are items. Why?
<ul>
<li :key="option.value" v-for="option in options">
<QuizCheckbox
#click.native="handleClick(option.value)"
>
{{ option.value }}
</QuizCheckbox>
</li>
</ul>
...
methods: {
handleClick(val) {
console.log(val);
},
EDIT:
If I replaced ... with a simple element, then clicking that doesn't trigger the problem. So it's the <QuizCheckbox> component who's the culprit. However, nothing in it seems to indicate what could cause the problem. Here's the content of QuizCheckbox.vue:
<template>
<div :class="['quiz-checkbox', {'quiz-checkbox--checked': shouldBeChecked}]">
<div :class="['form-checkbox']">
<label class="form-checkbox__label">
<slot/>
<input
:checked="shouldBeChecked"
:value="value"
#change="updateInput"
class="form-checkbox__input"
type="checkbox"
/>
<span class="form-checkbox__checkmark"></span>
</label>
</div>
</div>
</template>
<script>
export default {
model: {
prop: 'modelValue',
event: 'change'
},
props: {
value: {
type: String,
},
modelValue: {
type: [Boolean, Array],
default: false
}
},
computed: {
shouldBeChecked() {
if (this.modelValue instanceof Array) {
return this.modelValue.includes(this.value);
}
return this.modelValue;
}
},
created() {
if (!this.$slots.default) {
console.error('QuizCheckbox: requires label to be provided in the slot');
}
},
methods: {
updateInput(event) {
const isChecked = event.target.checked;
if (this.modelValue instanceof Array) {
const newValue = [...this.modelValue];
if (isChecked) {
newValue.push(this.value);
} else {
newValue.splice(newValue.indexOf(this.value), 1);
}
this.$emit('change', newValue);
} else {
this.$emit('change', isChecked);
}
}
}
};
</script>
The code you post seems fine. Although simplified here is the code you post running:
new Vue({
el: 'ul',
data: {
options: [
{
value: 'option1',
},
{
value: 'option2',
}
]
},
methods: {
handleClick(val) {
console.log(val);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<ul>
<li :key="option.value" v-for="option in options">
<div
#click="handleClick(option.value)"
>
{{ option.value }}
</div>
</li>
</ul>
The problem must be elsewhere.

Vue-draggable limit list to 1 element

Here I am trying to implement cloning an element from rankings list and put it in either of the two lists (list1 & list2). Everything seems to be working, I am able to drag and put but it looks like binding does not work as the two lists are not affected, because the watchers do not run when I drag an element to a list. Also, the clone function does not print the message to the console. I was using this example as a reference.
<template>
<div>
<div>
<div>
<draggable
#change="handleChange"
:list="list1"
:group="{ name: 'fighter', pull: false, put: true }"
></draggable>
</div>
<div>
<draggable
#change="handleChange"
:list="list2"
:group="{ name: 'fighter', pull: false, put: true }
></draggable>
</div>
</div>
<div>
<div v-for="wc in rankings" :key="wc.wclass">
<Card>
<draggable :clone="clone"
:group="{ name: 'fighter', pull: 'clone', put: false }"
>
<div class="cell" v-for="(fighter, idx) in wc.fighters" :key="fighter[0]">
<div class="ranking">
{{ idx + 1 }}
</div>
<div class="name">
{{ fighter[0] }}
</div>
</div>
</draggable>
</Card>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data() {
return {
rankings: [],
list1: [],
list2: []
};
},
methods: {
getRankingLabel(label) {
if (!label || label == "NR") return 0;
if (label.split(" ").indexOf("increased") !== -1) return 1;
if (label.split(" ").indexOf("decreased") !== -1) return -1;
},
clone({ id }) {
console.log("cloning");
return {
id: id + "-clone",
name: id
};
},
handleChange(event) {
console.log(event);
}
},
watch: {
// here I am keeping the length of both lists at 1
list1: function(val) {
console.log(val); // nothing prints, as the watcher does not run
if (val.length > 1) {
this.fighter_one.pop();
}
},
list2: function(val) {
console.log(val); // nothing prints, as the watcher does not run
if (val.length > 1) {
this.fighter_two.pop();
}
}
},
created() {
axios
.get("http://localhost:3000")
.then(res => {
this.rankings = res.data;
})
.catch(err => {
console.log(err);
});
}
};
</script>
<style>
</style>
As others have noted in the comments, your problem is likely related the <draggable> tag not containing either a :list prop or v-model.
With that said, you can limit the size of a list to 1 by calling the splice(1) method on the list in the #change event.
<draggable :list="list1" group="fighter" #change="list1.splice(1)">
{{ list1.length }}
</draggable>

Set css class for single items in v-for loop

I am playing around with Vue.js and I am trying to change the class of individual items in a v-for route dependent on a checkbox.
<template>
<div>
<ul>
<div :class="{completed: done}" v-for="things in items">
<h6 v-bind:key="things"> {{things}} - <input #click="stateChange" type="checkbox"/></h6>
</div>
</ul>
</div>
</template>
<script>
export default {
name: 'ItemList',
data() {
return {
items: [],
done: false
}
},
mounted() {
Event.$on('itemAdded', (data) => {
this.items.push(data);
})
},
methods: {
stateChange() {
this.done = !this.done;
}
}
}
</script>
<style>
.completed {
text-decoration-line: line-through;
}
</style>
The above code places a line through every item, not just the checked one.
How do I code so only the checked item is crossed out?
Thanks
Paul.
It looks like you have only one done property. You should have a done property for each element in your items array for this to work. Your item should like {data: 'somedata', done: false }
This should work:
<template>
<div>
<ul>
<div :class="{completed: item.done}" v-for="(item,index) in items">
<h6 v-bind:key="things"> {{item.data}} - <input #click="stateChange(item)" type="checkbox"/></h6>
</div>
</ul>
</div>
</template>
<script>
export default {
name: 'ItemList',
data() {
return {
items: [],
}
},
mounted() {
Event.$on('itemAdded', (data) => {
this.items.push({ data, done: false });
})
},
methods: {
stateChange(changeIndex) {
this.items = this.items.map((item, index) => {
if (index === changeIndex) {
return {
data: item.data,
done: !item.done,
};
}
return item;
});
}
}
}
</script>
<style>
.completed {
text-decoration-line: line-through;
}
</style>
#Axnyff
You were very close. Thank you. Here is the little changes I made to get it working.
<template>
<div>
<ul>
<div :class="{completed: item.done}" v-for="item in items">
<h6> {{item.data}} - <input #click="item.done = !item.done" type="checkbox"/></h6>
</div>
</ul>
</div>
</template>
<script>
export default {
name: 'ItemList',
data() {
return {
items: [],
}
},
mounted() {
Event.$on('itemAdded', (data) => {
this.items.push({ data, done: false });
console.log("DATA- ", this.items)
})
},
methods: {
}
}
</script>
<style>
.completed {
text-decoration-line: line-through;
}
</style>

Vue.JS: turn every matching case to a hyperlink

I'm trying to turn every matching case from an array to a link
// I call the message component using this from another component
<ul>
<li
v-for="message in message"
v-bind:message="message"
is="message"
</ul>
// in the message component I have a mounted that reads the text message
// from the server and filters hashtags
data () {
return {
hashtagsInMessages: ''
};
},
mounted () {
const filterMessageHashtags = _.filter( this.message,text.split( ' ' ), value => hashtags.indexOf(value) != -1 );
this.hashtagsInMessages = filterMessageHashtags;
}
With this, how can I turn a message to a link, for instance:
hey how you doing #cool #fire #water bro?
To this, using Vue.js
hey how you doing #cool #fire #water bro?
This is my solution:
Vue.component("message-el", {
template: "#message",
props: ["message"],
created: function (){
this.messages = this.message.split(" ");
this.hashTags = this.messages.filter(function (message){
return message[0] === "#";
});
},
data: function (){
return {
messages: [],
hashTags: [],
};
},
methods: {
}
});
var app = new Vue({
el: "#app",
data: function (){
return {
}
},
methods: {
}
});
.none {
display: none;
}
<script src="https://unpkg.com/vue#2.5.2/dist/vue.js"></script>
<div id="app">
<message-el message="this is a message #tag1 #tag2">
</message-el>
</div>
<div id="message" class="none">
<div>
<div>
<template v-for="message in messages">
<a v-if="hashTags.indexOf(message) > -1" :href="'#' + message">
{{ message }}
</a>
<span v-else>
{{ message }}
</span>
&nbsp
</template>
</div>
</div>
</div>

Why is object not defined?

I want to try Vue.js 2 and started with a simple example. I've took this one from here https://jsfiddle.net/gmsa/gfg30Lgv/ and created a simple project with it. After I divided this code into files the project doesn't work. So I've made a data property a function:
data: function(){
return {
tabs: [{
name: "tab1",
id : 0,
isActive: true
}],
activeTab: {}
}
},
But there's an error in a console: Uncaught ReferenceError: newTab is not defined.
Project: https://github.com/rinatoptimus/vue-webpack-delete
File QueryBrowserContainer:
<template>
<div id="queryBrowserContainer">
<p>queryBrowserContainer text</p>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" v-for="tab in tabs" :class="{active:tab.isActive}">
{{ tab.name }}
</li>
<li>
<button type="button" class="btn btn-primary" #click="openNewTab">New tab</button>
</li>
</ul>
<div class="tab-content">
<div v-for="tab in tabs" role="tabpanel" class="tab-pane" :class="{active:tab.isActive}">
<app-querybrowsertab :tab="tab"></app-querybrowsertab>
</div>
</div>
<pre>{{ $data | json }}</pre>
</div>
</template>
<script>
import QueryBrowserTab from './QueryBrowserTab.vue';
export default {
data: function(){
return {
tabs: [{
name: "tab1",
id : 0,
isActive: true
}],
activeTab: {}
}
},
ready: function () {
this.setActive(this.tabs[0]);
},
methods: {
setActive: function (tab) {
var self = this;
tab.isActive = true;
this.activeTab = tab;
/*this.activeTab.isActive = true;
console.log("activeTab name=" + this.activeTab.name);*/
this.tabs.forEach(function (tab) {
if (tab.id !== self.activeTab.id) { tab.isActive = false;}
});
},
openNewTab: function () {
newTab = {
name: "tab" + (this.tabs.length + 1),
id: this.tabs.length,
isActive: true
};
this.tabs.push(newTab);
this.setActive(newTab);
/*this.activeTab = newTab;
console.log("### newtab name=" + newTab.name);*/
},
test: function() {
alert('676767');
},
closeTab: function () {
console.log("### CLOSE!");
}
}
}
File QueryBrowserTab:
<template>
<div>
<p>querybbbTab</p>
<h3>{{tab.name}}</h3>
<h3>{{tab.id}}</h3>
</div>
</template>
<script>
import QueryBrowserContainer from './QueryBrowserContainer.vue';
export default {
data: function () {
return {
databaseOptions: [],
};
},
props: ['tab'],
methods: {},
components: {
'app-querybrowsercontainer': QueryBrowserContainer
}
}
</script>
File App:
<template>
<div id="app">
<app-message></app-message>
<app-querybrowsertab></app-querybrowsertab>
<app-querybrowsercontainer></app-querybrowsercontainer>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {}
}
}
</script>
It seems in file: QueryBrowserTab, you have not passed tab props, but you are using it, make sure you pass tab as props from whatever places you are using it.
As stated in the docs here, you can pass props to a component like following:
<app-querybrowsertab :tab="tab"></app-querybrowsertab>
which you are already doing in app-querybrowsercontainer,but in file App, you are not passing the prop, which might be the source of error for you.