1

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!

2 Answers 2

2

Here's a pretty complex solution - but at least it's quite flexible.

  1. Create a Vue.observable store: this is nothing else, but a reactive object
  2. Create the methods you want to use to update the observable in your Vue instance
  3. Add a watcher to the store: this is a standard Vue object & a $watch set on it
  4. 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>

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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

2 Comments

The thing is. In our current framework we can quickly generate basic CRUD pages by using .net core MVC. From time to time we need add dynamic behavior to such a page. For example a 'Vehicle' page on which a list of 'Certificates' needs to be managed ( add/remove/edit ). It is that list of certificates I'm trying to do with Vue. Alternative would indeed be to create a partial razor view with JQuery code?
If you use Vue only for this part and JQuery throughout the website I'd rebuild the list of certificates using only JQuery, and this way it would probably make sense to use the hidden input as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.