Get template as string in Vue - javascript

Say, I have the following single file component in Vue:
// Article.vue
<template>
<div>
<h1>{{title}}</h1>
<p>{{body}}</p>
</div>
</template>
After importing this component in another file, is it possible to get its template as a string?
import Article from './Article.vue'
const templateString = // Get the template-string of `Article` here.
Now templateString should contain:
<div>
<h1>{{title}}</h1>
<p>{{body}}</p>
</div>

It is not possible.
Under the hood, Vue compiles the templates into Virtual DOM render functions.
So your compiled component will have a render function, but no place to look at the string that was used to generate it.
Vue is not a string-based templating engine
However, if you used a string to specify your template, this.$options.template would contain the string.

set ref attribute for your component, and then you can get rendered HTML content of component by using this.$refs.ComponentRef.$el.outerHTML, and remember don't do this when created.
<template>
<div class="app">
<Article ref="article" />
</div>
</template>
<script>
import Article from './Article.vue'
export default {
name: 'App',
data() {
return {
templateString: ""
}
},
components: {
Article,
},
created() {
// wrong, $el is not exists then
// console.log(this.$refs.article.$el.outerHTML)
},
mounted() {
this.templateString = this.$refs.article.$el.outerHTML
},
}
</script>

Related

querySelector not working in mount() with Vue

I am using markdown-it-vue to render Markdown into HTML on the fly :
<template>
<div ref="content">
<markdown-it-vue :content="content" />
<code>foobar</code>
</div>
</template>
When the props content is modified, the HTML content is injected at the <markdown-it-vue> component.
This HTML content contain some <code> tags and I want to replace them with something else. Unfortunately, I cannot select them with the following:
mounted() {
this.$refs.content.querySelectorAll('code').forEach(code => {
console.log(code)
})
},
Only the <code>foobar</code> is found, the ones in the <markdown-it-vue> are not found. However, they are in the DOM because if I do console.log(this.$refs.content) and I run querySelectorAll('code') on this variable from the Debug Console I get all my elements.
How can I access/replace the generated content from a sub-component?
I have tried to do it differently but with the same issues:
<template>
<div ref="content">
</div>
</template>
<script>
import Vue from 'vue'
import MarkdownItVue from 'markdown-it-vue'
let MyMarkdownItVue = Vue.extend(MarkdownItVue)
export default {
props: {
content: String,
},
mounted() {
let md = new MyMarkdownItVue({propsData: {content: this.content}});
md.$mount();
// Displays the full output with all my `<code>` tags
console.log(md.$el);
// But nothing is found there. I get an empty NodeList []
console.log(md.$el.querySelectorAll('code'))
}
};
</script>

How to import dynamic v-bind:is component with module system in Vue

Problem: I am trying to programmatically register a component to be used in a slot in my Vue/Nuxt site. The component name is included in the data of the parent index.vue file, in this instance the component is named Projects. I am including it in a v-for template as the various objects in the 'modules' data array are iterated over. I had assumed this would be possible/easy from the dynamic component documentation and example however I have not managed to get it working in my case.
What I expect to happen: I expected the component to be registered and 'slotted' into the Module component correctly.
What actually happens: While I can see on the rendered view that the component is 'there', it is not in the correct place (i.e. slotted into the Module component). I also get a vue/no-unused-components error saying: The "Projects" component has been registered but not used.
I have read the documentation about component registration in modular systems but these seem to be for more complex cases than what I am trying to achieve. Any advice would be really helpful as I am totally stuck!
index.vue
<template>
<div>
<template v-for="module in modules">
<Module
:title="module.title"
:to="module.link"
/>
<component v-bind:is="module.slot" />
</Module>
</template>
</div>
</template>
<script>
import Module from '~/components/module/Module.vue'
import Projects from '~/components/module/slots/Projects.vue'
export default {
components: {
Module,
Projects
},
data () {
return {
modules: [
{
title: 'Work',
slot: 'Projects'
},
{
...
}
]
}
}
}
</script>
Edit: As a side note, I get the same error when registering the component with import like so:
components: {
Module,
'Projects': () => import('#/components/module/slots/Projects')
}
Module.vue
<template>
<div>
<h2>
{{ title }}
</h2>
<slot />
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
}
}
</script>
Projects.vue
<template>
<div>
<h3>Projects</h3>
</div>
</template>
<script>
export default {
name: 'Projects'
}
</script>
You use the self closure tag in your Module component.
This prevents your Projects component to be rendered within the slot.
Just replace:
<Module
:title="module.title"
:to="module.link"
/>
with:
<Module
:title="module.title"
:to="module.link"
>
and it should work.

Register Vue.js component dynamically from a string

I have a CRUD that enables me to write Vue.js component's code in the textarea like:
<template>
<div><p class='name-wrapper'>{{ model.name }}</p></div>
</template>
<script>
module.exports = {
name: 'NameWrapper',
props: ['model']
}
</script>
<style lang='sass'>
.name-wrapper
color: red
</style>
Then in other component, I fetch this data and want to register it as a dynamic/async, custom component like:
<template>
<component :is='dynamicName' :model='{name: "Alex"}'></component>
</template>
<script>
import httpVueLoader from 'http-vue-loader'
import Vue from 'vue'
export default {
name: 'DynamicComponent',
props: ['dynamicName', 'componentDefinitionFromTextareaAsString'],
beforeCreate: {
// I know that as a second parameter it should be an url to the file, but I can't provide it, but I would like to pass the contents of the file instead there:
httpVueLoader.register(Vue, this.$options.propsData.componentDefinitionFromTextareaAsString)
// I was trying also:
Vue.component(this.$options.propsData.dynamicName, this.$options.propsData.componentDefinitionFromTextareaAsString)
}
}
</script>
As far as I know, httpVueLoader needs the url to the .vue file instead - is there a way to pass there the code itself of the component?
I am aware that passing and evaluating <script></script> tag contents can cause security issues, but I really need to do it that way.
I've read also about Vue.js compile function, but that works only for templates, not the code of the component (so the script tags again).
Is it even possible to achieve such functionality in Vue.js?
It should be possible to use a data: URI with http-vue-loader, like this:
const vueText = `
<template>
<div class="hello">Hello {{who}}</div>
</template>
<script>
module.exports = {
data: function() {
return {
who: 'world'
}
}
}
<\/script>
<style>
.hello {
background-color: #ffe;
}
</style>
`
const MyComponent = httpVueLoader('data:text/plain,' + encodeURIComponent(vueText))
new Vue({
el: '#app',
components: {
MyComponent
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/http-vue-loader#1.4.1/src/httpVueLoader.js"></script>
<div id="app">
<my-component></my-component>
</div>
If that doesn't work for some reason (maybe because one of your target browsers doesn't support it) then you could get it working by patching httpRequest. See https://www.npmjs.com/package/http-vue-loader#httpvueloaderhttprequest-url-. The documentation focuses on patching httpRequest to use axios but you could patch it to just resolve the promise to the relevant text.

print Component B inside component A - vue.js

Using Vue.js,
How to create componentA that gets componentB as a prop, and print it inside of it?
example:
index.vue
<template>
<div>
<componentA :componentPlaceHolder="componentB"></componentA>
</div>
</template>
<script>
import componentA from './compnentA.vue';
import componentB from './componentB.vue'
export default {
name: 'index',
components: {componentA,componentB }
}
</script>
componentA.vue
<template>
<div>
{{componentPlaceHolder}}
</div>
</template>
<script>
export default {
name: 'componentA',
props: {
'componentPlaceHolder': {}
}
}
</script>
There are some issues to your implementation:
You have gotten the scope wrong: componentPlaceHolder lives in the parent scope, not in that of component A. Read: Compilation Scope.
Use :is (i.e. v-bind: is) for dynamic component binding. The data bound should reference the key of the component.
Since you are nested additional components in another component in the same context, that means you have to interweave the content. This is done by using slots, declared in <component-a>.
Avoid using case-sensitive DOM elements, use kebab case instead, i.e. <component-a> instead of <componentA>, since HTML elements are case-insensitive (<componentA> and <componenta> will be treated the same).
Here is the updated code:
<template>
<div>
<component-a>
<customComponent :is="componentPlaceHolder"></customComponent>
</component-a>
</div>
</template>
<script>
import componentA from './componentA.vue';
import componentB from './componentB.vue'
export default {
name: 'index',
components: {
'component-a': componentA,
'component-b': componentB
},
data: {
componentPlaceHolder: 'component-b'
}
}
</script>
And then in your componentA.vue:
<template>
<div>
<!-- Slot will interweave whatever that is found in <componentA> -->
<slot></slot>
</div>
</template>
<script>
export default {
name: 'componentA'
}
</script>
Proof-of-concept example
If in doubt, here is a live proof-of-concept example:
var componentA = {
template: '#component-a'
};
var componentB = {
template: '#component-b'
};
new Vue({
el: '#app',
components: {
'component-a': componentA,
'component-b': componentB
},
data: {
componentPlaceHolder: 'component-b'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="app">
<component-a>
<!-- DOM elements in here will be interweaved into <slot> -->
<customComponent :is="componentPlaceHolder"></customComponent>
</component-a>
</div>
<template id="component-a">
<div>
<p>I am component A</p>
<slot></slot>
</div>
</template>
<template id="component-b">
<p>I am component B</p>
</template>
Footnote:
The VueJS readme is exceptionally composed, and I suggest here are some things that you can read up on that is very relevant to your use case:
Compilation Scope
Dynamic Components
Content Distribution with Slots

vue.js passing data from parent single file component to child

Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}

Categories