I am using the Vue3 options API within a static HTML page (well, actually its dynamically generated .NET MVC, but that is irrelevant to my question). I had originally had this working with Vue2x, but after updating to Vue3, my original method doesn't seem to work anymore.
I have some plain JavaScript code that can be used to format a person's date of birth in a few different ways. I want to be able to reuse this code across several different pages/apps. Here is a few lines of my "DataFormatting.js" file:
// DataFormatting.js
function formatDob(dob) {
if (dob) {
dob = new Date(dob);
if (isValidDate(dob)) {
var options = { month: 'short', day: 'numeric', year: 'numeric' };
return dob.toLocaleDateString("default", options);
}
}
return "";
}
function formatDobWithAge(dob) {
if (dob) {
dob = new Date(dob);
if (isValidDate(dob)) {
var age = getAge(dob, new Date());
var options = { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' };
return dob.toLocaleDateString("default", options) + " (" + age + " yo)";
}
}
return "";
}
// other formatting functions omitted
function isValidDate(d) {
return d instanceof Date && !isNaN(d);
}
Now I want to be able to reuse this file/library across multiple different pages (or Vue app instances).
I used to be able to just simply include this file with a <script>
tag like so:
<script src="~/js/DataFormatting.js"></script>
And then, from the template, I could reference my JavaScript library functions like so:
<tr v-for="person in people">
<td>{{ formatDobWithAge(person.DateOfBirth) }}</td>
</tr>
Using Vue3, this is what my setup looks like:
<script src="~/lib/vue/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
return {
people: @Html.Raw(JsonSerializer.Serialize(Model.People))
}
},
mounted() {
},
computed: {
},
watch: {
},
methods: {
}
}).mount('#app');
</script>
Why can I not reference this extra JavaScript library anymore? What do I need to do so that I can reference a set of JS functions for formatting purposes? Do I need to get away from a plain JS file and move it to something more Vue (component?) friendly?
I am using the Vue3 options API within a static HTML page (well, actually its dynamically generated .NET MVC, but that is irrelevant to my question). I had originally had this working with Vue2x, but after updating to Vue3, my original method doesn't seem to work anymore.
I have some plain JavaScript code that can be used to format a person's date of birth in a few different ways. I want to be able to reuse this code across several different pages/apps. Here is a few lines of my "DataFormatting.js" file:
// DataFormatting.js
function formatDob(dob) {
if (dob) {
dob = new Date(dob);
if (isValidDate(dob)) {
var options = { month: 'short', day: 'numeric', year: 'numeric' };
return dob.toLocaleDateString("default", options);
}
}
return "";
}
function formatDobWithAge(dob) {
if (dob) {
dob = new Date(dob);
if (isValidDate(dob)) {
var age = getAge(dob, new Date());
var options = { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' };
return dob.toLocaleDateString("default", options) + " (" + age + " yo)";
}
}
return "";
}
// other formatting functions omitted
function isValidDate(d) {
return d instanceof Date && !isNaN(d);
}
Now I want to be able to reuse this file/library across multiple different pages (or Vue app instances).
I used to be able to just simply include this file with a <script>
tag like so:
<script src="~/js/DataFormatting.js"></script>
And then, from the template, I could reference my JavaScript library functions like so:
<tr v-for="person in people">
<td>{{ formatDobWithAge(person.DateOfBirth) }}</td>
</tr>
Using Vue3, this is what my setup looks like:
<script src="~/lib/vue/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
createApp({
data() {
return {
people: @Html.Raw(JsonSerializer.Serialize(Model.People))
}
},
mounted() {
},
computed: {
},
watch: {
},
methods: {
}
}).mount('#app');
</script>
Why can I not reference this extra JavaScript library anymore? What do I need to do so that I can reference a set of JS functions for formatting purposes? Do I need to get away from a plain JS file and move it to something more Vue (component?) friendly?
You can easily convert DataFormatting.js
into a module, the moment you make something in a file exportable it qualifies as a module.
Ensure your file is in the scope of your src
directory and import your functions wherever you need them in the specific .vue files or other JS files that need to leverage those functions.
// DateFormatting.js
export function formatDob(dob) { ... }
export function formatDobWithAge(dob) { ... }
function isValidDate(d) { ... }
// example.vue
<script>
import { formatDob, formatDobWithAge } from 'DateFormatting.js'
... {
data() { ... }
methods: { formatDob, formatDobWithAge }
...
</script>
P.S. To expand on understanding ES Modules, You can do the same with ES module version of lodash or any ES supporting lib for that matter, The lodash-es
lib can be leveraged in the template with the least effort by registering only what you use in methods
.
https://www.npmjs.com/package/lodash-es
It can be beneficial to write the application in a modular, componentized way instead of relying on global variables and using Vue as a template engine, but this is not a requirement.
Vue templates are DSL, JavaScript expressions aren't evaluated as is. {{ formatDobWithAge(...) }}
means instance.formatDobWithAge(...)
and doesn't
evaluate formatDobWithAge(...)
in global scope. Even if this was possible in Vue 2, this is unsafe and not supported in Vue 3.
In order to make this work, formatDobWithAge
needs to be exposed to component instance. To do this globally, Vue plugins are used. In this case the plugin can be anonymous:
createApp({ ... })
.use({
install: (app) => {
app.config.globalProperties.formatDobWithAge = formatDobWithAge;
}
})
.mount('#app');
This both exposes formatDobWithAge
in component templates and this.formatDobWithAge
in options API.
You can convert DataFormatting.js
into a composable, which than makes it reusable across your components.
Your composable code will look something like this
import { ref } from 'vue';
export function useDataFormatting() {
const isValidDate = (date) => date instanceof Date && !isNaN(date);
const formatDob = (dob) => {
if (dob) {
const date = new Date(dob);
if (isValidDate(date)) {
const options = { month: 'short', day: 'numeric', year: 'numeric' };
return date.toLocaleDateString('default', options);
}
}
return '';
};
const getAge = (dob, currentDate) => {
const diff = currentDate - dob;
const ageDate = new Date(diff); // Time difference as milliseconds
return Math.abs(ageDate.getUTCFullYear() - 1970);
};
const formatDobWithAge = (dob) => {
if (dob) {
const date = new Date(dob);
if (isValidDate(date)) {
const age = getAge(date, new Date());
const options = { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' };
return `${date.toLocaleDateString('default', options)} (${age} yo)`;
}
}
return '';
};
return {
formatDob,
formatDobWithAge,
isValidDate,
};
}
Now you can use this composable in your components like this
<template>
<div>
<p>Date of Birth: {{ formattedDob }}</p>
<p>Date of Birth with Age: {{ formattedDobWithAge }}</p>
</div>
</template>
<script>
import { ref } from 'vue';
import { useDataFormatting } from '@/composables/useDataFormatting';
export default {
setup() {
const { formatDob, formatDobWithAge } = useDataFormatting();
const dob = ref('1990-01-15'); // Example DOB
const formattedDob = formatDob(dob.value);
const formattedDobWithAge = formatDobWithAge(dob.value);
return {
formattedDob,
formattedDobWithAge,
};
},
};
</script>
Thanks to @Marc's answer and @EstusFlask's answer and comments, I've got a working solution which I will summarize here so that it might help others!
The original goal was to be able to reuse a 'library' of JavaScript functions that formatted data in a Vue3 HTML template.
Here is an example 'library' JS file (mark the functions you want exported with export
:
export function formatDob(dob) {
if (dob) {
dob = new Date(dob);
if (isValidDate(dob)) {
var options = { month: 'short', day: 'numeric', year: 'numeric' };
return dob.toLocaleDateString("default", options);
}
}
return "";
}
export function formatDobWithAge(dob) {
if (dob) {
dob = new Date(dob);
if (isValidDate(dob)) {
var age = getAge(dob, new Date());
var options = { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' };
return dob.toLocaleDateString("default", options) + " (" + age + " yo)";
}
}
return "";
}
function getAge(dob, refDate) {
// calculate age
return age;
}
function isValidDate(d) {
return d instanceof Date && !isNaN(d);
}
Here is the creation of the Vue app:
<script src="~/lib/vue/dist/vue.global.js"></script>
<script type="module"> // make sure type="module"
const { createApp } = Vue
// import all exported functions with '* as'
import * as dateFormatting from '/js/DateFormatting.js';
createApp({
data() {
return {
people: @Html.Raw(JsonSerializer.Serialize(Model.People))
}
},
methods: {
...dateFormatting // the spread operator is important here
}
}).mount('#app');
</script>
And then to use an imported function in the template:
<tr v-for="person in people">
<td>{{ formatDobWithAge(person.DateOfBirth) }}</td>
</tr>
Note that you do not qualify formatDobWithAge
with dateFormatting
like dateFormatting.formatDobWithAge(dob)
(<- wrong!)