retrieve content from markdown file and convert it to valid HTML code in vuejs - vue.js

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

Related

Vue 3 Vite - dynamic image src

I'm using Vue 3 with Vite. And I have a problem with dynamic img src after Vite build for production. For static img src there's no problem.
<img src="/src/assets/images/my-image.png" alt="Image" class="logo"/>
It works well in both cases: when running in dev mode and after vite build as well. But I have some image names stored in database loaded dynamically (Menu icons). In that case I have to compose the path like this:
<img :src="'/src/assets/images/' + menuItem.iconSource" />
(menuItem.iconSource contains the name of the image like "my-image.png").
In this case it works when running the app in development mode, but not after production build. When Vite builds the app for the production the paths are changed (all assests are put into _assets folder). Static image sources are processed by Vite build and the paths are changed accordingly but it's not the case for the composed image sources. It simply takes /src/assets/images/ as a constant and doesn't change it (I can see it in network monitor when app throws 404 not found for image /src/assets/images/my-image.png).
I tried to find the solution, someone suggests using require() but I'm not sure vite can make use of it.
Update 2022: Vite 3.0.9 + Vue 3.2.38
Solutions for dynamic src binding:
1. With static URL
<script setup>
import imageUrl from '#/assets/images/logo.svg' // => or relative path
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
2. With dynamic URL & relative path
<script setup>
const imageUrl = new URL(`./dir/${name}.png`, import.meta.url).href
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
3.With dynamic URL & absolute path
Due to Rollup Limitations, all imports must start relative to the importing file and should not start with a variable.
You have to replace the alias #/ with /src
<script setup>
const imageUrl = new URL('/src/assets/images/logo.svg', import.meta.url)
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
2022 answer: Vite 2.8.6 + Vue 3.2.31
Here is what worked for me for local and production build:
<script setup>
const imageUrl = new URL('./logo.png', import.meta.url).href
</script>
<template>
<img :src="imageUrl" />
</template>
Note that it doesn't work with SSR
Vite docs: new URL
Following the Vite documentation you can use the solution mentioned and explained here:
vite documentation
const imgUrl = new URL('./img.png', import.meta.url)
document.getElementById('hero-img').src = imgUrl
I'm using it in a computed property setting the paths dynamically like:
var imagePath = computed(() => {
switch (condition.value) {
case 1:
const imgUrl = new URL('../assets/1.jpg',
import.meta.url)
return imgUrl
break;
case 2:
const imgUrl2 = new URL('../assets/2.jpg',
import.meta.url)
return imgUrl2
break;
case 3:
const imgUrl3 = new URL('../assets/3.jpg',
import.meta.url)
return imgUrl3
break;
}
});
Works perfectly for me.
The simplest solution I've found for this is to put your images in the public folder located in your directory's root.
You can, for example, create an images folder inside the public folder, and then bind your images dynamically like this:
<template>
<img src:="`/images/${ dynamicImageName }.jpeg`"/>
</template>
Now your images should load correctly in both development and production.
Please try the following methods
const getSrc = (name) => {
const path = `/static/icon/${name}.svg`;
const modules = import.meta.globEager("/static/icon/*.svg");
return modules[path].default;
};
In the context of vite#2.x, you can use new URL(url, import.meta.url) to construct dynamic paths. This pattern also supports dynamic URLs via template literals.
For example:
<img :src="`/src/assets/images/${menuItem.iconSource}`" />
However you need to make sure your build.target support import.meta.url. According to Vite documentation, import.meta is a es2020 feature but vite#2.x use es2019 as default target. You need to set esbuild target in your vite.config.js:
// vite.config.js
export default defineConfig({
// ...other configs
optimizeDeps: {
esbuildOptions: {
target: 'es2020'
}
},
build: {
target: 'es2020'
}
})
All you need is to just create a function which allows you to generate a url.
from vite documentation static asset handling
const getImgUrl = (imageNameWithExtension)=> new URL(`./assets/${imageNameWithExtension}`, import.meta.url).href;
//use
<img :src="getImgUrl(image)" alt="...">
Use Vite's API import.meta.glob works well, I refer to steps from docs of webpack-to-vite. It lists some conversion items and error repair methods. It can even convert an old project to a vite project with one click. It’s great, I recommend it!
create a Model to save the imported modules, use async methods to dynamically import the modules and update them to the Model
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
const assets = import.meta.glob('../assets/**')
Vue.use(Vuex)
export default new Vuex.Store({
state: {
assets: {}
},
mutations: {
setAssets(state, data) {
state.assets = Object.assign({}, state.assets, data)
}
},
actions: {
async getAssets({ commit }, url) {
const getAsset = assets[url]
if (!getAsset) {
commit('setAssets', { [url]: ''})
} else {
const asset = await getAsset()
commit('setAssets', { [url]: asset.default })
}
}
}
})
use in .vue SFC
// img1.vue
<template>
<img :src="$store.state.assets['../assets/images/' + options.src]" />
</template>
<script>
export default {
name: "img1",
props: {
options: Object
},
watch: {
'options.src': {
handler (val) {
this.$store.dispatch('getAssets', `../assets/images/${val}`)
},
immediate: true,
deep: true
}
}
}
</script>
My enviroment:
vite v2.9.13
vue3 v3.2.37
In vite.config.js, assign #assets to src/assets
'#assets': resolve(__dirname, 'src/assets')
Example codes:
<template>
<div class="hstack gap-3 mx-auto">
<div class="form-check border" v-for="p in options" :key="p">
<div class="vstack gap-1">
<input class="form-check-input" type="radio" name="example" v-model="selected">
<img :src="imgUrl(p)" width="53" height="53" alt="">
</div>
</div>
</div>
</template>
<script>
import s1_0 from "#assets/pic1_sel.png";
import s1_1 from "#assets/pic1_normal.png";
import s2_0 from "#assets/pic2_sel.png";
import s2_1 from "#assets/pic2_normal.png";
import s3_0 from "#assets/pic3_sel.png";
import s3_1 from "#assets/pic3_normal.png";
export default {
props: {
'options': {
type: Object,
default: [1, 2, 3, 4]
}
},
data() {
return {
selected: null
}
},
methods: {
isSelected(val) {
return val === this.selected;
},
imgUrl(val) {
let isSel = this.isSelected(val);
switch(val) {
case 1:
case 2:
return (isSel ? s1_0 : s1_1);
case 3:
case 4:
return (isSel ? s2_0 : s2_1);
default:
return (isSel ? s3_0 : s3_1);
}
}
}
}
</script>
References:
Static Asset Handling of Vue3
Memo:
About require solution.
"Cannot find require variable" error from browser. So the answer with require not working for me.
It seems nodejs >= 14 no longer has require by default. See this thread. I tried the method, but my Vue3 + vite give me errors.
In Nuxt3 I made a composable that is able to be called upon to import dynamic images across my app. I expect you can use this code within a Vue component and get the desired effect.
const pngFiles = import.meta.glob('~/assets/**/*.png', {
//#ts-ignore
eager: true,
import: 'default',
})
export const usePNG = (path: string): string => {
// #ts-expect-error: wrong type info
return pngFiles['/assets/' + path + '.png']
}
sources
If you have a limited number of images to use, you could import all of them like this into your component. You could then switch them based on a prop to the component.

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>

Render and Compile String using 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

User editable Vue template

In my app, I have a template for things like Invoice, Email etc. I'd like the user to be able to edit these templates by dragging and dropping elements. I'm currently using vue-loader along with webpack to pre-compile my vue files into pure JS.
Is it possible to load a vue template from the database on the fly? I've seen this post but this isn't using vue-loader so I'm not sure how to override the template on my component via the code. Something like:
created: function () {
this.$template = '<html><p>Loaded from the DB!</p></html>'
}
would be useful. Is this possible?
Edit: I've tried the following but I get an error Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.:
created: function () {
document.body.innerHTML = '<html><p>I AM FROM THE DB {{total}}</p></html>'
}
This would need to be modified to pass in the templates from your database, but this works in a very simple single file component. Obviously you will want to customize, but this demonstrates the concept.
Dynamic.vue
<script>
export default {
props:["template"],
data(){
return {
message:"hello"
}
},
created(){
this.$options.template = this.template
}
}
</script>
App.vue
<template>
<div>
<dynamic
v-for="template, index of templates"
:template="template" :key="index">
</dynamic>
</div>
</template>
<script>
import Vue from "vue"
import Dynamic from "./Dynamic.vue"
export default {
name: 'app',
data () {
return {
templates: [
"<h1>{{message}}</h1>",
"<h4>{{message}}</h4>"
]
}
},
components:{
Dynamic
}
}
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App)
})

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>