I'm having troubles loading the content of an HTML file in a Vue ponent. Basically i have a Django backend that generates an HTML file using Bokeh and a library called backtesting.py. My frontend is using Nuxt/Vue, so i can't just load the HTML on the page dynamically.
Here is what the HTML file looks like (it was too long to post here):
The content of that file should be loaded in a basic ponent:
<template>
<div>
<h1>Some content here</h1>
</div>
</template>
<script>
export default {
ponents: {
},
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
The problem is that i really don't know how to do that. If i just copy and paste the content in the vue ponent, i'll get a lot of error due to the fact that i'm using a <script>
tag in a ponent. The only thing i managed to do was to load the BokehJS CDN in my index.html
file, but even after that i'll get a Bokeh is undefined
error in the ponent.
What can i do to acplish this? Any kind of advice is appreciated
I'm having troubles loading the content of an HTML file in a Vue ponent. Basically i have a Django backend that generates an HTML file using Bokeh and a library called backtesting.py. My frontend is using Nuxt/Vue, so i can't just load the HTML on the page dynamically.
Here is what the HTML file looks like (it was too long to post here): https://controlc./aad9cb7f
The content of that file should be loaded in a basic ponent:
<template>
<div>
<h1>Some content here</h1>
</div>
</template>
<script>
export default {
ponents: {
},
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
The problem is that i really don't know how to do that. If i just copy and paste the content in the vue ponent, i'll get a lot of error due to the fact that i'm using a <script>
tag in a ponent. The only thing i managed to do was to load the BokehJS CDN in my index.html
file, but even after that i'll get a Bokeh is undefined
error in the ponent.
What can i do to acplish this? Any kind of advice is appreciated
Share Improve this question edited Sep 23, 2021 at 7:27 Jack022 asked Sep 23, 2021 at 7:20 Jack022Jack022 1,2977 gold badges50 silver badges111 bronze badges 3- You can check this video and the links in the description youtube./watch?v=J037aiMGGAw – I_Al-thamary Commented Sep 25, 2021 at 14:04
- I don't know much about nuxt, but how would you be accessing this HTML? Would it be a URL endpoint? Sounds like it would be a Django endpoint, but just want to make sure. Also, what level of reactivity are you looking for from the Vue ponent? – Guru Prasad Commented Sep 26, 2021 at 2:34
- How to access the HTML is what i'm trying to understand; my original attempt was to just copy and paste the content in the vue ponent, but i got any kind of error like 'Bokeh was not found', Vue not being able to load <script> tags.. – Jack022 Commented Sep 26, 2021 at 8:40
3 Answers
Reset to default 4Tao's answer is spot on and is very similar to how I've solved this issue for myself in the past.
However, I'd like to throw in an alternative iframe approach that could work in case reactivity is important. Here's a codesandbox link
The only difference is that this approach loads the code/HTML via XHR and writes it manually into the iframe. Using this approach, you should be able to add some reactivity if necessary.
<script>
export default {
ponents: {},
data() {
return {};
},
async mounted() {
this.initialize();
},
methods: {
async initialize() {
const html = await this.loadHTML();
const doc = this.htmlToDocument(html);
this.updateIframe(doc);
},
async loadHTML() {
const response = await fetch("/plot");
const text = await response.text();
return text;
},
htmlToDocument(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
return doc;
},
updateIframe(doc) {
const iframe = this.$refs.frame;
const iframeDocument = iframe.contentWindow.document;
iframeDocument.open();
iframeDocument.write(doc.documentElement.innerHTML);
iframeDocument.close();
}
},
};
</script>
In the codesandbox, I've thrown in two additional methods to give you an example of how reactivity can work with this approach:
modify() {
if (this.orig) {
// Only for the purpose of this example.
// It's already been modified. Just short-circuit so we don't overwrite it
return;
}
const bokehDoc = this.$refs.frame.contentWindow.Bokeh.documents[0];
// Get access to the data..not sure if there's a better/proper way
const models = [...bokehDoc._all_models.values()];
const modelWithData = models.find((x) => x.data);
const { data } = modelWithData;
const idx = Math.floor(data.Close.length / 2);
// Store old data so we can reset it
this.orig = data.Close[idx];
data.Close[Math.floor(data.Close.length / 2)] = 0;
modelWithData.change.emit();
},
reset() {
if (!this.orig) {
return;
}
const bokehDoc = this.$refs.frame.contentWindow.Bokeh.documents[0];
// Get access to the data..not sure if there's a better/proper way
const models = [...bokehDoc._all_models.values()];
const modelWithData = models.find((x) => x.data);
const { data } = modelWithData;
const idx = Math.floor(data.Close.length / 2);
data.Close[idx] = this.orig;
modelWithData.change.emit();
delete this.orig;
}
Probably the simplest way is to make your HTML available at the URL of your choice, on your server (regardless of Vue).
Then, in your app, use an <iframe>
and point its src
to that html. Here's an example, using codesandbox.io, where I placed what you posted into the index.html
. Below you can see it working with both <iframe>
and <object>
tags:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
'el': '#app'
})
body {
margin: 0;
}
h1, h3 {padding-left: 1rem;}
object, iframe {
border: none;
height: 800px;
width: 100%;
min-height: calc(100vh - 125px);
}
<script src="https://cdnjs.cloudflare./ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>This content is placed in Vue</h1>
<h3>Vue doesn't really care.</h3>
<iframe src="https://1gk6z.csb.app/"></iframe>
<h1><code><object></code> works, too:</h1>
<object type="text/html" data="https://1gk6z.csb.app/"></object>
</div>
Note: if the domain serving the graph and the one displaying it differ, you'll need server-side configuration to allow the embed (most domains have it turned off by default).
Strategy:
- insert and init bokeh in head tag of public/index.html
- read file in a string via ajax/xhr and parse as dom tree
- extract each needed dom element from the parsed tree
- recreate and append each element
No iframe needed. window.Bokeh
is directly accessible.
A skeletal example of reactivity is suggested through the method logBkh
that logs the global Bokeh object when clicking on the graph
<template>
<div id="app">
<div id="page-container" @click="logBkh"></div>
</div>
</template>
<script>
// loaded from filesystem for test purposes
import page from 'raw-loader!./assets/page.txt'
// parse as dom tree
const extDoc = new DOMParser().parseFromString(page, 'text/html');
export default {
methods: {
logBkh(){
console.log(window.Bokeh)
}
},
mounted() {
const pageContainer = document.querySelector('#page-container')
// generate and append root div
const dv = document.createElement('div')
const { attributes } = extDoc.querySelector('.bk-root')
for(const attr in attributes) {
dv.setAttribute(attributes[attr].name, attributes[attr].value)
}
pageContainer.append(dv)
for(const _scrpt of extDoc.body.querySelectorAll('script')) {
// generate and append each script
const scrpt = document.createElement('script')
for(const attr in _scrpt.attributes) {
scrpt.setAttribute(
_scrpt.attributes[attr].name,
_scrpt.attributes[attr].value
)
}
scrpt.innerHTML = _scrpt.innerHTML
pageContainer.append(scrpt)
}
}
}
</script>
result: