External javascript from public/index.html not loading in Vue component - javascript

I want to use Mathjax on my website. I put in the <head> section of public/index.html:
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax#3/es5/tex-mml-chtml.js"></script>
And, in my component :
<template>
<div v-html="note_content"></div>
</template>
<script>
import { typeset, dummy_typeset } from '../assets/js/text_processing.js';
import showdown from 'showdown';
const converter = new showdown.Converter({ tables: true });
export default {
data () {
return {
}
},
computed: {
note_content: function() {
return typeset(this.exchange_data);
}
},
props: ['exchange_data'],
watch: {
note_content: function() {
Mathjax.typesetPromise();
}
}
}
</script>
However on runtime, I get the error :
Uncaught (in promise) ReferenceError: Mathjax is not defined
What I do not understand is that the css which is located in the head of public/index.html is correctly loaded and rendered in the component. Also I read elsewhere that loading external javascript this way should make it available to all components. What is wrong ?

I think it is connected to webpack config, but i might be wrong. Anyway, have you tried this method?
How to add external JS scripts to VueJS Components?
It enforces the script to be loaded.

Contrarily to what is written at https://docs.mathjax.org/en/latest/web/typeset.html, the syntax for asynchronous rendering is not :
Mathjax.typesetPromise()
But (notice the case):
MathJax.typesetPromise()
The typo could be detected from the surrounding context.
Also not that to use typeset Mathjax in a Vue component, the virtual DOM should be processed before calling the typesetPromise method, so an example of working code would be:
watch: {
note_content: function() {
this.$nextTick(MathJax.typesetPromise);
}
}

Related

How to access "this" context (Vue/Nuxt instance) in a VanillaJS file?

I have a function inside a .js file in a nuxt 2 project.
Basically I need to use a nuxt module property from nuxt.config like this this.nuxt.options. inside a function in a normal .js file.
For example:
aNormalJsFile.js
if (this.nuxt.options.module.triggers.isActive) {
// do something
}
But now I can't because it doesn't know what is Nuxt of course. I'm getting this error:
Cannot read properties of undefined (reading 'nuxt')
If you want to inject a Vue/Nuxt instance inside of a .js file (vanilla JS), you can follow this helper function approach
/src/utils/printCoolNumber.js
export const printIt = (VueInstance) => console.log(VueInstance.coolNumber)
// ☝🏻 basically does console.log(this.coolNumber) as in a .vue file
Any .vue file
<script>
import { printIt } from "#/utils/printCoolNumber";
export default {
data() {
return {
coolNumber: 12,
};
},
mounted() {
printIt(this) // πŸ‘ˆπŸ» Vue instance ("this") passed here, it prints 12 in the console
},
};
</script>
I wouldn't say that this is the best approach in the long run (using Composables is usually still better) but it is great for small helper functions where you don't need too much Vue-specific methods.

How to Add Client-side Scripts to Nuxt.js?

I've been reading Nuxt.js's documentation on Plugins and the config nuxt file for adding scripts. I tried both, but no luck.
I'm trying to add the following code (located in /static/js/scripts.js):
$(window).scroll(function(){
if ($(window).scrollTop() >= 200) {
$('nav').addClass('fixed-header');
}
else {
$('nav').removeClass('fixed-header');
}
});
Can someone help me please?
In regular HTML usage, I just do the script tags before the closing body tag (for the sake of user experience). I was able to get the scripts.js to load in a Vue.js 2.0 framework, but once I moved everything to Nuxt.js, and tried to adapt it to Nuxt.js, everything else worked, except the scripts.js file.
[SOLVED]!
I figured it out! I had to refer back to the Vue.js documentation since the Nuxt.js documentation didn't help me at all.
So I basically went into the vue file that contained the navigation bar I wanted the class to toggle appear in based on scroll. I also had to modify the javascript so it's ESLINT friendly... So for the code underneath is the equivalent of what I wrote in my question.
<script>
export default {
name: 'MainNav',
data: function () {
return {
fixedOnScroll: false
}
},
methods: {
handleScroll () {
if (window.scrollY >= 200) {
this.fixedOnScroll = true
} else {
this.fixedOnScroll = false
}
}
},
created () {
if (process.browser) {
window.addEventListener('scroll', this.handleScroll)
}
},
beforeUpdate () {
if (process.browser) {
window.addEventListener('scroll', this.handleScroll)
}
}
}
</script>
Once that was settled, I had to use the vue bind class in the template tag on the same file.
<nav class = "navbar-expand-lg text-center" v-bind:class="{ 'fixed-header': fixedOnScroll }">
Everything worked perfectly after that!
The official guide in Nuxt v1.4.0 document is here: https://nuxtjs.org/faq/window-document-undefined#window-or-document-undefined-
If you need to specify that
you want to import a resource only on the client-side, you need to use
the process.browser variable.
For example, in your .vue file:
if (process.browser) {
require('external_library')
}
If you are using this library within multiple files, we recommend that
you add it into your vendor bundle via nuxt.config.js:
build: {
vendor: ['external_library']
}

"document is not defined" in Nuxt.js

I am trying to use Choices.js within a Vue component. The component compiles successfully, but then an error is triggered:
[vue-router] Failed to resolve async component default:
ReferenceError: document is not defined
In the browser I see:
ReferenceError document is not defined
I think this has something to do with the SSR in Nuxt.js? I only need Choices.js to run on the client, because it's a client only aspect I guess.
nuxt.config.js
build: {
vendor: ['choices.js']
}
AppCountrySelect.vue
<script>
import Choices from 'choices.js'
export default {
name: 'CountrySelect',
created () {
console.log(this.$refs, Choices)
const choices = new Choices(this.$refs.select)
console.log(choices)
}
}
</script>
In classic Vue, this would work fine, so I'm very much still getting to grips with how I can get Nuxt.js to work this way.
Any ideas at all where I'm going wrong?
Thanks.
It's a common error when you start a Nuxt project ;-)
The Choices.js lib is available only for client-side! So Nuxt tried to renderer from server-side, but from Node.js window.document doesn't exist, then you have an error.
nb: window.document is only available from the browser renderer.
Since Nuxt 1.0.0 RC7, you can use <no-ssr> element to allow your component only for client-side.
<template>
<div>
<no-ssr placeholder="loading...">
<your-component>
</no-ssr>
</div>
</template>
take a look at the official example here: https://github.com/nuxt/nuxt.js/blob/dev/examples/no-ssr/pages/index.vue
Update:
Since Nuxt >= 2.9.0, you have to use the <client-only> element instead of <no-ssr>:
<template>
<div>
<client-only placeholder="loading...">
<your-component>
</client-only>
</div>
</template>
To know more, see nuxt docs: https://nuxtjs.org/docs/2.x/features/nuxt-components#the-client-only-component
The accepted answer (while correct) was too short for me to understand it and use it correctly, so I wrote a more detailed version. I was looking for a way to use plotly.js + nuxt.js, but it should be the same as the OP's problem of Choice.js + nuxt.js.
MyComponent.vue
<template>
<div>
<client-only>
<my-chart></my-chart>
</client-only>
</div>
</template>
<script>
export default {
components: {
// this different (webpack) import did the trick together with <no-ssr>:
'my-chart': () => import('#/components/MyChart.vue')
}
}
</script>
MyChart.vue
<template>
<div>
</div>
</template>
<script>
import Plotly from 'plotly.js/dist/plotly'
export default {
mounted () {
// exists only on client:
console.log(Plotly)
},
components: {
Plotly
}
}
</script>
Update: There is <client-only> tag instead of <<no-ssr> in Nuxt v>2.9.0, see #Kaz's comment.
You need to add it as a plugin and then disable SSR for it.
As the document and window are not defined on the server-side.
Your nuxt.config.js should look like below
plugins: [
{ src: '~/plugins/choices.js' } // both sides
{ src: '~/plugins/client-only.js', mode: 'client' }, // only on client side
{ src: '~/plugins/server-only.js', mode: 'server' } // only on server side
],
I found that now the no-ssr is replace by , i am using echart and have the same problem but now itΒ΄s working!
<client-only>
<chart-component></chart-component>
</client-only>
I had this error with lightgallery.js adding mode: 'client'
seems helped
nuxt.config.js
plugins: [
{ src: '~/plugins/lightgallery.js', mode: 'client' }
],
plugins/lightgallery.js
import Vue from 'vue'
import lightGallery from 'lightgallery.js/dist/js/lightgallery.min.js'
import 'lightgallery.js/dist/css/lightgallery.min.css'
Vue.use(lightGallery)
ImageGallery.vue
<template>
<section class="image-gallery-container">
<div class="image-gallery-row">
<div
ref="lightgallery"
class="image-gallery"
>
<a
v-for="image in group.images"
:key="image.mediaItemUrl"
:href="image.mediaItemUrl"
class="image-gallery__link"
>
<img
:src="image.sourceUrl"
:alt="image.altText"
class="image-gallery__image"
>
</a>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'ImageGallery',
props: {
group: {
type: Object,
required: true
}
},
mounted() {
let vm = this;
if (this.group && vm.$refs.lightgallery !== 'undefined') {
window.lightGallery(this.$refs.lightgallery, {
cssEasing: 'cubic-bezier(0.680, -0.550, 0.265, 1.550)'
});
}
}
}
</script>
<script>
import Choices from 'choices.js'
export default {
name: 'CountrySelect',
created () {
if(process.client) {
console.log(this.$refs, Choices)
const choices = new Choices(this.$refs.select)
console.log(choices)
}
}
}
</script>
I guess this should help, nuxt will touch insides of computed after it renders on server and window will be defined
This thread is a bit old, but I will leave my solution here so maybe someone finds it useful.
I had similar issue with vue-star-rating and few other plugins recently.
Below steps can be followed and adjusted depending on the plugin name, import / usage settings:
Go to your plugins folder and create new js file, in this case vue-star-rating.js, then edit it to setup the plugin:
import Vue from 'vue'
import VueStarRating from 'vue-star-rating'
Vue.component('vue-star-rating', VueStarRating); //<--- the name you used to register the plugin will be the same to use when in the component (vue-star-rating)
Go to your nuxt.config.js file and add plugin:
plugins: [{
src: '~/plugins/vue-star-rating', // <--- file name
mode: 'client'
},
//you can simply keep adding plugins like this:
{
src: '~/plugins/vue-slider-component',
mode: 'client'
}]
Now you are ready to use the plugin anywhere in the application. However, to do that you will need to wrap it in the container <client-only>. Example:
<client-only placeholder="loading...">
<vue-star-rating />
</client-only>
Notes:
You do not need to import anything locally to the component, simply using it like above should fix the problem.
Please make sure you are naming the plugin the same way in both places, step 1 and step 3. In this case it would be vue-star-rating.
if you still want to do it, document object can be taken this way:
const d = typeof document === 'undefined' ? null : document
For completeness, it's worth mentioning that instead of the object syntax in Yusuf Adeyemo answer (which I prefer as it separates out the file from how it is used), you can also set plugins to operate in client or server side only by naming the files like so:
export default {
plugins: [
'~/plugins/foo.client.js', // only in client side
'~/plugins/bar.server.js', // only in server side
'~/plugins/baz.js' // both client & server
]
}
src: https://nuxtjs.org/docs/directory-structure/plugins/#client-or-server-side-only
On top of all the answers here, you can also face some other packages that are not compatible with SSR out of the box (like in your case) and that will require some hacks to work properly. Here is my answer in details.
The TLDR is that you'll sometimes need to:
use process.client
use the <client-only> tag (be careful, it will not render but still execute the code inside)
use a dynamic import if needed later on, like const Ace = await import('ace-builds/src-noconflict/ace')
load a component conditionally components: { [process.client && 'VueEditor']: () => import('vue2-editor') }
With all of this, you're pretty much covered for every possible case.
I was trying to access document in created hook so when I moved the logic from created hook to mounted hook, my problem was solved.

VueJs templating. How to load external templates

I'm new to Vue.js, I've used AngularJS for some time and in angular we used to load templates such as,
template: '/sometemplate.html',
controller: 'someCtrl'
How can we do such a thing in Vue, instead of keeping large HTML templates inside JavaScript like this,
new Vue({
el: '#replace',
template: '<p>replaced</p>'
})
This is OK for small templates but for large templates is this practical?
Is there a way to load external template HTML or use HTML template inside a script tag like in Vue?
<script type="x-template" id="template">HTML template goes here</html>
You can use the script tag template by just referring to its id.
{
template: '#some-id'
}
Though, I highly recommend using vueify (if you use browserify) or vue-loader (if you use webpack) so you can have your components stored in nice little .vue files like this.
Also, the author of Vue wrote a nice post about the topic of external template urls:
https://vuejs.org/2015/10/28/why-no-template-url/
You can try this:
for Vue2 : https://github.com/FranckFreiburger/http-vue-loader
for Vue3 : https://github.com/FranckFreiburger/vue3-sfc-loader
Example (Vue2) :
new Vue({
components: {
'my-component': httpVueLoader('my-component.vue')
},
...
Example (Vue3) :
Vue.createApp({
components: {
'my-component': Vue.defineAsyncComponent(() => loadModule('./myComponent.vue', opts))
},
...
David, that is a nice example, but what's the best way to make sure the DOM is compiled?
https://jsfiddle.net/q7xcbuxd/35/
When I simulate an async operation, like in the example above, it works. But as soon as I load an external page "on the fly", Vue complains because the DOM is not ready.
More specifically:
Uncaught TypeError: Cannot set property 'vue' of undefined
Is there a better way to do this than to call $compile when the page has loaded? I've tried with $mount, but that didn't help.
UPDATE:
Never mind, I finally figured out how to do it:
Vue.component('async-component', function (resolve, reject) {
vue.$http.get('async-component.html', function(data, status, request){
var parser = new DOMParser();
var doc = parser.parseFromString(data, "text/html");
resolve({
template: doc
});
});
});
And in the actual template, I removed the
<script id="someTemplate" type="text/x-template"></script>
tags and only included the html.
(This solution requires the http loader from https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.1.10/vue-resource.min.js)
I've tried http-vue-loader and it works fine.
This library is easy to use and has good documentation and examples
Although you can't load templates from filed directly you still can keep html in separate single-file components. You can even skip <script>...</script> part.
Usage (from loader's documentation)
my-component.vue
<template>
<div class="hello">Hello {{who}}</div>
</template>
index.html
<!doctype html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
</head>
<body>
<div id="my-app">
<my-component></my-component>
</div>
<script type="text/javascript">
new Vue({
el: '#my-app',
components: {
'my-component': httpVueLoader('my-component.vue')
}
});
</script>
</body>
</html>
Both files should be placed in one folder at the same level
1. In Vue 2.x I recommend sticking with convention by using .vue files but instead, inverting the order of imports:
// template.vue
<template>
<div class="helloworld">
<h1>Hello world</h1>
</div>
</template>
<script>
import src from './src'
export default src
</script>
and in a separate file
// src.js
export default {
name: 'helloworld',
props: {},
...
}
Then in your component registration
import helloworld from './helloworld/template.vue'
new Vue({
components: {
'helloworld': helloworld
},
...})
This way you get the best of both worlds and you don't have to force yourself to build templates within a string.
2. If you want to lazy load, apparently there is a way to do so in Vue 2.x
new Vue({
components: {
'helloworld': () => import(/* webpackChunkName: "helloworld" */ './helloworld/template.vue')
},
...})
This will load helloworld.js (which will contain all that component's code) on request of that page in the browser.
Of course, all the above assumes you are using ES6 with import capabilities
there are at least 2 ways to achieve what you want, on of them (x-templates) already mentioned by Bill Criswell, but I think it worth to add an example
Define your component like this
Vue.component('my-checkbox', {
// id of x-template
template: '#my-template'
});
Add html file with your x-template (id should match the one you specified in the component)
<script type="text/x-template" id="my-template">...</script>
Another approach (and I like this one better) would be to use inline template
Define your template like this
Vue.component('my-template', {});
Add html file with your component and template inside it
<my-template inline-template>place for your html</my-template>
Just don't forget to add inline-template attribute, otherwise it won't work
You can use this approach with superagent:
var promise = superagent.get("something.html")
.end(function (error, response) {
if (error) {
console.error("load of something.html failed", error));
return;
}
var parser = new DOMParser()
var doc = parser.parseFromString(response.text, "text/html");
document.body.appendChild(doc.scripts[0]);
});
Just put your <script> tag based template inside of something.html on your server.
If you are using jQuery, .load should work.
Just make sure this completes before the DOM in question is compiled by Vue. Or use $mount to manually set things up.
Use browserify to bundle everything like this:
//Home.js
import Vue from 'vue';
var Home = Vue.extend({
template: require('./Home.vue')
});
export default Home;
//Home.vue
<h1>Hello</h1>
// And for your browserify bundle use a transform called stringify
... .transform(stringify(['.html', '.svg', '.vue', '.template', '.tmpl']));

Why is my Component not calling its init method?

I have a UIComponent which has a dependency of a faceless Component.
Main component metadata:
jQuery.sap.declare("MYAPP.Component");
sap.ui.core.UIComponent.extend("MYAPP.Component", {
metadata: {
dependencies: {
libs: [],
components: [
"MYAPP.Component2"
]
}, etc
The app indicates it has reached the faceless component, since if I make some deliberate syntax errors I do get the error messages when loading the webpage. I can also get a console.log("test") to print out from outside the sap.ui.core.Component.extend() code.
jQuery.sap.declare("Component2.Component");
console.log("outside test"); //this prints
sap.ui.core.Component.extend("Component2.Component", {
metadata: {
},
init: function(){
sap.ui.core.Component.prototype.init.apply(this, arguments);
console.log("component2 init test"); //this doesn't print
}
});
Perhaps there's an issue with my declarations of resources?
Some of index.html:
<script id='sap-ui-bootstrap' type='text/javascript'
src='resources/sap-ui-core.js'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-libs='sap.m, sap.me'
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-xx-supportedLanguages="en"
data-sap-ui-resourceroots='{"MYAPP":"./"}'></script>
<script>
sap.ui.localResources("view");
sap.ui.localResources("utils");
sap.ui.localResources("control");
sap.ui.localResources("Component2");
My folder structure:
MYAPP
/Component2 //faceless component folder
Component.js
/view //views and controllers folder
Component.js //main component
index.html
The answer I've found with some help from a user at SCN (here) is that the parent component's metadata will call a function to load the new component without instantiating it. So you must explicitly create the component as well.
I have added a simple sap.ui.component in the init() function of my parent component.
sap.ui.component({ name: "MYAPP.Component2" });
Now the new component is loaded and instantiated before the parent component. Hopefully this post will help others using faceless components, as I found little documentation myself when researching this problem.

Categories