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!
CodePudding user response:
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 yourVue
instance - Add a
watcher
to the store: this is a standardVue
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/[email protected]/dist/vue.js"></script>
<div id="outside">
Counter outside: <span id="countervalue"></span>
</div>
<div id="app"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
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.
CodePudding user response:
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