querySelector not working in mount() with Vue - javascript

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>

Related

v-model throws ReferenceError only in Production

I have the following View in Vue:
<script setup>
import Overwrite from "../components/Overwrite.vue";
</script>
<template>
<div>
...
<textarea v-model="text" cols="99" rows="20"></textarea>
...
</div>
</template>
<script>
export default {
data() {
return {
text: ""
};
},
components: { Overwrite: Overwrite },
};
</script>
Everything works perfectly fine when I start the application with npm run dev.
However, when I build the app for production and run it, I get the following error as soon as I type anything into the textarea:
index.57b77955.js:3 Uncaught ReferenceError: text is not defined
at HTMLTextAreaElement.t.onUpdate:modelValue.s.<computed>.s.<computed> [as _assign] (index.57b77955.js:3:1772)
at HTMLTextAreaElement.<anonymous> (vendor.31761432.js:1:53163)
I also have other form elements that show the exact same behaviour.
You can use a maximum of 1 × <script> tag and a maximum of 1 × <script setup> per vue component.
Their outputs will be merged and the object resulting from merging their implicit or explicit exports is available in <template>.
But they are not connected. Which means: do not expect any of the two script tags to have visibility over the other one's imports.
The worst part is that, although the first <script setup> does declare Ovewrite when you import it (so it should become usable in <template>), the second one overwrites it when you use components: { Overwrite: Overwrite }, because Overwrite is not defined in the second script. So your components declaration is equivalent to:
components: { Overwrite: undefined }
, which overwrites the value already declared by <script setup>.
This gives you two possible solutions:
Solution A:
<script>
import Overwrite from "../components/Overwrite.vue";
export default {
components: {
Overwrite
},
// you don't need `data` (which is Options API). use `setup` instead
setup: () => ({
text: ref('')
})
}
</script>
Solution B:
<script setup>
import Overwrite from "../components/Overwrite.vue";
const text = ref('')
</script>
Or even:
<script setup>
import Overwrite from "../components/Overwrite.vue";
</script>
<script>
export default {
data: () => ({ text: "" })
};
</script>
Can you try using only the setup script tag? Using it only for imports this way doesn't make sense. If you import a component in setup script tags you don't need to set components maybe the issue is related to that.
Also you could try the full setup way then:
<script setup>
import { ref } from 'vue'
import Overwrite from "../components/Overwrite.vue";
const text = ref('')
</script>
<template>
<div>
...
<textarea v-model="text" cols="99" rows="20"></textarea>
...
</div>
</template>

Convert Vue Component to HTML in advance

I need to create a vuejs component and pass it into a library (mapbox) as pure html. Mapbox has a setHtml method for popups that I'm trying to populate.
https://docs.mapbox.com/mapbox-gl-js/example/popup/
var popup = new mapboxgl.Popup({ closeOnClick: false })
.setLngLat([-96, 37.8])
.setHTML('<h1>Hello World!</h1>')
.addTo(map);
I haven't been able to find any way to pre-render a specific component into html that I could then insert into the mapbox call. Sort of like v-html in reverse.
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">
<Hello ref="hello" />
</div>
</template>
<script>
import Hello from './Hello.vue'
export default {
name: 'App',
components: {
Hello,
},
created() {
// wrong, $el is not exists then
// console.log(this.$refs.hello.$el.outerHTML)
},
mounted() {
console.log(this.$refs.hello.$el.outerHTML)
},
}
</script>

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.

Get template as string in Vue

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>

How to render objects inside a string vue2

In Vue2, I have a string along the lines of the following in my component.
<template>
<div><h1>Hello World</h1>
<div v-html="testString"></div>
</div>
</template>
<script>
export default {
name: "test-component",
props: ['incoming'],
data: function() {
return {
testString: "";
}
},
created() {
this.testString = this.incoming;
}
}
</script>
And then when I do the following (with all the imports done correctly)
<template>
<text-component :incoming="mycontent"></text-component>
</template>
<script>
import 'test-component' from './path/to/test-component.vue'
export default { // etc, just presume this bit is right, it's only psudo code
components: ['test-component'],
data() { return { mycontent: '' }},
created() {
this.mycontent="I just want to <router-link to='/home' v-html='Go Home'></router-link>"
</script>
So now I want the first template with testString to render the <router-link> as if I had put it in myself directly.
I've tried v-html and {{ }} , in my test-component but I think i'm missing something. I'm pretty sure in Vue1 I would use {{{ }}}.
Can anyone help out? Thanks.
From the documentation on v-html:
Note that the contents are inserted as plain HTML - they will not be compiled as Vue templates.
You should be using a slot instead.
Add the slot to your testComponent:
<template>
<div><h1>Hello World</h1>
<slot></slot>
</template>
And then just put the content right in the test-component tag:
<template>
<test-component>
I just want to <router-link to='/home' v-html='Go Home'></router-link>
</test-component>
</template>

Categories