Capturing html wrapped by the component to set a data value in vue.js - vue.js

I am working on a tiptap based component. I want to take the html that is within the component tags
<vue-wysiwyg>
<p>Hey, this is some test text.</p>
<p>I should end up in editor.content</p>
</vue-wysiwyg>
And have the editor.content data for my component be given it.
export default {
data() {
return {
editor: new Editor({
content: '',
}),
}
},
}
I thought about slots but that just passes through html, it doesn't capture it.

I think the slots are the way to go, but since they contain vdom objects we have to use vue to convert it into html. The following should work:
computed: {
html(){
return new Vue({
render: h => h('div', {}, this.$slots.default)
})
.$mount().$el.innerHTML
}
}
We are creating simple component instance that has only a render function and than we $mount it to generate the DOM and than we extract the innerHTML from the root element.
NOTE: You would have to import Vue in this file

You can give the component a ref and access its inner HTML and assign it editor.content
<vue-wysiwyg ref="wysiwyg">
<p>Hey, this is some test text.</p>
<p>I should end up in editor.content</p>
</vue-wysiwyg>
And then in the mounted life cycle hook
export default {
data() {
return {
editor: new Editor({
content: '',
}),
}
},
mounted() {
this.editor.content = this.$refs.wysiwyg.$el.innerHTML;
}
}
Like this sandbox
Hope this helps

Related

Vue Dynamic Form Values AND Keys, with pre existing value for render, why not rendering?

I am trying to create a dynamic 'quick input' form with Vue.
A simple text input that has a dynamic data key so that I can change what I'm submitting to axios. I couldn't figure out how to get a dynamic key name coming from a prop eg
data() {
return {
DYNAMIC-NAME-FROM-PROP: value
}
}
So I've got a values: {} bit of data that gets filled by the props instead.
The code below achieves everything EXCEPT pre-rendering the existing value.
See the comments next to v-model in the tempate:
<template>
<div class="relative">
<input
type="text"
v-model="values[fieldName]" // This is not rendering on load
// v-model="values[this.$props.field]" -> 'this' is null error #input
#keydown.enter.prevent="submit"
/>
</div>
</template>
<script>
export default {
props: ["userId", "field", "preFill"],
data() {
return {
values: {},
fieldName: this.$props.field,
};
},
mounted() {
this.values[this.$props.field] = this.$props.preFill;
},
methods: {
submit() {
axios.post(`/admin/${this.userId}/update`, this.values).then(response => {
// success
});
}
}
};
</script>
Am I going about this completely wrong?
Or am I 'nearly there' and just need to fix the v-model render issue?
To set a dynamic key name on an object in javascript, it turns out you can use square brackets, as in:
{
[keyName] : value
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names
So my code is fixed by simply passing the prop as the key in the axios call:
submit() {
axios
.post(`/admin/${this.userId}/update`, {
[this.$props.field]: this.value
})
.then(response => {
// we out here
});
}

Nuxt render function for a string of HTML that contains Vue components

I'm trying to solve this for Nuxt
Codesandbox of a WIP not working: https://codesandbox.io/s/zw26v3940m
OK, so I have WordPress as a CMS, and it's outputting a bunch of HTML. A sample of the HTML looks like this:
'<h2>A heading tag</h2>
<site-banner image="{}" id="123">Slot text here</site-banner>
<p>some text</p>'
Notice that it contains a Vue component <site-banner> that has some props on it (the image prop is a JSON object I left out for brevity). That component is registered globally.
I have a component that we wrote, called <wp-content> that works great in Vue, but doesn't work in Nuxt. Note the two render functions, one is for Vue the other is for Nuxt (obviously this is for examples sake, I wouldn't use both).
export default {
props: {
html: {
type: String,
default: ""
}
},
render(h, context) {
// Worked great in Vue
return h({ template: this.html })
}
render(createElement, context) {
// Kind of works in Nuxt, but doesn't render Vue components at all
return createElement("div", { domProps: { innerHTML: this.html } })
}
}
So the last render function works in Nuxt except it won't actually render the Vue components in this.html, it just puts them on the page as HTML.
So how do I do this in Nuxt? I want to take a string of HTML from the server, and render it on the page, and turn any registered Vue components into proper full-blown Vue components. Basically a little "VueifyThis(html)" factory.
This was what worked and was the cleanest, thanks to Jonas Galvez from the Nuxt team via oTechie.
export default {
props: {
html: {
type: String,
default: ""
}
},
render(h) {
return h({
template: `<div>${this.html}</div>`
});
}
};
Then in your nuxt.config.js file:
build: {
extend(config, ctx) {
// Include the compiler version of Vue so that <component-name> works
config.resolve.alias["vue$"] = "vue/dist/vue.esm.js"
}
}
And if you use the v-html directive to render the html?
like:
<div v-html="html"></div>
I think it will do the job.
Here's a solution on codesandbox: https://codesandbox.io/s/wpcontent-j43sp
The main point is to wrap the dynamic component in a <div> (so an HTML tag) in the dynamicComponent() template, as it can only have one root element, and as it comes from Wordpress the source string itself can have any number of top level elements.
And the WpContent component had to be imported.
This is how I did it with Nuxt 3 :
<script setup lang="ts">
import { h } from 'vue';
const props = defineProps<{
class: string;
HTML: string
}>();
const VNode = () => h('div', { class: props.class, innerHTML: props.HTML })
</script>
<template>
<VNode />
</template>
There was not need to update nuxt.config.ts.
Hopefully it will help some of you.
I made some changes to your codesandbox. seems work now https://codesandbox.io/s/q9wl8ry6q9
Things I changed that didn't work:
template can only has one single root element in current version of Vue
v-bind only accept variables but you pass in a string.

Accessing data inside a render function of a functional vuejs component

I'm trying to use a functional component to render multiple elements without having to have a single root element but I can't seem to have access to the component data, see this simplified example:
Trying to access context.data logs an empty object {}:
<div id="app">
<hello />
</div>
Vue.component('hello', {
functional: true,
render: function(h, context) {
console.log(context.data) // This logs {}
return new Array(5).fill(1).map((_, index) => h('p', {}, 'Hello world'))
},
data () {
return {
foo: 'bar'
}
}
})
new Vue({
el: '#app',
data: function() {
return {
foo: '<p>foo</p>',
bar: 'bar'
}
}
})
A working jsfiddle
Thanks in advance
A functional component has no data which is why it does not receive data in the context. You can find additional information in this section of the manual
If you want to render a component without having to use a single root element you might want to give vue-fragment a try.

Vue.js - set slot content programmatically

is there any way how can I set/override slot's content from inside the component? Like
Template:
<div>
<slot></slot>
</div>
JS:
export default {
...
mounted() {
this.$slot.render("<button>OK</button>");
}
...
}
I know I can use v-html on my element to dynamically push content into component template, but I mean not just pure HTML I mean HTML with Vue directives. Like:
JS:
export default {
...
mounted() {
this.$slot.default.render('<button #click="submit">OK</button>');
},
methods: {
submit() {
// Here I want to get :)
}
}
...
}
Basically I want Vue to render (like parse and render, not like innerHTML) certain string in scope of my component and put in on certain spot in my component. The reason is I get the new content from server via AJAX.
I'm sorry but I can't still get my head around after 2 days of googling.
Thanks a lot!
According to this you can instantiate a component programmatically and insert slot content as well.
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass({
propsData: { type: 'primary' }
})
instance.$slots.default = [ 'Click me!' ]
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)
I think this is your best chance: building your component on the fly.
Here in the example I use a simple placeholder (with v-cloak).
You may obtain a better result using vue-router to add your newly created component during the execution, with router.addRoutes so your app doesn't have to wait to instantiate the whole vue.
function componentFactory(slot) {
return new Promise((resolve, reject) => {
window.setTimeout(() => { // Asynchronous fetching
resolve({ // Return the actual component
template: `<div>
${slot}
</div>`,
methods: {
submit() {
console.log('hello');
}
}
});
}, 1000);
});
}
componentFactory('<button #click="submit">OK</button>') // Build the component
.then(component => {
new Vue({ // Instantiate vue
el: '#app',
components: {
builtComponent: component
}
});
});
[v-cloak], .placeholder {
display: none;
}
[v-cloak] + .placeholder {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id='app' v-cloak>
<built-component></built-component>
</div>
<div class="placeholder">
This is a placeholder, my vue app is loading...
</div>

Use computed property in data in Vuejs

How can I use a computed property in the data or emit it via bus?
I have the following vue instance, but myComputed is always undefined but computedData is working correctly.
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
Unfortunately, it is impossible to use computed property in data because of component creation timing: data evaluates Before computed properties.
To make things as simple as possible, just do the work in watcher, unless you want to emit the changes to different components or there're a lot of variables you want to notify, then you may have to use Vuex or the event bus:
var vm = new Vue({
data(){
return{
myComputed: '',
computedData: 'Hello World'
}
},
created() {
this.myComputed = this.computedData;
},
watch: {
computedData() {
this.myComputed = this.computedData;
}
}
});
Computed is already accessible in the template using {{ }}.
But you can use the
watch:{
//your function here
}
instead of computed
If you are using computed/reactive objects then it should be inside the computed and not inside the data.
Simply change your code to use computed instead of data
var vm = new Vue({
data(){
return{}
},
computed: {
computedData(){
return 'Hello World'
},
myComputed(){
return this.computedData
}
}
})
you are trying to use data as computed and this shall not be.
data doesn't act like computed object.
and it's not because of component creation timing. What if we changed the component creation timing ? this will not solve anything as data will take only the first computed value(only one) and will not update after.
you can work just with the computed function
var vm = new Vue({
data(){
return{
//is not necessary
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
and in your template
<template>
<div>{{ computedData }}</div>
</template>
You are over-coding it. Computed props are accessible in the same manner as data props in your template.
var vm = new Vue({
computed: {
myComputed(){
return 'Hello World'
}
}
})
In the template you have access to this just like you do to data:
<template>
<div>{{ myComputed }}</div>
</template>
https://v2.vuejs.org/v2/guide/computed.html
Try to convert the computed in a method
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
methods: {
computedData(){
return 'Hello World'
}
}
})
This is simple and it works (NOT reactive), but has a cost:
https://medium.com/notonlycss/the-difference-between-computed-and-methods-in-vue-js-9cb05c59ed98
computed is not available at the time data gets initialized.
If it should be a one-time thing (and NOT reactive), you could achieve this by setting the data at the moment where the computed property is available by using the created() hook:
export default {
data: () => ({
myDataBackend: '',
}),
computed: {
computedData () {
return 'Hello World'
}
},
created() {
this.$set(this, 'myDataBackend', this.computedData)
}
}
Futher reading: Vue Documentation on Lifecycle Hooks
In case you are trying to work with v-model:
You could also use :value and some event like #change or #keyup in the element instead.
:value is the value which the input-element initially works with
After writing some letter in the input field, the #keyup event changes the data.
Typically, events carry the updated form value in target.value
The changeMyData method sets the value
the computed property listens to the data change and the :value of the input field gets updated.
Note: I used data as a data store. But you could also use for example vuex instead.
<template>
<div>
<input
type="text"
:value="computedData"
#keyup="changeMyData"
/>
<p>{{myDataBackend}}</p>
</div>
</template>
<script>
export default {
data: () => ({
myDataBackend: 'Hello World'
}),
methods: {
changeMyData(evt) {
this.$set(this, 'myDataBackend', evt.target.value)
console.log('Changed the value to: ' + evt.target.value)
}
},
computed: {
computedData () {
return this.myDataBackend
}
}
}
</script>