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>
Related
How are we supposed to access a Vue component its data from outside the app? For example how can we get the data within a regular JavaScript onClick event triggered from a button that is in the DOM outside the Vue app.
In the following setup I have a hidden field which I keep updated with every action in the Vue app, this way I have the necessary data ready for the JS click event .. but I am sure there is a better way.
Currently my setup is the following:
VehicleCertificates.js
import { createApp, Vue } from 'vue'
import VehicleCertificates from './VehicleCertificates.vue';
const mountEl = document.querySelector("#certificates");
const app = createApp(VehicleCertificates, { ...mountEl.dataset })
const vm = app.mount("#certificates");
VehicleCertificates.vue
<template>
<div style="background-color: red;">
<h3>Certificates</h3>
<div>
<table class="table table-striped table-hover table-condensed2" style="clear: both;">
<thead>
<tr>
<th><b>Type</b></th>
<th><b>Valid From</b></th>
<th><b>Valid Till</b></th>
<th style="text-align: right;">
<a href="#" #click='addCertificate'>
<i class="fa fa-plus-square"></i> Add
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(certificate, index) in certificates" :key="index">
<td>{{ certificate.CertificateTypeDescription }}</td>
<td>
{{ certificate.ValidFrom }}
</td>
<td>
{{ certificate.ValidTo }}
</td>
<td>
<a href='#' #click="removeCertificate(index)" title="Delete" style="float: right;" class="btn btn-default">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
<tr v-show="certificates.length == 0">
<td colspan="4">
No certificates added
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { onMounted, ref } from "vue";
export default {
props: {
vehicleId: String
},
data() {
return {
count: 0,
certificates: ref([]),
types: []
}
},
created() {
onMounted(async () => {
let result = await axios.get("/api/v1.0/vehicle/GetCertificates", { params: { vehicleId: this.vehicleId } });
this.certificates.splice(0, 0, ...result.data);
this.certificatesUpdated();
});
},
methods: {
removeCertificate(index) {
this.certificates.splice(index, 1);
this.certificatesUpdated();
},
addCertificate() {
this.certificates.push({ CertificateTypeDescription: 'ADR', ValidFrom: 1, ValidTo: 2 });
this.certificatesUpdated();
},
certificatesUpdated() {
$("#VehicleCertificatesJson").val(JSON.stringify(this.certificates));
}
}
}
</script>
In the end I want to be able to send the data from the Vue app together with other non-Vue data on submit of an ASP.Net core razor page its form. The Vue app is just a specific part of the razor view and thus not an SPA.
Thanks in advance!
Here's a pretty complex solution - but at least it's quite flexible.
Create a Vue.observable store: this is nothing else, but a reactive object
Create the methods you want to use to update the observable in your Vue instance
Add a watcher to the store: this is a standard Vue object & a $watch set on it
Set up the callback if the store changes (watcher instance): this callback is where you can connect with the "outside world"
Snippet:
const countervalueSpan = document.getElementById('countervalue')
// creating a Vue.observable -
// reactive object
const store = Vue.observable({
counter: 0
})
// setting up a watcher function
// using the Vue object
function watch(obj, expOrFn, callback, options) {
let instance = null
if ('__watcherInstance__' in obj) {
instance = obj.__watcherInstance__
} else {
instance = obj.__watcherInstance__ = new Vue({
data: obj
})
}
return instance.$watch(expOrFn, callback, options)
}
// creating a watcher that reacts
// if the given store item changes
const subscriber = watch(
store,
'counter',
(counter) => {
let html = `<strong>${counter}</strong>`
countervalueSpan.innerHTML = html
}
)
new Vue({
el: "#app",
methods: {
increment() {
store.counter++
}
},
template: `
<div>
<button
#click="increment"
>
INCREMENT
</button>
</div>
`
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<div id="outside">
Counter outside: <span id="countervalue"></span>
</div>
<div id="app"></div>
You can always access the store object from the outside easily (e.g. store.counter) & you always get the current state of the object. The watcher is needed to react to changes automatically.
I would advise against doing it and wrapping everything in Vue or directly use JQuery, depending on how your website is build. Having multiple frontend frameworks is usually a bad idea and introduces unnecessary complexity.
However, if you really need to access Vue's data with plain javascript you can use the following:
const element = document.getElementById('#element-id');
element._instance.data // or element._instance.props, etc...
For the properties available you can look at the inspector (see attached screenshot).
Inspector screenshot
I'm trying to make a simple app to output the API call from https://docs.coincap.io into the HTML table using Vue.js, bacause I will need to add some other features.
The problem is, that I can not get my array of objects into a page using a v-for and moustache to check variable data.
I've tried to use vue lifetime hooks to get my API call data into a variable, and different places to place my data into an object array.
<div id="app">
TEST APPLICATION FOR COINCAP <br>
<div id="xhrRes">
{{ items }}
</div>
<table class="table-o">
<tr class="table-o__head">
<th class="table-o__rank">Rank</th>
<th>Name</th>
<th>Price</th>
<th>Market Cap</th>
<th>Volume</th>
<th>Change</th>
</tr>
<tr v-for="(item, index) in items">
<td>
{{ index }}
</td>
<td>
{{ item.name }}
</td>
<td>
{{ item.price }}
</td>
<td>
{{ item.marketCapUsd }}
</td>
<td>
{{ item.volumeUsd24Hr }}
</td>
<td>
{{ item.changePercent24Hr }}
</td>
</tr>
</table>
</div>
export default {
name: 'app',
data () {
return {
msg: 'Welcome to Your Vue.js App',
xhrUri: 'https://api.coincap.io/v2/assets?limit=15',
xhrResult: '',
items: []
}
},
updated() {
// this.items = this.xhrRequest();
this.xhrRequest();
// console.log(this.items);
},
methods: {
xhrRequest: function() {
let xhr = new XMLHttpRequest();
xhr.open('GET', this.xhrUri, true);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) {
return;
}
if (xhr.status === 200) {
this.items = JSON.parse(xhr.responseText).data;
console.log(this.items);
} else {
console.log('err', xhr.responseText)
}
}
}
}
}
I expected to have an array of object in {{ items }} and a filled table, but got my array of objects undefined and my table empty
I suggest using the created hook instead of updated.
The bigger problem is the this context inside xhr.onreadystatechange. It won't be pointing at the Vue instance. Using an arrow function is the simplest fix:
xhr.onreadystatechange = () => {
Arrow functions retain the this value from the surrounding scope.
The usual alternatives also apply, such as using bind on the function or grabbing this in a closure using const that = this. Vue automatically binds functions in the methods to the correct this value so if you introduced another method as the handler for onreadystatechange that would also work.
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/
I have one issue. I want to pass function link to the child component. It's working but in HTML I get that code. It's correct how improve it?
I have Vue instance
app = new Vue({
... some code
data: {
onAppClose: null,
onAppSend: null
}
})
I want to add from global window any function. Or register function in Vue instance
app.onSend = () => console.log('data')
And pass this function to child
<div id="app">
<dynamsoft-component v-if="displayComponent"
:docs="docs"
:onAppSend="onSend"
:onAppClose="onClose"
></dynamsoft-component>
</div>
But I get this HTML template in console
<div id="app">
<div onappsend="()=>{}" onappclose="function (data) {
console.warn('dwdawad')
console.log('data')
}"></div>
</div>
You example code is not making a lot of sense - do you want to add a listener not a div or pass a function to a child component?`
I assume the latter. Vue has custom events for that .
Parent template:
<div v-on:appsend="someMethod" v-on:appclose="someOtherMethod"></div>
Parent component methods:
methods: {
someOtherMethod: function (data) {
console.warn('dwdawad')
console.log('data')
},
// ...
}
And then emit form the child:
this.$emit('appclose', {id: 'whatever'} /*pass data here*/)
Edit:
I still don't see how those functions would end up directly in the template, but the real problem is: HTML is not case-sensitive. so :onAppSend becomes :onappsend. You have to use kebap-case: :on-app-send. Vue will convert it to onAppSend in the component.
I have never used Vue.js before now..
But having a look at the how to on their site, this seems to work
In Vue style guide have recommendations about props naming
https://v2.vuejs.org/v2/style-guide/#Prop-name-casing-strongly-recommended
Vue.component('dynamsoft-component', {
props: ['onAppSend'],
template: '<button v-on:click="buttonclick">click me</button>',
methods: {
buttonclick(e){
// Check if onAppSend is defined.
if(Boolean(this.onAppSend)){
this.onAppSend();
}
}
}
})
new Vue({
el: '#app',
methods: {
onSend: function(){
console.log('child clicked');
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<dynamsoft-component :on-app-send="onSend"></dynamsoft-component>
</div>
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>