Vue mixin render HTML and append to $ref - javascript

I want to use a mixin to find a referenced Node and then append some HTML to it rendered using Vue, so I can pass data into it.
const Tutorial = guide => ({
mounted() {
this.guide = guide;
this.html = Vue.compile(`<p>Test</p>`).render;
guide['add-location'].forEach(step => {
this.$refs[step.ref].appendChild(this.html);
})
},
data: function() {
return {
guide: null,
html: null
}
}
});
export default Tutorial;
This is what I have at the moment, it gets the ref correctly, just can't append the HTML as I don't think i'm using Vue.compile correctly.
Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'

In my opinion, It's better if we can avoid mutate DOM directly. What about replace ref with v-html?
const tutorial = guide => ({
mounted() {
guide['add-location'].forEach(step => {
this[step.ref] += this.html;
})
},
data: function() {
return {
...guide['add-location'].reduce((result, step) => {
result[step.ref] = ''
return result
}, {}),
html: `<p>Test</p>`
}
}
});
const Foo = {
template: `
<div>
<div v-html='foo'></div>
<div v-html='bar'></div>
</div>
`,
mixins: [tutorial({
'add-location': [
{ ref: 'foo' },
{ ref: 'bar' }
]
})]
}
Another idea is using wrapper component to wrap target or if your target is a component then you create a wrapper as mixin too.
Using with html property:
<wrapper ref='foo'>
<div>Foo</div>
</wrapper>
const Wrapper = {
props: ['html'],
render(h) {
return h('div', [this.$slots.default, h('div', {
domProps: {
innerHTML: this.html
}
})])
}
}
...
this.$refs.foo.html = '<h1>Hello Foo</h1>'
Example
Or using with custom appendChild method:
const Wrapper = {
data: () => ({
children: []
}),
methods: {
appendChild(child) {
this.children.push(child)
}
},
render(h) {
return h('div', [
this.$slots.default,
...this.children.map(child => h('div', {
domProps: {
innerHTML: child
}
}))
])
}
}
...
this.$refs.foo.appendChild('<h1>Hello Foo</h1>')
this.$refs.foo.appendChild('<h1>Hello Bar</h1>')
Example
Or using with Vue.compile in case that html is not plain html:
const Wrapper = {
data: () => ({
template: '',
context: {}
}),
methods: {
setChild(template, context) {
this.template = template
this.context = context
}
},
render(h) {
let res = Vue.compile(this.template)
return h('div', [
this.$slots.default,
h({
data: () => this.context,
render: res.render,
staticRenderFns: res.staticRenderFns
})
])
}
}
...
this.$refs.foo.setChild('<h1>Hello {{ name }}</h1>', {
name: 'Foo'
})
Example

Related

Quill.js Edit Email Template

I want to edit my templates via Quill. I'm using bubble theme. So here is my component;
<template>
<QuillEditor #input="onInputChange" theme="bubble" toolbar="essential" id="quill-Editor" #ready="ready"></QuillEditor>
</template>
<script>
import { QuillEditor } from '#vueup/vue-quill'
import '#vueup/vue-quill/dist/vue-quill.bubble.css';
export default {
props: ['value'],
components: {
QuillEditor
},
created(){
console.log();
setTimeout(() => {
document.querySelector('#quill-Editor .ql-editor').innerHTML = this.value
}, 100);
},
methods: {
onInputChange() {
this.$emit('input', document.querySelector('#quill-Editor .ql-editor').innerHTML);
},
ready(editor){
editor.getModule('toolbar').addHandler('image', () => {
const tooltip = editor.theme.tooltip;
const originalSave = tooltip.save;
const originalHide = tooltip.hide;
tooltip.save = function () {
const range = editor.getSelection(true);
const value = this.textbox.value;
if (value) {
editor.insertEmbed(range.index, 'image', value, 'user');
}
};
tooltip.hide = function () {
tooltip.save = originalSave;
tooltip.hide = originalHide;
tooltip.hide();
};
tooltip.edit('image');
tooltip.textbox.placeholder = 'Embed URL';
});
},
}
}
</script>
Value is this html value. But when I do that quill is deleting all tags giving just few blank <p> tag. What should I do? How can I do that?

Dynamically creating React components using recursion

I'm trying to create react components dynamically based on my JSON structure using a recursive function. Here is my implementation.
components.js
import React from "react";
import Text from "./components/Text";
import Image from "./components/Image";
const Components = {
text: Text,
image: Image
};
export default (block) => {
if (typeof Components[block.type] !== "undefined") {
return React.createElement(Components[block.type], {
key: block.id,
block: block
});
}
return React.createElement(
() => <div>The component {block.type} has not been created yet.</div>,
{ key: block.id }
);
};
dummy data
const data = {
userId: 123123,
pages: [
{
id: 1,
type: "div",
data: { text: "hello" },
content: [
{
id: 123,
type: "text",
data: { text: "hello" }
},
{
id: 456,
type: "image",
data: { source: "url", link: "url" }
}
]
}
]
};
There is a third argument in the React.createElement which is to render the children. Extended your Components component like below to do the recursive rendering:
const ComponentsMap = {
div: Div,
text: Text,
image: Image
};
const Components = (block) => {
if (typeof ComponentsMap[block.type] !== "undefined") {
return React.createElement(
ComponentsMap[block.type],
{
key: block.id,
block: block
},
block.content ? block.content.map((subComp) => Components(subComp)) : null
);
}
return React.createElement(
() => <div>The component {block.type} has not been created yet.</div>,
{ key: block.id }
);
};
export default Components;
Working Demo:

How to pass HTML slot in vue component?

I want to pass default slot as VNode element as DOM Tag and text property undefined. currently i am getting VNode as text means when i log slot default then give VNode property text have HTML content and other properties are undefined i am passing like given below code:
<passage-question>{{ itemData(item).title }}</passage-question>
and PassageQuestion have code like this:
<script>
import { isArray } from 'lodash'
import PassageReference from './passage-reference.vue'
const PASSAGE_REF_MARK = '#passage-'
function isPassageRefAsAnchorNode(node, keyword) {
return node.tag === 'a' && node?.data?.attrs?.href?.startsWith(keyword)
}
function replacePassageRefAsAnchorNode(createElement, nodes) {
return (
nodes &&
nodes.map((node) => {
if (isPassageRefAsAnchorNode(node, PASSAGE_REF_MARK)) {
const refId = node.data.attrs.href.substr(PASSAGE_REF_MARK.length)
if (refId) {
return createElement(PassageReference, {
props: { refId },
})
}
}
if (node.children && isArray(node.children)) {
node.children = replacePassageRefAsAnchorNode(createElement, node.children)
}
return node
})
)}
export default {
components: {
PassageReference,
},
render(createElement) {
const nodes = Array.from(this.$slots.default || [])
const resultNodes = replacePassageRefAsAnchorNode(createElement, nodes)
return createElement(
'div',
{
attrs: { class: 'passage-question' },
},
resultNodes
)},
}
</script>
please help me. Thank You.

How do I call Axios on prop change in Vue?

On the change of the value id, I would like to make a JSON call via Axios and update necessary parts of the page. How do I do that? Currently, I have mounted and activated and they do not seem to be working...
Code:
const Home = {
template: `
<div class="user">
<h2>user {{ id }}</h2>
<h2>{{ info }}</h2>
bet
</div>
`,
props: {
id: {
type: String,
default: 'N/A'
}
},
data () {
return {
info: null
}
},
activated () {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json',
{ params: { id: id }}
)
.then(response => (this.info = response))
},
mounted() {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = 'response'))
}
}`
You can listen to id prop change by using watch:
watch: {
id: function(newId) {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json',
{ params: { id: newId }}
)
.then(response => (this.info = response))
}
}
Here is a little demo based on the code that you shared that shows how watch reacts to id prop change. Wrapper component below is solely for demonstration purpose as something that triggers id value change.
const Home = {
template: `
<div class="user">
<h2>user {{ id }}</h2>
<h2>{{ info }}</h2>
bet
</div>
`,
props: {
id: {
default: 'N/A'
}
},
data () {
return {
info: null
}
},
mounted() {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = 'response'))
},
watch: {
id: function(newId) {
console.log(`watch triggered, value of id is: ${newId}`);
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json',
{ params: { id: newId }}
)
.then(response => (this.info = response))
}
}
}
const Wrapper = {
template: '<div><home :id="id" /></div>',
components: { Home },
data() {
return {
id: 0
}
},
mounted() {
const limit = 5;
const loop = (nextId) => setTimeout(() => {
console.log(`#${nextId} loop iteration`);
if (nextId < limit) {
this.id = nextId;
loop(nextId + 1);
}
}, 3000);
loop(this.id);
}
}
new Vue({
el: '#app',
components: { Wrapper }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" ></script>
<div id="app">
<wrapper />
</div>

Vue binding parent and child components

How to binding parent's model to child in Vue.js?
These codes below is works fine. if i fill the input manually, then child's model return it's value to the parent's model.
But the issue is, if the data set from AJAX request in a parent, the input doesn't automatically filled.
Can anyone help me on this?
Form.vue
<template>
<form-input v-model="o.name" :fieldModel="o.name" #listenChanges="o.name = $event"/>
<form-input v-model="o.address" :fieldModel="o.address" #listenChanges="o.address = $event"/>
</template>
<script>
import FormInput from '../share/FormInput.vue'
export default {
data () {
return {
o: {
name: '',
address: ''
}
}
},
components: { 'form-input': FormInput },
created: function() {
axios.get('http://api.example.com')
.then(response => {
this.o.name = response.data.name
this.o.address = response.data.address
})
.catch(e => { console.log(e) })
}
}
</script>
FormInput.vue
<template>
<input type="text" v-model='fieldModelValue' #input="forceUpper($event, fieldModel)">
</template>
<script>
export default {
props: ['fieldModel'],
data() {
return {
fieldModelValue: ''
}
},
mounted: function() {
this.fieldModelValue = this.fieldModel;
},
methods: {
forceUpper(e, m) {
const start = e.target.selectionStart;
e.target.value = e.target.value.toUpperCase();
this.fieldModelValue = e.target.value.toUpperCase();
this.$emit('listenChanges', this.fieldModelValue)
}
}
}
</script>
Things are more straightforward if you take advantage of v-model in components.
If you put v-model on a component, the component should take a prop named value, and should emit input events to trigger it to update.
I like to make a computed to hide the event emitting, and allow me to just v-model the computed inside my component.
new Vue({
el: '#app',
data: {
o: {
name: '',
address: ''
}
},
components: {
'form-input': {
template: '#form-input',
props: ['value'],
computed: {
fieldModelValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue.toUpperCase());
}
}
}
}
},
// Simulate axios call
created: function() {
setTimeout(() => {
this.o.name = 'the name';
this.o.address = 'and address';
}, 500);
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
Name ({{o.name}})
<form-input v-model="o.name"></form-input>
Address ({{o.address}})
<form-input v-model="o.address"></form-input>
</div>
<template id="form-input">
<input type="text" v-model='fieldModelValue'>
</template>
The mounted() hook is blocking subsequent updates from the parent.
Remove mounted and change v-model to 'fieldModel'
<template>
<input type="text" :value='fieldModel' #input="forceUpper($event, fieldModel)">
</template>
<script>
export default {
props: ['fieldModel'],
data() {
return {
fieldModelValue: ''
}
},
// mounted: function() {
// this.fieldModelValue = this.fieldModel;
// },
methods: {
forceUpper(e, m) {
const start = e.target.selectionStart;
e.target.value = e.target.value.toUpperCase();
this.fieldModelValue = e.target.value.toUpperCase();
this.$emit('listenChanges', this.fieldModelValue)
}
}
}
</script>
Demo CodeSandbox

Categories

Resources