Function call inside a template - javascript

I have a vue component for simple pagination of a table. I need to highlight a row of said table on mouse over. What I did so far is create the template and create the dynamically loaded ajax powered next and previous buttons. The issue is that highlighting because its a dynamic element.
Here is the template code:
<tbody class="table-body">
<template id="template-student">
<tr #mouseover="highlightRow()" class="students-row" data-student-url="{{ URL::route("student", 1) }}">
<td>
<div class="student-image">
<div class="flag"></div>
</div>
</td>
<td>
#{{ student.student_firstname }}
</td>
<td>
#{{ student.student_lastname }}
</td>
<td>
#{{ student.student_telephone }}
</td>
<td>
#{{ student.student_fathersname }}
</td>
</tr>
</template>
</tbody>
And the vue code:
Vue.component('student', {
template: '#template-student',
props: ['student'],
});
new Vue({
el: '#app',
data: {
students: [],
pagination: {}
},
ready() {
this.fetchStudents();
},
methods: {
fetchStudents(pageNumber) {
if (pageNumber === undefined) {
pageNumber = 1;
}
const vm = this;
const pageUrl = `/api/on-roll/all/${pageNumber}`;
this.$http.get(pageUrl)
.then((response) => {
vm.makePagination(response.data.pagination);
vm.$set('students', response.data.data);
});
},
makePagination(data) {
const pagination = {
current_page: data.current_page,
total_pages: data.total_pages,
next_page: data.next_page,
prev_page: data.prev_page
};
this.$set('pagination', pagination);
},
highlightRow(){
console.log("test")
}
}
});
The issue is when I hover over any row I get an error:
Uncaught TypeError: scope.highlightRow is not a function
Now I understand (more or less) why is this happening, I am calling a function inside the template tags (if I cal outside on a example element it works). Some research lead me to this question dynamical appended vuejs content: scope.test is not a function But the help there didn't help me. I couldn't figure out how to implement the solution to my example.

Any time you want a component to have access to something from a parent, pass it in as a prop.
new Vue({
el: '#app',
components: {
myComponent: {
template: '<div #mouseover="onHover">Hi</div>',
props: ['onHover']
}
},
methods: {
highlightRow() {
console.log('test');
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<my-component :on-hover="highlightRow"></my-component>
</div>

Related

Rendering items via props after handling some processing using Vue.js and Vue CLI 3

I have a main component called App.vue and a child one MyTable.vue which wraps a table of data and showing only the 10 first rows, i'm working with vue cli 3 and when i ran the npm run serve command and go to the given address, it renders only the head of my table, but when i add some code in the mounted() function inside MyTable.vue like console.log() it renders also the body of my table, the problem comes back when i refresh my page, how can i deal with that ?
these is my components
App.vue
<template>
<div class="main-page">
<my-table title="todos" :cols="todo_attr" :rows_data="todo_data"></my-table>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
import todos from './assets/todos.json'
export default {
name: 'app',
data(){
return{
todo_attr:[
"todoId","id","title","completed"
],
todo_data:[]
}
},
components: {
MyTable
},
mounted(){this.todo_data=todos;}
}
</script>
MyTable.vue
<template>
<div class="vet-container">
<table>
<thead>
<th class="tab-head-cell" v-for="col in cols" :key="col">{{col}}</th>
</thead>
<tbody>
<tr class="tab-rows_data-row" v-for="row in currentPageData" :key="row.id">
<td class="tab-rows_data-cell" v-for="(cell,key,index) in row" :key="key+index" > {{cell}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'my-table',
props: {
title: String,
cols: {},
rows_data: {}
},
data() {
return {
currentPageData: {}
};
},
methods:{
createFirstPage(){
this.currentPageData = this.rows_data.slice(0, 10);
}
}
,
mounted() {
this.createFirstPage();
}
}
</script>
First, you declared cols and rows_data as objects in MyTable.vue but you declared them as arrays in App.vue. You also declared currentPageData as an object instead of an array. It may cause some errors.
Second, you should prefer do this:
<template>
<div class="vet-container">
<table>
<thead>
<th class="tab-head-cell" v-for="col in cols" :key="col">{{col}}</th>
</thead>
<tbody>
<tr class="tab-rows_data-row" v-for="row in currentPageData" :key="row.id">
<td
class="tab-rows_data-cell"
v-for="(cell,key,index) in row"
:key="key+index" >{{cell}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'my-table',
props: {
title: String,
cols: Array,
rows_data: Array,
},
data() {
return {
index: 0,
size: 10,
};
},
computed: {
currentPageData() {
const start = this.index * this.size;
const end = start + this.size;
return this.rows_data.slice(start, end);
},
},
};
</script>
You could then pass index in props and change it on parent on click on buttons.
Little explanation of the computed property: this property act like calculated data. You can use it just like any other data or props and you can calculate its content based on other stuff, like here, with the current index and the size of page.

Vue.js $scopedSlots don't work for Vue instance

I'm working in a Vue component that I'll publish when it's finished that wraps Clusterize.js (there is a vue-clusterize component but it only works for v1.x). What I want to achieve is to render a huge list of items pretty fast using Vue. I actually need it for a table. I tried with vue-virtual-scroll but it doesn't support tables and the performance is not that good. So I wanted to try with Clusterize.js.
Because I want this component to be highly configurable I decided that you will be able to provide a scoped slot for each row of the items list where you will receive the item. The problem is when I try to assign the scoped slot from the clusterize componets to each row before mounting the component it doesn't work.
Here you have some snippets of my code (it is just a mvp)
clusterize.vue
Template
<div class="clusterize">
<table>
<thead>
<tr>
<th>Headers</th>
</tr>
</thead>
</table>
<div
ref="scroll"
class="clusterize-scroll">
<table>
<tbody
ref="content"
class="clusterize-content">
<tr class="clusterize-no-data">
<td>Loading...</td>
</tr>
</tbody>
</table>
</div>
Script
import Vue from 'vue';
import Clusterize from 'clusterize.js';
export default {
name: 'Clusterize',
props: {
items: {
type: Array,
required: true,
},
},
data() {
return {
clusterize: null,
};
},
computed: {
rows() {
return this.items.map(item => '<tr><slot :item="1"/></tr>');
},
},
watch: {
rows() {
this.clusterize.update(this.rows);
},
},
mounted() {
const scrollElem = this.$refs.scroll;
const contentElem = this.$refs.content;
this.clusterize = new Clusterize({
rows: this.rows,
scrollElem,
contentElem,
});
this.clusterize.html = (template) => {
contentElem.innerHTML = template;
const instance = new Vue({ el: contentElem });
instance.$slots = this.$slots;
instance.$scopedSlots = this.$scopedSlots;
instance.$mount();
console.log(instance.$scopedSlots); // empty
console.log(instance.$slots) // not empty
};
},
};
component.vue
<clusterize :items="test">
<template slot-scope="props">
item
</template>
</clusterize>
The thing is that if it don't use a scoped slot it works perfectly but I really need to use them otherwise the component doesn't have any sense.
I'll appreciate any help or advice.
Thank you so much in advance.
The issue should be caused by mount different Vue instance to same el multiple times (please look into the second demo, you shouldn't mount multiple instances to same element, the following instances will not mount since the element is already “blocked” by first instance).
My solution: create Vue instance (doesn't bind to el) in the air then take vm.$el as the output.
Please look into below simple demo,
Vue.config.productionTip = false
Vue.component('clusterize', {
template: `<div class="clusterize">
<table>
<thead>
<tr>
<th>Headers</th>
</tr>
</thead>
</table>
<div
ref="scroll"
class="clusterize-scroll">
<table>
<tbody
ref="content"
id="clusterize-id"
class="clusterize-content">
<tr class="clusterize-no-data">
<td>Loading...</td>
</tr>
</tbody>
</table>
</div></div>`,
props: {
items: {
type: Array,
required: true,
},
},
data() {
return {
clusterize: null,
clusterVueInstance: null
};
},
computed: {
rows() {
return this.items.map(item => {
return '<tr><td><span>' +item+'</span><slot :item="1"/></td></tr>'
});
},
},
watch: {
rows() {
this.clusterize.update(this.rows);
},
},
mounted() {
const scrollElem = this.$refs.scroll;
const contentElem = this.$refs.content;
this.clusterize = new Clusterize({
rows: this.rows,
scrollElem,
contentElem,
});
this.clusterize.html = (template) => {
this.clusterize.content_elem.innerHTML = template;
if(this.clusterVueInstance) {
this.clusterVueInstance.$destroy()
this.clusterVueInstance = null
}
this.clusterVueInstance = new Vue({ template: '<tbody>'+template+'</tbody>' })
//or use Vue.extend()
this.clusterVueInstance.$slots = this.$slots
this.clusterVueInstance.$scopedSlots = this.$scopedSlots
this.clusterVueInstance.$mount()
this.clusterize.content_elem.innerHTML = this.clusterVueInstance.$el.innerHTML
//console.log(this.clusterVueInstance.$scopedSlots); // empty
//console.log(this.clusterVueInstance.$slots) // not empty*/
};
}
})
app = new Vue({
el: "#app",
data() {
return {
test: ['Puss In Boots', 'test 1', 'test2'],
index: 0
}
},
mounted: function () {
//this.test = ['Puss In Boots', 'test 1', 'test2']
},
methods: {
addItem: function () {
this.test.push(`test ` + this.index++)
}
}
})
<link href="https://cdn.bootcss.com/clusterize.js/0.18.0/clusterize.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/clusterize.js/0.18.0/clusterize.min.js"></script>
<div id="app">
<button #click="addItem()">
Add Item
</button>
<clusterize :items="test">
<template slot-scope="props">
item: {{props.item}}
</template>
</clusterize>
</div>
Please look into below demo: created multiple Vue instance to same el, but Vue always uses first instance to render (I can't find any useful statement at Vue Guide, probably from the source codes from Vue Github we can find out the logic. If someone knows, please feel free to edit my answer or add a comment).
Vue.config.productionTip = false
app1 = new Vue({
el: '#app',
data () {
return {
test: 'test 1'
}
},
mounted(){
console.log('app1', this.test)
}
})
app2 = new Vue({
el: '#app',
data () {
return {
test: 'test 2'
}
},
mounted(){
console.log('app2', this.test)
}
})
//app1.$data.test = 3
//app1.$mount() //manual mount
app2.$data.test = 4
app2.$mount() //manual mount
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/clusterize.js/0.18.0/clusterize.min.js"></script>
<div id="app">
<a>{{test}}</a>
</div>

Cannot render multiple components in Vue

I'm just starting with Vue and I have defined 2 components, however, I cannot render them in the same 'Vue' instance.
Here are my 2 components:
Vue.component('mat-example', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
Next, I do have the 'Vue' entry point defined:
var app = new Vue({
el: '#app'
});
And in my HTML, I do have the following code:
<div id="app">
<button-counter />
<mat-example />
</div>
The 'Vue' development tools does only show the 'button-counter' component:
If I remove the 'button-counter', the 'mat-example' is showed up in the Vue Developer Tools.
How does it come that I cannot render those 2 components in my Vue entry point?
this is going to work:
<div id="app">
<button-counter></button-counter>
<mat-example></mat-example>
</div>
demo:
Vue.component('mat-example', {
data: function() {
return {
count: 0
}
},
template:
'<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
Vue.component('button-counter', {
data: function() {
return {
count: 0
}
},
template:
'<button v-on:click="count++">You clicked me {{ count }} times</button>'
})
var app = new Vue({
el: '#app'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<button-counter></button-counter>
<mat-example></mat-example>
</div>
Use this tag instead of using self closing tag
<div id="app">
<button-counter></button-counter>
<mat-example></mat-example>
</div>

Vue inline template not rendering

When using Vue inline template, I was expecting the following code to render a,b,c in a table. Instead, it renders nothing with no console errors. What am I doing wrong?
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function() {
app = new Vue({
el: '#app',
data: {
items: ["a", "b", "c"]
}
});
Vue.component('items-component', {
props: ['items'],
computed: {
showTable: function() {
return this.items.length > 0
}
}
})
})
</script>
<div id="app">
<items-component inline-template>
<table v-if="showTable">
<tr v-for="item in items">
<td>{{item}}</td>
</tr>
</table>
</div>
</div>
First, you don't need jQuery wrapper to run your Vue code.
Second, you should specify data in your component, not on constructor.
Third, You are expecting a prop on your items-component to get, but not sending any, you should remove it too. Also data must return an object, and it should be a function
Below, the working one
https://jsfiddle.net/2yyjy3bc/1/

Vue.js component click event not finding method

I'm fairly new to VUE.JS and I'm trying to integrate it into an MVC project.
In my MVC view I have my app div.
#RenderPage("~/Views/Product/Templates/product-details-template.cshtml")
<div id="app">
<product-details-component></product-details-component>
</div>
The component is rendering fine and fetching the database for data which also works fine.
<template id="product-details-container">
<div class="wrapper">
<div class="container-body">
<div class="container-header">
<h1>3 Year Discount</h1>
<span class="details"><i class="fa fa-long-arrow-left"></i> Back to Product Details</span>
</div>
<div class="container-table">
<table class="table-product-details">
<tr>
<td on:click="UpdateProduct" class="table-title spaceUnder">Description: </td>
<td class="table-value spaceUnder">{{ description }}</td>
<td class="table-title spaceUnder">Product Code: </td>
<td class="table-value spaceUnder">{{ code }}</td>
<td class="table-title spaceUnder">Scheme Code: </td>
<td class="table-value spaceUnder">{{ schemecode }}</td>
</tr>
But when I'm trying to fire an event / change the data of vm from the component it simply doesn't work.
Here is the actual js I removed the props from the component and a lot of data from the vm for better readability.
Vue.component('product-details-component', {
template: '#product-details-container'
});
var vm = new Vue({
el: '#app',
data: {
show: true
},
beforeMount() {
this.UpdateProduct();
},
methods: {
UpdateProduct: function() {
axios.get('myapiurl' + this.productId)
.then(function(response) {
var data = response.data;
// assigning data to the vm
});
},
When i try to run the application it just crashes and in the console it says "UpdateProduct is not defined".
Right Im not sure maybe the problem is because #RenderPage is running before the javascript and trying to attach the event to a method that doesn't exist at that time, but then why would my attribute bindig work? {{ description }} is working fine. I removed the code when I assign value to description and etc but it doesn't matter in this case. Any suggestions?
It's not the parenthesis but the lack of proper tagging.
it should be
<td v-on:click="UpdateProduct" class="table-title spaceUnder">Description: </td>
Vue properties all start with v- except in cases of the shorthand which in this case could be #click="UpdateProduct"
You have missed parenthesis in HTML, it should be like following:
<td on:click="UpdateProduct()" class="table-title spaceUnder">Description: </td>
Right I have managed to make it work.
I added methods options to the component itself, and from there I can call the app's method.
Vue.component('product-details-component', {
template: '#product-details-container',
methods: {
CallMethod: function(){
return vm.MethodIwantToCall();
}
}
var vm = new Vue({ ....
methods: {
MethodIwantToCall () => ...
}
You are missing something.
Your component should have the click method, so yo should add the click into your template and then call to vue app.
Vue.component('product-details-component', {
template: '#product-details-container',
methods:{
UpdateProduct: function() {
//Here you are calling your real method from the template
vm.UpdateProduct();
}
}
});
change #click to ##click
<div id="app">
<button ##click="count++">{{ count }}</button>
</div>
<script type="module">
import { createApp } from 'vue'
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
</script>

Categories

Resources