Use Vue component data in JavaScript - javascript

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

Related

How can I avoid mutation prop and modify the same in other component?

I'm a new Dev VUE.JS and reading a lot too about "share/pass" data to anothers components, but I'm not getting perceive this examples useful in my component.
Table.vue - Fetch API and populate a table.
File.vue - Instance Table.vue and here I'm trying to override the component variable.
Table:
<template>
<v-simple-table class="mt-5" id="tableOs" dense >
<thead>
<tr>
<th v-for="h in headers" v-bind:key="h.value">{{h.text}}</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" v-bind:key="item.id">
<td>{{item.id}}</td>
<td>{{item.customer}}</td>
<td>{{item.neighbor}}</td>
</tr>
</tbody>
</v-simple-table>
</template>
<script>
export default {
data (){
return{
headers: [
{ text: 'Id', value: 'id' },
{ text: 'Name', value: 'customer' },
{ text: 'Neighbor', value: 'neighbor' }
],
items: this.pitems,
}
},
props:{
'pitems': []
},
async created(){
const res = await fetch(
"http://localhost:5000/buscarx/3",
{
method:"GET"
}
);
const data = await res.json()
this.items = data
}
}
</script>
and my Files.vue
<template>
<v-container
id="dashboard"
fluid
tag="section"
>
<v-btn #click="modify()"> </btn>
<TableOS />
</v-container>
</template>
<script>
export default {
components:{
Table: () => import('./components/Table')
},
methods:{
modify(){
console.log(this.item)
}
}
}
</script>
Anyone can me help, suggest the best way to use this fetch and modify the data in the table to use a Filter for example in the future?
You can use props to pass variables from one component to another.
For example, you need to fetch your items data in the created hook inside File.vue. Save the fetched items in items array.
Then, you can pass this items array via prop to the Table.vue component.
You can modify, filter, map and perform other tasks to the items array in the File.vue. The changes will be reactive and automatically reflect in the Table.vue file.
You can also share variables between components using vuex. But, in case of variable sharing between child and parent, like in your case, you don't need this. But if the relation between components is not so simple or they are not related you have to use vuex.

VUE.JS/HTTP. The problem with getting a result of http query to an html using vue.js

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.

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.

Function call inside a template

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>

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