I've made a component (is that overkill?) to call recursively in order to render a tree, but it seems to be impossible to handle click events because stuff rendered inside the component can't see the method I want in the Vue's methods; this appears to be some monstrous complication about components and events.
How can I handle these click events?
Does it really have to be so difficult?
<script type="text/html" id="template-tree">
<ul>
<c-tree-item v-for="item in items" v-bind:item="item"></c-tree-item>
</ul>
</script>
<script type="text/html" id="template-tree-item">
<li v-on:click="clickTreeItem"> <!-- click doesn't work :( -->
<p> {{ item.Name }}</p>
<ul v-if="item.Items.length > 0">
<c-tree-item v-for="i in item.Items" v-bind:item="i"></c-tree-item>
</ul>
</li>
</script>
<script type="text/javascript">
var vueApp = undefined;
var ct = Vue.component('c-tree', {
template: document.getElementById('template-tree').innerHTML,
props: ['items']
});
var cti = Vue.component('c-tree-item', {
template: document.getElementById('template-tree-item').innerHTML,
props: ['item']
});
$(document).ready(function () {
var router = new VueRouter({
mode: 'history',
routes: []
});
vueApp = new Vue({
router,
el: '#vue',
data: {
tree: JSON.parse(document.getElementById('data').innerHTML)
},
methods: {
clickTreeItem: function(event) {
console.log('click');
console.log(JSON.stringify(event));
}
}
});
});
</script>
<div id="vue">
<c-tree v-bind:items="tree"></c-tree>
</div>
A component can only raise events -- which may have data associated with them.
A component has to listen to it's sub-components' events and may, in turn, pass them upwards as its own events.
Example: https://jsfiddle.net/gkch7bnr/1/
StackOverflow is stroppy about JSFiddle so here is some code:
<head>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.common.dev.js" integrity="sha256-soI/D3XnqcarOMK229d8GWs8P+gYViEsbWBeMaRoSPk=" crossorigin="anonymous"></script>
<script type="application/json" id="data">
[
{"Id": 1, "Name": "Name1", "Items": []},
{"Id": 2, "Name": "Name2", "Items": []},
{"Id": 3, "Name": "Name3", "Items": [
{"Id": 31, "Name": "Name31"},
{"Id": 32, "Name": "Name32"},
{"Id": 33, "Name": "Name33"}
]}
]
</script>
<script type="text/html" id="template-tree">
<ul>
<c-tree-item v-for="item in items" v-bind:item="item" v-on:click="$emit('click', $event)"></c-tree-item>
</ul>
</script>
<script type="text/html" id="template-tree-item">
<li>
<p v-on:click="$emit('click', item)" > {{ item.Name }}</p>
<ul v-if="item.Items && item.Items.length > 0">
<c-tree-item v-for="i in item.Items" v-bind:item="i" v-on:click="$emit('click', $event)"></c-tree-item>
</ul>
</li>
</script>
</head>
<body>
<div id="vue">
<c-tree v-bind:items="tree" v-on:click="clickTreeItem"></c-tree>
</div>
</body>
var vueApp = undefined;
var ct = Vue.component('c-tree', {
template: document.getElementById('template-tree').innerHTML,
props: ['items'],
methods: {
clickTI: function(e) { console.log('clickTI: ' + e.Name);}
}
});
var cti = Vue.component('c-tree-item', {
template: document.getElementById('template-tree-item').innerHTML,
props: ['item']
});
$(document).ready(function () {
vueApp = new Vue({
el: '#vue',
data: {
tree: JSON.parse(document.getElementById('data').innerHTML)
},
methods: {
clickTreeItem: function(event) {
console.log('clickTreeItem: ' + event.Name);
}
}
});
});
Related
I'm taking the introductory phases at the Vue.js.
Everything is finde and all app components are showing until const { default: Vue } = require("vue"); is added. Then the webpage becomes like on the picture here
I have the following code:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{ helloWorld }}</h1>
</div>
<div id="app-2">
<span v-bind:title="message">
Hold musen over dette, for at se hvordan titlen vil ændre sig!
</span>
</div>
<div id="app-3">
<span v-if="seen">
Now you see me
</span>
</div>
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>
<div id="app-7">
<ol>
<!-- Create an instance of the todo-item component -->
<todo-item
v-for="item in groceryList"
b-bind:todo="item"
b-bind:key="item.id">
</todo-item>
</ol>
</div>
<script src="index.js"></script>
</body>
</html>
JS:
const { default: Vue } = require("vue");
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
var app = new Vue ({
el: '#app',
data: {
helloWorld: 'First vue output!'
}
});
var app2 = new Vue ({
el: '#app-2',
data: {
message: 'Du har loaded siden den: ' + new Date().toLocaleString()
}
});
var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
});
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{text: 'Første punkt'},
{text: 'Andet punkt'},
{text: 'Tredje punkt'}
]
}
});
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function() {
this.message = this.message.split('').reverse().join('')
}
}
});
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
});
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
})
var app = new Vue
Vue.component("blog-post", {
props: ["post"],
template: `
<div>
<h3>{{ post.title }}</h3>
<p> #####: {{ post.content }} </p>
</div>
`
});
new Vue({
el: "#blog-post-demo",
data: {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" }
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="blog-post-demo" class="demo">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.id"
></blog-post>
</div>
The above example is not working. But I am able to make the below one work.
Vue.component("blog-post", {
props: ["content", "title"],
template: `
<div>
<h3>{{ title }}</h3>
<p> #####: {{ content }} </p>
</div>
`
});
new Vue({
el: "#blog-post-demo",
data: {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" }
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="blog-post-demo" class="demo">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.id"
></blog-post>
</div>
Could someone explain what am I missing here?
In the first case, your component accept post as the prop, which is what you should pass from the parent component.
Vue.component("blog-post", {
props: ["post"],
template: `
<div>
<h3>{{ post.title }}</h3>
<p> #####: {{ post.content }} </p>
</div>
`
});
new Vue({
el: "#blog-post-demo",
data: {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" }
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.js"></script>
<div id="blog-post-demo" class="demo">
<blog-post
v-for="post in posts"
v-bind:post="post"
v-bind:key="post.id"
></blog-post>
</div>
I am new to Vue.js.
list.vue:
<template>
<div class="m-products-list">
<ul #mouseover="over">
<Item
v-for="(item,idx) in parentList"
location="item.location"
:key="idx"
:meta="item"/>
</ul>
</div>
</template>
<script>
export default {
...
methods: {
over: function (e) {
let dom = e.target;
let tag = dom.tagName.toLowerCase();
if (tag === 'dd') {
console.log(dom.getAttribute('location'))
}
}
}
}
</script>
The Item is from its parent component. And I want to get item.location in over() when I mouseover an item, but console.log always returns null. Anyone have an idea?
This is technically possible (but there may be a better alternative shown in the next section) by setting a data-* attribute in the Item.
// Item.vue
<li :data-location="location" class="item" ... >
new Vue({
el: '#app',
data() {
return {
items: [
{id: 1, location: 'New York'},
{id: 2, location: 'Los Angeles'},
{id: 3, location: 'Chicago'},
]
}
},
components: {
Item: {
props: ['location'],
template: `<li :data-location="location" class="item">{{location}}</li>`,
}
},
methods: {
over(e) {
console.log(e.target.dataset.location)
}
}
})
<script src="https://unpkg.com/vue#2.6.7/dist/vue.min.js"></script>
<div id="app">
<ul #mouseover="over">
<Item v-for="item in items"
:key="item.id"
:location="item.location" />
</ul>
</div>
A better solution that doesn't require DOM manipulation would be to use the data model in Vue and to move the mouseover event listener to the Item:
Change the argument of over() to the location name (previously the event object):
methods: {
over(location) {
/* ... */
}
}
Move the #mouseover event-listener annotation from ul to the Item in the template, and pass the item.location as an argument:
<ul>
<Item v-for="item in items" #mouseover="over(item.location)" ... />
</ul>
Edit the Item's template to forward its mouseover event to the parent:
// Item.vue
<li #mouseover="$emit('mouseover', $event)" ... >
new Vue({
el: '#app',
data() {
return {
items: [
{id: 1, location: 'New York'},
{id: 2, location: 'Los Angeles'},
{id: 3, location: 'Chicago'},
]
}
},
components: {
Item: {
props: ['location'],
template: `<li #mouseover="$emit('mouseover', $event)" class="item">{{location}}</li>`,
}
},
methods: {
over(location) {
console.log(location)
}
}
})
<script src="https://unpkg.com/vue#2.6.7/dist/vue.min.js"></script>
<div id="app">
<ul>
<Item v-for="item in items"
:key="item.id"
:location="item.location"
#mouseover="over(item.location)" />
</ul>
</div>
I understand how this works from the VueJS Documentation:
<div id="components-demo">
<button-counter></button-counter>
</div>
And also this:
<div id="app">
<a v-on:click="loadElement(request, etc)">Load</a>
</div>
Is there any way to write the equivalent from a function and have Vue pick it up? For example as this:
<div id="app">
{{ writeComponent('component-name', etc) }}
or
{{ writeElement('loadElement') }} <!-- which then writes the component above -->
</div>
The reason for this is that in this context quite a few components might need to be written and writing it out in HTML would be cumbersome.
You could use the render function like below:
Vue.component('button-counter', {
template: '<span class="bc">bc</span>'
})
new Vue({
el: '#app',
data: {
request: 'req!',
etc: 'etc!',
buttonCounters: []
},
methods: {
loadElement(r, e) {
console.log('loadElement', r, e);
this.buttonCounters.push(r);
}
},
render(h) {
let bcs = this.buttonCounters.map(bc => h('button-counter'));
let loadLink = h('a', {on: {"click": ($event) => { this.loadElement(this.request, this.etc)}}}, ["Load"]);
return h('div', {attrs: {"id": "app"}}, [loadLink, " ", ...bcs])
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app"></div>
Though the same can be achieved via regular template and v-for:
Vue.component('button-counter', {
template: '<span class="bc">bc</span>'
})
new Vue({
el: '#app',
data: {
request: 'req!',
etc: 'etc!',
buttonCounters: []
},
methods: {
loadElement(r, e) {
console.log('loadElement', r, e);
this.buttonCounters.push(r);
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<a v-on:click="loadElement(request, etc)">Load</a>
<button-counter v-for="(bc, index) in buttonCounters" :key="index"></button-counter>
</div>
I've an issue to fill data from model into code when I'v more then one template with separate data. What I need is first template renders as many times as many objects in firstFormDetails array and same the second one. There example of my code below:
<div id="app">
<first v-for="item in secondFormDetails" track-by="id"></first>
<second v-for="item in firstFormDetails" track-by="id"></second>
</div>
<template id="template-first">
<div> {{ item.docNumber }}</div>
</template>
<template id="template-second">
<div> {{ item.docNumber }}</div>
</template>
And VueJs module as follows:
Vue.component('first', {
template: '#template-first',
data: function() {
return {
firstFormDetails: [
{docNumber: 7},
{docNumber: 7777},
{docNumber: 10000}
]
}
}
});
Vue.component('second', {
template: '#template-second',
data: function() {
return {
secondFormDetails: [
{docNumber: 1908},
{docNumber: 7777},
{docNumber: 10000}
]
}
}
});
new Vue({
el: '#app'
});
#vbranden is correct, move the v-for into the component
<template id="template-first">
<div v-for="item in firstFormDetails"> {{ item.docNumber }}</div>
</template>
<template id="template-second">
<div v-for="item in secondFormDetails"> {{ item.docNumber }}</div>
</template>
You must define what components you use. Let try to use this:
var first = Vue.component('first', {
template: '#template-first',
data: function() {
return {
firstFormDetails: [
{docNumber: 7},
{docNumber: 7777},
{docNumber: 10000}
]
}
}
});
var second = Vue.component('second', {
template: '#template-second',
data: function() {
return {
secondFormDetails: [
{docNumber: 1908},
{docNumber: 7777},
{docNumber: 10000}
]
}
}
});
new Vue({
el: '#app',
components: {
'first': first,
'second': second
}
});
in addition on #johnnynotsolucky 's answer, you will need a wrapper element out of v-for, since only allow only one element inside it.
<template id="template-first">
<div class="form-details">
<div v-for="item in firstFormDetails"> {{ item.docNumber }}</div>
</div>
</template>
var first = Vue.component('first', {
template: '#template-first',
props: ['item']
});
var second = Vue.component('second', {
template: '#template-second',
props: ['item']
});
new Vue({
el: '#app',
components: {
'first': first,
'second': second
},
data: function () {
return {
firstFormDetails: [
{docNumber: 7},
{docNumber: 7777},
{docNumber: 10000}
],
secondFormDetails: [
{docNumber: 1908},
{docNumber: 7777},
{docNumber: 10000}
]
}
}
});
<div id="app">
<first v-for="item in secondFormDetails" :item="item"></first>
<second v-for="item in firstFormDetails" :item="item"></second>
</div>
<template id="template-first">
<div> {{ item.docNumber }}</div>
</template>
<template id="template-second">
<div> {{ item.docNumber }}</div>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
When Vue app is loaded, it looks for component field, if component field is not defined then not component is loaded, if component field is defined, Vue looks for the definition of the component and parse it for syntax checking, once the syntax is correct the component is binded. This happens recursively for nested components.
Registering a component is mandatory