Generate Vue Component with vanilla JS - vue.js

I am currently using the bootstrap table component to display a list (bootstrap-table.com)
This component (or jQuery plugin wrapped within a vue component) has a formatter for each column.
example with source code: https://examples.bootstrap-table.com/#welcomes/vue-component.html#view-source
I need to build a more complex set of links which involve some js processing that could easily be done with vue.
Instead of the formatter returning the <a> tag I would like to return the contents of a <component>. I have tried this but haven't been able to render a component.
I'm almost sure this is impossible because the child component is not previously binded which means the component won't be "reactive".
As an alternative I can build a js function that generates all the required links.
Another phrasing for this question would be: Is there any possibility to generate a vue component from vanilla js?

The above was not possible the way was tried because the components cannot be simply rendered like html tags as they can have methods and computed data therefore they must be compiled.
It looks like the solution for the above situation is using the Vue.compile() method:
const template = `
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>`;
const compiledTemplate = Vue.compile(template);
new Vue({
el: '#app',
data() {
return {
items: ['Item1', 'Item2']
}
},
render(createElement) {
return compiledTemplate.render.call(this, createElement);
}
});
Demo:
https://codepen.io/couellet/pen/ZZNXzy
Demo reference:
https://snipcart.com/blog/vue-render-functions
Vue docs:
https://v2.vuejs.org/v2/api/#Vue-compile

Related

Vue3 Reactivity in script setup for translation

I am adding some DOM elements in the script setup side but I want the messages to change when I change the language. I am using vue-i18n plugin. It's easy to do it in the template section because I can basically use the useI18n().t method but how can I do this in the script setup section. Using the useI18n().t method doesn't ensure reactivity.
Example Code:
$(".time")[0].innerHTML = `
<div>0<span>${useI18n().t("details.hour")}</span></div>
<div>0<span>${useI18n().t("details.minute")}</span></div>
<div>0<span>${useI18n().t("details.second")}</span></div>
`
Manipulating DOM directly inside the script leads to inconsistence in your app, you should drive your component by different reactive data to achieve your goal.
In your current situation try to define a computed property based on the translation then render it inside the template based on its different properties :
<script setup>
const {t} =useI18n()
const time = computed(()=>{
return {
hour:t(""details.hour"),
minute:t(""details.minute"),
second:t(""details.second"),
}
})
</script>
<template>
<div class="time">
<div>0<span>{{time.hour}}</span></div>
<div>0<span>{{time.minute}}</span></div>
<div>0<span>{{time.second}}</span></div>
</div>
</template>

Vue replaces "open" attribute to value "open" in any tag

I'm using vue.js (v2.6.12) components in laravel blade templates.
For the project, I'm also using MathML in which I need to use the open attribute of <mfenced> tag to be set to some custom values. Here is the example of the math expressing in mathml.
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>f</mi>
<mfenced close="]" open="[">
<mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
</mfenced>
</math>
But as soon as the page renders, the open attribute is converted into this open="open". I'm 100% sure there is no other library or script is loaded that updates like so, just plain vue. This actually breaks the math expression. So it looks like this:
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>f</mi>
<mfenced close="]" open="open">
<mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
</mfenced>
</math>
Later I realized that not only in math expression, litaratily any tag, be it <div open="anything">...</div>, <span open="anything">...</span>, <custom-element open="something">...</custom-element> having open attribute behaves the same. even if I use v-pre attribute to exclude it from vue js templete compiler.
And this do not happen, as soon I disable the vue app initialization.
The question here are:
Why vue is changing the open attribute like so?
How can I stop this behaviour, to the entire page within the vue application area or at least where I choose (something like using v-pre), is there ary config or any other way around?
Why
In HTML spec there are some attributes called boolean attributes. Spec dictates what can be a value of such attribute:
If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute's canonical name, with no leading or trailing whitespace.
The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.
open is one of the boolean attributes - it is defined for the <details> element
Problem with Vue 2 is, that it treats most of the boolean attributes as global - without considering the element it is placed on. Result is that open attribute is always rendered with value "open" or removed if the value is falsy (when v-binding). This is fixed in Vue 3 as shown in 2nd example...
How
The use of v-pre is the way to go but unfortunately for you there is a bug.
See this issue. The bug was already fixed with this commit(Sep 21, 2020) but it was not released yet...
example - the "With v-pre" should work in Vue version > 2.6.12
const vm = new Vue({
el: '#app',
data() {
return {
message: 'Hi!',
html: `<div open="[" close="]">Hi from html</div>`
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<div open="[" close="]">{{ message }}</div>
<div v-html="html"></div>
<div v-pre>
<p open="[" close="]">With v-pre</p>
</div>
</div>
example - it works in Vue 3 - open is treated as boolean attribute only if placed on <details>
const app = Vue.createApp({
data() {
return {
message: 'This works in Vue 3!',
}
},
})
app.mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.11/vue.global.js" integrity="sha512-1gHWIGJfX0pBsPJHfyoAV4NiZ0wjjE1regXVSwglTejjna0/x/XG8tg+i3ZAsDtuci24LLxW8azhp1+VYE5daw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="app">
<div open="[" close="]">{{ message }}</div>
<details open="[">
<summary>Details</summary>
open attribute on details element is treated as boolean (renders empty value)
</details>
</div>
One workaround is to create a directive (named "attr") that sets the attribute:
Vue.directive('attr', (el, binding) => el.setAttribute(binding.arg, binding.value || ''))
Then use it in your template like v-bind but with v-attr:
<mfenced v-attr:open="'['">
Vue.directive('attr', (el, binding) => el.setAttribute(binding.arg, binding.value || ''))
new Vue({ el: '#app' })
<script src="https://unpkg.com/vue#2.6.12"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<div id="app">
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>f</mi>
<mfenced close="]" v-attr:open="'['">
<mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
</mfenced>
</math>
</div>
I've found a simple hack to solve this problem.
Why hack?
Because it is eventually going to be fixed in the comming release as pointed by #Michal, so just a quick & dirty hack is enough for now to go for it.
What I did is I placed the math content in the content and also added it to the data attribute and replacing the original content after vue has done its bad work (sorry just using blade syntax here, but it will make sense). I keep it in both places just for SEO purposes.
The template where I need math expression to be displayed.
...
<div class="proxy-content" data-proxy-content="{{ $article->content }}">
{!! $article->content !!}
</div>
...
I was using it along with jQuery, but you can easily substitute with vue.js' $el. This is what it looks in my app.js file.
...
const app = new Vue({
el: '#app',
methods: {
proxyContent() {
// Set Proxy Content.
jQuery('.proxy-content').each((i, el) => {
const $el = jQuery(el);
$el.html( jQuery('<textarea />').html( $el.data('proxy-content')).text() );
});
}
loadMathJax() {
// Load & Initialize MathJax Library.
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://cdn.jsdelivr.net/npm/mathjax#3/es5/tex-mml-chtml.js";
document.getElementsByTagName("head")[0].appendChild(script);
}
}
mounted(){
// Enable proxy content after mount, so we are sure no more rendering issue for templates.
this.proxyContent();
// Load MathJax library with a little delay to make sure everything is ready before loading the library.
setTimeout(() => this.loadMathJax(), 10);
}
});
...
One might argue, that I'm mixing up things outside of the scope of the vue application. For me that is not an issue, as the whole page is using vue.js and also the single thing don't make any harm even if there is another scope that is using mathml (though it depends on actual implementation).
In that case, if you want to scope it well, just use $el of vue.

Can I get Vue.js component as an instance?

I try to get the template of Vue.js component since I need it for another function.
First,
I have a function that needs a Vue.js component template because I want the data in the template to dynamically change and not just some static HTML.
Secondly,
I have a problem to get a template from Vue.js component. Maybe they are not allowed to do that but I am not sure. I am new to Vue.js.
But from my understanding of JS, maybe this can happen.
I am tried to do something like this:
let vmComponent = Vue.component('VueComponent', {
data() {
return {
title: 'I am a Vue.js component manually.'
}
},
template: `
<div>
<h2>Template Exchange</h2>
<h3>{{ title }}</h3>
</div>`,
});
console.log(vmComponent.template);
I hope to get the HTML string when I run vmComponent.template
If this way of doing things is not possible. Is there any other ways I can get Vue.js component template as HTML String.
For example, maybe in a new.Vue({})
It's possible to access component template with vmComponent.options.template.
A template can be declared as a string for reuse:
export const template = `...`;
const vmComponent = Vue.component('VueComponent', {
template,
...
});
This may not work with pre-compiled templates.

Does Vue have bilateral bind?

<p contenteditable="true" v-bind="message"></p>
Object #message does not change when I edit the HTML element. Is there any simple way to do this using Vue.js?
Unfortunately, contenteditable doesn't work with Vue bindings such as v-model, it's recommended that you use a library such as medium.js to build your own component.
However, if you're just trying to do something simple and keep the data in sync you can probably do that yourself:
View Model
new Vue({
el: '#app',
methods: {
updateMessage() {
this.message = this.$refs.message.innerText;
}
},
watch: {
message() {
this.$refs.message.innerText = this.message;
}
},
data: {
message: 'Hello'
}
})
HTML
<p ref="message" contenteditable="true" #keyup="updateMessage" class="editable">{{message}}</p>
 
As you can see you have to deal with updating the DOM yourself when using contenteditable. What I'm actually doing here is using a ref to target the contenteditable, so I can get the inner text via this.$refs.message.innerText. I've then added a #keyup event which calls the updateMessage method to update the message data property. I've then added a watcher which reverses this process, so when message is updated it updates the contenteditable.
Here's the JSFiddle: https://jsfiddle.net/3ngc9486/
Yes it has a two-way binding directive v-model, but it works only with input elements. So, instead of using a p element and handling that with complex JS, use a textarea with v-model and it will work out of the box.
<textarea v-model="message"></textarea>
here is an example.

vuejs 2 v-for :key not working, html being replaced?

I'm rendering some HTML in a v-for
But everytime I change any of the data, all my html gets replaced (input fields lose their values)
I tried giving the :key all kinds of different values
I didn't have this problem in vue v1, only in v2
http://jsbin.com/jomuzexihu/1/edit?html,js,output
I had a little play with this and it appears that Vue does not re-render the entire list when using <input /> or if you use a component but it does with v-html. Heres the fiddle for the comparison:
https://jsfiddle.net/cxataxcf/
The key actually isn't needed here because the list isn't being re-ordered, so your issue isn't to do with :key but rather with v-html. Heres what the docs say about v-html:
The contents are inserted as plain HTML - data bindings are ignored. Note that you cannot use v-html to compose template partials, because Vue is not a string-based templating engine. Instead, components are preferred as the fundamental unit for UI reuse and composition.
So I guess this is where the problem lies.
It might be worth raising an issue on Vue's github page to see whether this is the expected behavior for v-html, but Vue 2.0 is much more heavily focused on components than vue 1.x and doesn't appear to recommend using v-html, so it may just be that you need to re-factor your code to use components instead.
Edit
The solution to this problem is to simply wrap the code in a component and pass the HTML as a prop:
Vue.component('unknown-html', {
props: {
html: ""
},
template: '<div><div v-html="html"></div>'
})
The markup:
<unknown-html :html="thing.html"></unknown-html>
And the View model:
var app = new Vue({
el: '#app',
data: {
numInputs: 1,
stuff: [{
'html':'<input />'
}, {
'html':'<button>Foo</button>'
}]
}
})
Here's the JSFiddle: https://jsfiddle.net/wrox5acb/
You are trying to inject raw html directly into the DOM. Probably it was possible in earlier versions of Vue.js, but it is definitely not the recommended way.
You can instead have an array of objects and bind it to html as shown in this jsFiddle: https://jsfiddle.net/43xz6xqz/
Vue.js version: 2.0.3
In the example above, vue.js is responsible for creating the input elements and also for binding these input elements to the object values using v-model.
To extract these values, you may use a computed property as shown in the sample code.
I guess, for performance optimization, when the key is not change, Vue will not rerender the dom, but will update the data import through directive.So when your input element is import through an directive (v-html), it will be rerendered everytime when stuff changes.
Due to the vue is not a string template engines, but template based on dom, so in the case of #craig_h 's example , to use the incomming html in a string template within a component:
Vue.component('unknown-html', {
props: {
html: ""
},
template: '<div><div v-html="html"></div>'
})
view:
<unknown-html :html="thing.html"></unknown-html>
So when the stuff changes, it will not to rerender the template declare in string, for vue is not a string template engine.