Instantiating Vue components using only string - vue.js

I have a string saved as a variable in javascript that contains both the <template> and <script> tags that could be saved into single file components in vue. Here is an example:
<template>
<div>
{{myVar}}
<button #click = "change(1)">Increment</button>
<button #click = "change(-1)">Decrement</button>
</div>
</template>
<script>
export default{
data(){
var randomVar = Math.round(10*Math.random());
return {
myVar: randomVar
}
},
mounted(){
this.change(12);
},
methods:{
change(num){
this.myVar += num;
}
}
};
</script>
Is there a way to instantiate said string as a vue component without having to save it as a single file component or separating the data, methods and other lifecycle hooks?

The HttpVueLoader can load .vue files directly in the browser - you can look at the source code and try to modify it to accept the string directly instead of fetching it from an URL.
Additionally, you may want to take a look at the Client-Side Vue project - it avoids the building step for SFC so you can use your .vue components directly in the browser.

Related

Use existing CMS output as data for Single File Components (Vue.js)

I have access to the templates that CMS uses to generate pages. This CMS is very primitive. The output is pieces of HTML.
I want to use this data in Single File Components. Most importantly, this data should not be rendered by the browser. I figured that wrapping the CMS output into a noscript tag would work. Then I just parse the string from the noscript to get HTML.
This method is pretty dirty and it does not use the power of Vue.js templates. I'm wondering if there is a better way?
CMS template:
<noscript id="cms-output">
<!-- HTML generated by CMS -->
</noscript>
Main JavaScript file:
import Vue from 'vue'
import App from './app.vue'
const cmsOutput = document.getElementById('cms-output')
const parser = new DOMParser()
Vue.prototype.$cms = parser.parseFromString(cmsOutput.innerHTML, 'text/html')
Vue.config.productionTip = false
new Vue({ render: (h) => h(App) }).$mount('#app')
Single File Component:
<template>
<div v-html="content"></div>
</template>
<script>
export default {
computed: {
content: function() {
const contentElement = this.$cms.querySelector('.content')
// contentElement manipulations here (working with descendants, CSS classes, etc)
return contentElement.outerHTML
}
}
}
</script>
You can use DOM-injected HTML (or even JavaScript strings) as a template in your SFC but you'll need to enable Vue's runtime compiler. Add the following to the project's vue.config.js:
module.exports = {
runtimeCompiler: true
}
Wrap the content of your HTML output in an x-template:
<script type="text/x-template" id="cms-output">
...
</script>
In your SFC, don't use <template></template> tags. Instead, use the template option in your component (this is what the runtime compiler is needed for):
<script>
export default {
template: '#cms-output'
}
</script>
Now you can use the template just as if it were defined in the SFC, with directives, mustache syntax, etc.
EDIT (based on feedback)
There's nothing unique or complex about this if I understand correctly. Use a normal component / template. Since the output isn't ready to be used as a template then there is no choice but to parse it. You could load it from AJAX instead of embedding it as in your question but either way works. Your component could look something like this:
export default {
data() {
return {
data1: '',
data2: '',
dataN: ''
}
},
created() {
const contentElement = this.$cms.querySelector('.content');
const arrayOfData = parseTheContent(contentElement);
this.data1 = arrayOfData[1];
this.data2 = arrayOfData[2];
...
this.dataN = arrayOfData[100];
}
}
And you'd use a standard template:
<template>
<div>
Some stuff {{ data1 }}. Some more stuff {{ data2 }}.<br />
{{ dataN }}
</div>
</template>

pass router params to component object

I have view and I want to load svg based on router params, I have installed vue-loader and it is working if I hard code it.
<template>
<div>
<suit/>
</div>
</template>
<script>
export default {
components:{
suit: ()=>import('../assets/svg/'+Param+'.svg')
}
}
</script>
<style>
</style>
This is what I have. Instead of Param I want to get this.route.params, but when I try this I get undefined which is logical because components wrapper is object. Is there a way to pass a variable here or must I redo the whole thing?
Instead of this.route.params, within a component you should be using this.$route.params. Vue Router Docs.

Vue js loading js file in mounted() hook

I have the following Vue component:
<template>
<div id="wrapper">
<div class="main-container">
<Header />
<router-view/>
<Footer/>
</div>
</div>
</template>
<script>
import './assets/js/popper.min.js';
// other imports
// ....
export default {
name: 'App',
components : {
Header,
Footer
},
mounted(){
// this is syntax error
import './assets/js/otherjsfile.js'
}
}
</script>
As is clear from the code snippet, I want to have the otherjsfile.js loaded in mounted() hook. That script file has certain IIFEs which expects the html of the web page to be fully loaded.
So how do I invoke that js file in a lifecycle hook?
This is the pattern I use. The example is importing a js file which contains an IIFY, which instantiates an object on window.
The only problem with this would occur if you want to use SSR, in which case you need Vue's <ClientOnly> component, see Browser API Access Restrictions
mounted() {
import('../public/myLibrary.js').then(m => {
// use my library here or call a method that uses it
});
},
Note it also works with npm installed libraries, with the same path conventions i.e non-relative path indicates the library is under node_modules.
I'm a little unsure of what your asking. But if you are just trying to include an external js file in your page, you can just use the script tag in your template and not have to put anything in your mounted function, like this:
<template>
<div id="wrapper">
<div class="main-container">
<Header />
<router-view/>
<Footer/>
</div>
<script src="./assets/js/otherjsfile.js"></script>
</div>
</template>
<script>
import './assets/js/popper.min.js';
// other imports
// ....
export default {
name: 'App',
components : {
Header,
Footer
},
}
</script>
Does this solve your issue?

How to use the contents of a Vue js component as are initially loaded and then bind them to a variable

We are looking to get the contents from the html (for example using a slot ) but we later (after mounting) want to bind this to a variable as one would do with a v-html.
In other words we want on initialization to have the contents being loaded as they appear in the slot and later this entire piece of content to be controlled by a binding variable like v-html (because it will be html content).
How to achieve this? without following any ugly solutions such as needing to pass the entire initial html content inside v-html attribute!
You can try like this
<template>
<custom-component>
<div v-if="html_variable" v-html="html_variable"></div>
<template v-else>
<!-- default content here -->
</template>
</custom-component>
</template>
<script>
export default
{
data()
{
return {html_variable: null};
}
}
</script>
How using v-html is ugly? Anyway just use a non-visible element to define your initial html. a <script ref="initHTML" type="text/html"> ... </script> might be a good option.
Define a data entry that should hold your HTML init:
data () {
return {
dHTML: '',
}
},
On created() update your initial html state using the ref
created() {
this.dHTML = this.$refs.initHTML.innerHTML;
},
Now you can have a pretty v-html="dHTML"

Nested single file components - vue.js with electron-forge

I am trying electron for the first time and I am blown away by it. I have hit a wall, though, when trying to use single file vue.js components using electron-forge. My problem is the following:
I create a project using the vue.js template and run it. Works and looks great. I have a single file page with an index file that looks like this:
<div id="test"></div>
</body>
<script>
import Vue from 'vue';
import Test from './test';
const app = new Vue(Test).$mount('#test');
app.text = "Electron Forge with Vue.js!";
</script>
So far, so good. It imports Test, which is a single file component and renders it.
Now, I would like to have other single file components nested in this main component. For example, I would like to have the following, in my app file called test.vue
<template>
<h2>Hello from {{text}}</h2>
</template>
<script>
import About from './About.vue'
export default {
components: {
appAbout: About,
},
data () {
return {
text: 'Electron'
}
}
}
</script>
Again, so far so good. I can run the app with no errors so the component is being imported.
Here comes my problem: if I now try to render the component using <appAbout></appAbout>, as I have done before in web apps with vue.js, I get the following error.
It basically says that I am not using a single root element in my component, which is really strange because my component looks like this:
<template lang="html">
<div>Hello from component</div>
</template>
<script>
export default {
}
</script>
<style lang="css">
</style>
I am stuck. Can someone please help?
So I have tried a few different things with no success, like using or even as the component names.
I also have tried these two ways of starting the vue:
The way you get with electron-forge
const app = new Vue(App).$mount('#app')
and the way I learned
new Vue({el: '#app', render: h => h(App)})
Nothing seems to work...
Define your component like this :
export default {
components: {
'app-about': About
}
}
Then use it in template like this (with kebab-case) :
<app-about></app-about>
About your compiling template error you need to wrap everything in test.vue in a root element :
<template>
<div>
<h2>Hello from {{text}}</h2>
<app-about></app-about>
</div>
</template>