Render and Compile String using vue.js - vue.js

There is a requirement where all html elements are defined in a JSON file and used in the template.
There is a function - "markComplete" which needs to be triggered on change of a checkbox.
Code Template:
<template>
<span v-html="htmlContent"></span>
</template>
<script>
data(){
return{
htmlContent: "<input type='checkbox' v-on:change='markComplete'>"
}
}
</script>
Above code won't work as onChange event won't be mounted, and I get Uncaught ReferenceError: markComplete is not defined
Is there any way to make this work?

You are trying to compile the string as Vue Templates using v-html.
Note that the contents are inserted as plain HTML - they will not be compiled as Vue templates
Read about v-html in Vue Docs.
As solution you can read this article
You don't want to use a library? Checkout the code below:
First create a js file (preferably RenderString.js):
import Vue from "vue"
Vue.component("RenderString", {
props: {
string: {
required: true,
type: String
}
},
render(h) {
const render = {
template: "<div>" + this.string + "</div>",
methods: {
markComplete() {
console.log('the method called')
}
}
}
return h(render)
}
})
Then in your parent component:
<template>
<div><RenderString :string="htmlContent" /></div>
</template>
<script>
import RenderString from "./components/RenderString"
export default {
name: "App",
data: () => ({
htmlContent: "<input type='checkbox' v-on:change='markComplete'>"
})
}
</script>
Note: I didn't run the code above but I created a similar working CodeSandbox Example

Related

Vuejs build/render component inside a method and output into template

I have a string (example, because it's an object with many key/values, want to loop and append to htmloutput) with a component name. Is it possible to render/build the component inside a method and display the html output?
Is that possible and how can i achieve that?
<template>
<div v-html="htmloutput"></div>
</template>
<script>
export default {
component: {
ComponentTest
},
data() {
return {
htmloutput: ''
}
},
methods:{
makeHtml(){
let string = 'component-test';//ComponentTest
//render the ComponentTest directly
this.htmloutput = ===>'HERE TO RENDER/BUILD THE COMPONENTTEST'<==
}
},
created(){
this.makeHtml();
}
</script>
You might be looking for dynamic components:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
Example:
<template>
<component :is="changeableComponent">
</component>
</template>
<script>
import FirstComponent from '#/views/first';
import SecondComponent from '#/views/second';
export default {
components: {
FirstComponent, SecondComponent
},
computed: {
changeableComponent() {
// Return 'first-component' or 'second-component' here which corresponds
// to one of the 2 included components.
return 'first-component';
}
}
}
</script>
Maybe this will help - https://forum.vuejs.org/t/how-can-i-get-rendered-html-code-of-vue-component/19421
StarRating is a sample Vue component. You can get it HTML code by run:
new Vue({
...StarRating,
parent: this,
propsData: { /* pass props here*/ }
}).$mount().$el.outerHTML
in Your method. Remember about import Vue from 'vue'; and of course import component.
What you're trying to do really isn't best practice for Vue.
It's better to use v-if and v-for to conditionally render your component in the <template> section.
Yes you can use the render function for that here is an example :
Vue.component('CompnentTest', {
data() {
return {
text: 'some text inside the header'
}
},
render(createElement) {
return createElement('h1', this.text)
}
})
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<Compnent-test />
</div>
Or :
if you are using Vue-cli :
on your componentTest component :
export default {
render(createElement) {
return createElement('h1', 'sometext')
}
// Same as
// <template>
// <h1>sometext</h1>
// </template>
//
}
and on your root element (App.vue as default) :
export default {
....
component: {
ComponentTest
}
}
<template>
<div>
....
<Component-test />
</div>
</template>
example : codesandbox
you can read more about
Render Functions & JSX

Vue watch page url changes

I'm trying to watch page url. I don't use Vue Router.
My final goal is to set page url as input value:
<template>
<div>
<input v-model="pageUrl">
</div>
</template>
<script>
export default {
data() {
return {
pageUrl: window.location.href,
link: ''
}
},
watch: {
pageUrl: function() {
this.link = window.location.href
}
}
}
</script>
The example above doesn't work somewhy.
I've also tried
watch: {
'window.location.href': function() {
this.link = window.location.href
}
},
Input value is being set only once on component render.
What can be wrong?
well, that is exactly the reason you want to use vue-router!
vue can only detect changes in reactive properties: https://v2.vuejs.org/v2/guide/reactivity.html
if you want to react to changes in the url, you have 2 ways:
https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event or
https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event
i would rather use vue-router or a similar plugin.

Dropzone is not defined when used inside a component

Im currently trying to import vue2-dropzone into my Laravel project so it is using Laravel mix.
I am importing it in my bootstrap.js as below:
import vueDropzone from "vue2-dropzone";
Vue.component('vueDropzone', vueDropzone)
I then want to be able to use in one of my components which is inside a file called "CreatePersonalExpense.vue". This component is accessed using Vue router.
Below is a snippet of how it is being used in my component:
<template>
<div class="form-row py-2">
<div class="col-md-12">
<h4>Upload</h4>
<vue-dropzone v-on:vdropzone-sending="sendingFiles" id="drop1" ref="myVueDropzone" #vdropzone-complete-multiple="afterAllFilesUploaded" :options="dropOptions"></vue-dropzone>
</div>
</div>
</template>
<script>
export default {
data() {
return {
type: "personal",
id: "",
total: "",
files: {
},
dropOptions: {
url: '/api/expenses/files',
autoProcessQueue: false,
uploadMultiple: true,
headers: { "x-csrf-token": document.querySelector('meta[name="csrf-token"]').getAttribute('content') },
params: {}
},
errors: new Errors(),
form: new Form(),
}
},
components: {
vueDropzone
}
}
</script>
However the dropzone is not recognised and I get the error:
Uncaught ReferenceError: vueDropzone is not defined
However if I were to import the dropzone directly into this Vue component by putting import vueDropzone from "vue2-dropzone"; at the beginning of the script tag, dropzone works fine. Why can't I just include it in the bootstrap.js file and have it work for there?
If you already register vueDropzone in bootstrap.js, you don't need to register it again in your component. You should remove this in CreatePersonalExpense.vue
components: {
vueDropzone
}
vueDropzone is an undefined variable. Just remove it and it should work.
Try defining as below =>
Vue.component('vue-dropzone', vueDropzone)

retrieve content from markdown file and convert it to valid HTML code in vuejs

I want to create a documentation page and have some markdown files which represent the main content. I have a navigation sidebar where I can select the specific content.
When clicking on a navigation item I need to read the content from a markdown file. I have a method that returns me the required path but I don't know how to read the file.
Lastly I took marked to render the markdown syntax to HTML code.
I created a small example that shows what is missing
https://codesandbox.io/s/006p3m1p1l
Is there something I can use to read the markdown content?
Use VueResource to retrieve the content from your markdown file.
Import the VueResource, and add it using Vue.use method (main.js):
import Vue from "vue";
import App from "./App";
import VueResource from "vue-resource";
Vue.config.productionTip = false;
Vue.use(VueResource);
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
Then use this.$http.get() method it within your App.vue file to retrieve the markdown file conent.
You can use markdown parsing library, like Showdown.js, wrapped within a vue.js method, directive or filter.
See: https://github.com/showdownjs/showdown and http://showdownjs.com/
There is also vuejs component wrapper for Showdown:
See: https://github.com/meteorlxy/vue-showdown and https://vue-showdown.js.org/
In your case that should look something like this ( using vue-showdown):
<template>
<div id="app"><VueShowdown :markdown="fileContent"></VueShowdown></div>
</template>
<script>
import VueShowdown from "vue-showdown";
export default {
name: "App",
components: VueShowdown,
data: function() {
return {
fileContent: null,
fileToRender:
"https://gist.githubusercontent.com/rt2zz/e0a1d6ab2682d2c47746950b84c0b6ee/raw/83b8b4814c3417111b9b9bef86a552608506603e/markdown-sample.md",
rawContent: null
};
},
created: function() {
// const fileToRender = `./assets/documentation/general/welcome.md`;
//const rawContent = ""; // Read the file content using fileToRender
// this.fileContent = "### marked(rawContent) should get executed";
this.getContent();
},
methods: {
getContent() {
this.fileContent = "rendering ";
// var self;
this.$http.get(this.fileToRender).then(
response => {
// get body data
this.fileContent = response.body;
},
response => {
// error callback
this.fileContent = "An error ocurred";
}
);
}
}
};
</script>
Check in sandbox: https://codesandbox.io/s/poknq9z6q
If your markdown file load is one time thing, then you could import it data, just like you import the components, js files and libraries:
<template>
<div id="app"><VueShowdown :markdown="fileContent"></VueShowdown></div>
</template>
<script>
import VueShowdown from "vue-showdown";
import MarkDownData from './assets/documentation/general/welcome.md';
export default {
name: "App",
components: VueShowdown,
data: function() {
return {
fileContent: null,
rawContent: null
};
},
created: function() {
// const fileToRender = `./assets/documentation/general/welcome.md`;
//const rawContent = ""; // Read the file content using fileToRender
// this.fileContent = "### marked(rawContent) should get executed";
this.getContent();
},
methods: {
getContent() {
this.fileContent = MarkDownData;
}
}
};
</script>
See: https://codesandbox.io/s/xpmy7pzyqz
You could also do it with a combination of html-loader, markdown-loader & v-html.
First you need to install the loaders:
npm i html-loader markdown-loader
Then declare a computed property that returns an array with the names of the markdown files.
In data - add showContent and set the wanted default value - the init markdown file that gets loaded.
Then in the template - loop through the array and set the wanted markdown file on click.
Then finally, you can load your markdown files with a combination of v-html and template literals.
Example below:
<template>
<div class="home">
<h1>
Markdown files
</h1>
<ul>
<li
v-for="item in docs"
:key="item"
#click="shownContent = item"
>
{{ item }}
</li>
</ul>
<div v-html="require(`!!html-loader!markdown-loader!../assets/docs/${shownContent}.md`)"></div>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
shownContent: 'doc1',
}
},
computed: {
docs() {
return [
'doc1',
'doc2',
'doc3',
]
},
},
}
</script>
This way it's important to note, that the name in the array has to be the same as the markdownfile.
I followed the example as mentioned above. I put the code in a component, not App.vue
https://codesandbox.io/s/xpmy7pzyqz?file=/src/App.vue
I get the following error
[Vue warn]: Invalid prop: type check failed for prop "markdown". Expected String with value "[object Object]", got Object

How to use helper functions for imported modules in vuejs .vue template?

I have a helper.js file with contains:
module.exports = {
getSrmColor: (color) => {
return color;
}
}
My .vue file has:
<template>
<div>
{{ recipeHelper.getSrmColor(recipe.color) }}
</div>
</template>
<script>
import recipeHelper from "./helpers.js";
export default {
name: "Recipe",
props: ["recipe"]
}
</script>
I get the following error:
Property or method "recipeHelper" is not defined on the instance but referenced during render.
Make sure to declare reactive data properties in the data option.
Make new helper instance inside your vue component, like below.
<script>
import recipeHelper from "./helpers.js";
export default {
name: "Recipe",
props: [
"recipe"
],
mounted: function() {
this.recipeHelper = recipeHelper;
}
}
</script>
I think you need to create "data value" for your import value. Could you try something like that:
<script>
import recipeHelper from "./helpers.js";
export default {
name: "Recipe",
props: ["recipe"],
data: function() {return {
recipeHelper: recipeHelper
}}
}
</script>