Vue js render text with html content - javascript

What i'm trying to do is have a root element with a prop that holds inner html like: hello<b>hey</b>
but i can't use v-html because this element also has children for example:
<template>
<component :is="element.tag" contenteditable="true">
<div contenteditable="false">
<span class="delete-obj" :id="'delete'+element.id" >delete</span>
</div>
<RenderString :string="element.content" />
</component>
</template>
<script>
import Vue from "vue";
Vue.component("RenderString", {
props: {
string: {
required: true,
type: String
}
},
render(h) {
const render = {
template: this.string ,
methods: {
markComplete() {
console.log('the method called')
}
}
}
return h(render)
}
})
export default {
name: "VElement",
props: {
element: {
required: false,
default: null
},
},
}
</script>
I have tried the above and I have tried using slots. I can solve it with vanilla JavaScript like element.innerText, but I don't want to. The main goal is that the element is editable when they type they are editing element.content that will be rendered and the div that's inside it is normal HTML that I also need.
The main problem is that the inner HTML that I want doesn't have a root element.
The element is something like:
{id:1,tag:"div",content:"hello<b>hey</b>"}
I want the final result to be:
<div contenteditable="true">
<div contenteditable="false">
<span class="delete-obj" :id="'delete'+element.id" >delete</span>
</div>
hello<b>hey</b>
<div>
And I want to edit hello<b>hey</b> when I click inside I don't want it wrapped in anything else and if I put v-html on the outer div the inner div is gone.

If you really aren't able to wrap the content in a parent element, maybe a good approach is to write a VUE directive that render the text.
JS FIDDLE FULL DEMO
//DIRECTIVE
Vue.directive('string', {
inserted(el, bind) {
/**
* Here you can manipulate the element as you need.
*/
el.insertAdjacentText('beforeend', bind.value);
}
});
//TEMPLATE
<template>
<component :is="element.tag" contenteditable="true" v-string="element.content">
<div contenteditable="false">
<span>delete</span>
</div>
</component>
</template>

Like Seblor said, v-html will work with nested html strings.
Simply replacing the RenderString component with a <div v-html="element.content"/> should get you what you want.
Tested with the given example of hello<b>hey</b>:
Vue.component('VElement', {
name: "VElement",
props: {
element: {
required: false,
default: null
},
},
template: '\
<component :is="element.tag" contenteditable="true">\
<div contenteditable="false">\
<span class="delete-obj" :id="\'delete\'+element.id">delete</span>\
</div>\
<div v-html="element.content"/>\
</component>'
})
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<v-element :element="{id:1, tag:'div', content:'hello<b>hey</b>'}" />
</div>

Related

problematically generated tree of components in vue2

I have a scenario which i need to build a nested menu and the menu can have an infinite level of nested layers (even thought this will not happen) and I'm wanting to know what is the best way of building the list of child components dynamically?
This is some test code that I put together to try some dynamic code from a function that would essentially give me the list of components in an array etc. What is the best way of problematically building up the child components tree?
<template>
<a-row>
<div v-html="getContent()"></div>
</a-row>
</template>
<script>
export default {
methods: {
getContent() {
return `<div #click="sayHello()">RYAN</div>`
},
sayHello() {
console.log('Hello there');
}
}
}
</script>
You can try with :is and pass component:
new Vue({
el: '#demo',
data() {
return {
component: '',
contents: ['RYAN', 'DJURO', 'SPIRO']
}
},
methods: {
getContent() {
this.component = 'comp'
},
}
})
Vue.component('comp', {
template: `<div #click="sayHello">{{ content }}</div>`,
props: ['content'],
methods: {
sayHello() {
console.log('Hello there ' + this.content);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="getContent">getContent</button>
<ul v-if="component">
<li v-for="(cont, i) in contents" :key="i">
<component :is="component" :content="cont"> </component>
</li>
</ul>
</div>

Automatic resizing of textarea after loading data in Vue

I have a Vue page which loads an json array from an api and displays the content in a list of multiple s by using v-for.
If you focus on one of the textarea's or change the text a function automatically resize's the textarea to fit the content.
<div v-for="post in posts">
<textarea v-model="post.body" rows="1" #focus="resizeTextarea" #keyup="resizeTextarea"></textarea>
</div>
resizeTextarea(e) {
let area = e.target;
area.style.overflow = 'hidden';
area.style.height = area.scrollHeight + 'px';
}
With my limited Vue knowledge, I can't find a solution to automatically resize all textarea's after loading the data from the API. There is no #load on a textarea.
I was trying to reach the same goal with using a watcher on the data but it feels like a long workaround.
Anyone a descent solution? Thank you!
https://jsfiddle.net/oehoe83/c1b8frup/19/
One solution would be to create a component for your textarea element and then resize it in the mounted() hook. Here's an example using single-file components:
// CustomTextarea.vue
<template>
<textarea
v-model="value"
ref="textarea"
rows="1"
#focus="resize"
#keyup="resize"
>
</textarea>
</template>
<script>
export default {
props: {
value: {
type: String,
required: true,
}
},
mounted() {
this.resize();
},
methods: {
resize() {
const { textarea } = this.$refs;
textarea.style.height = textarea.scrollHeight - 4 + 'px';
}
}
}
</script>
Then in your parent:
<template>
<div v-for="post in posts">
<CustomTextarea v-model="post.body" />
</div>
</template>
<script>
import CustomTextarea from './CustomTextarea.vue';
export default {
components: {
CustomTextarea,
}
// etc.
}
</script>
Note: if you're using Vue 3, replace value with modelValue in the child component.
Alternatively you could use a watch like you suggested, there's nothing wrong with that. Something like this:
watch: {
posts() {
// Wait until the template has updated
this.$nextTick(() => {
[...document.querySelectorAll('textarea')].forEach(textarea => {
this.resizeTextarea({ target: textarea });
});
});
}
}
you can add the ref attribute :
<div id="app">
<div v-for="post in posts" ref="container">
<textarea v-model="post.body" rows="1"#focus="resizeTextarea" #keyup="resizeTextarea" ></textarea>
</div>
</div>
and add the following code at the end of mounted() :
this.$nextTick(()=>{
this.$refs.container.forEach( ta => {
ta.firstChild.dispatchEvent(new Event("keyup"));
});
});

vue.js and slot in attribute

I'm trying to build a vue.js template that implements following:
<MyComponent></MyComponent> generates <div class="a"></div>
<MyComponent>b</MyComponent> generates <div class="a" data-text="b"></div>.
Is such a thing possible?
EDIT
Here is the best I can reach:
props: {
text: {
type: [Boolean, String],
default: false
}
},
and template
<template>
<div :class="classes()" :data-text="text">
<slot v-bind:text="text"></slot>
</div>
</template>
but the binding does not work, text always contains false.
You can use the mounted() method to get text using $slot.default property of the component to get the enclosing text. Create a text field in data and update inside mounted() method like this :
Vue.component('mycomponent', {
data: () => ({
text: ""
}),
template: '<div class="a" :data-text=text></div>',
mounted(){
let slot = this.$slots.default[0];
this.text=slot.text;
}
});
Note: It will only work for text, not for Html tags or components.
You're mixing slots and properties here. You'll have to pass whatever you want to end up as your data-text attribute as a prop to your component.
<MyComponent text="'b'"></MyComponent>
And in your template you can remove the slot
<template>
<div :class="classes()" :data-text="text"></div>
</template>
Another thing: it looks like your binding your classes via a method. This could be done via computed properties, take a look if you're not familiar.
You can try this.
<template>
<div :class="classes()">
<slot name="body" v-bind:text="text" v-if="hasDefaultSlot">
</slot>
</div>
</template>
computed: {
hasDefaultSlot() {
console.log(this)
return this.$scopedSlots.hasOwnProperty("body");
},
}
Calling
<MyComponent>
<template v-slot:body="props">
b
</template>
</MyComponent>

can not insert vue-router tag inside link

I have a Vuejs app where I allow users to register and become members.
I added vuex to store messages related to success/failure of the process.
Currently, everything works except when I try to store and show a [vue-router] link inside a variable like this:
...
Registration() {
if(true) {
this.$store.commit('SET_MESSAGE', {
type: 'success',
title: 'Registration Success',
content: 'please click <router-link to="/profile"> here </router-link> to see your profile'
}
}
Now, I can retrieve all the properties and display them, but the <router-link to="/profile"> here </router-link> tag does not transform (or functions) as it is supposed to.
This is how I am displaying it.
<div class="alert alert-dismissible" :class="'alert-' +type" >
<h1> {{tilte}} </h1>
<p> {{content}} </p>
</div>
I tried with <p v-bind:html='content'></p> and {{{ content }}} the route does not work in either case
The double mustaches interprets the data as plain text, not HTML. In
order to output real HTML, you will need to use the v-html directive DOC
<p v-html="content" />
EDIT
In order to make router-link to work you need to use a computed property that return an object of components options:
computed: {
contentComp () {
return { template: `<p>${this.content}</p>` }
}
}
Then render it:
<component :is="contentComp"></component>
Final result:
const profile = {
template: '<div> profile page! </div>'
}
const router = new VueRouter({
routes: [{
path: '/profile',
component: profile
}]
})
new Vue({
router,
el: '#app',
data: {
title: 'Registration Success',
content: 'please click <router-link to="/profile"> here </router-link> to see your profile'
},
computed: {
contentComp() {
return {
template: `<p>${this.content}</p>`
}
}
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<div>
<h1> {{title}} </h1>
<component :is="contentComp"></component>
</div>
<router-view></router-view>
</div>
You cannot insert dynamic content (that is to say content that needs to be compiled, like your router-link) with v-html, it's only meant for regular html. I suggest an alternate approach, that is to put your router-link in the template, but hide it with a v-if switch, that way it will be rendered and displayed only once you toggle the switch.
Template
<span v-if="displayRouter">
please click <router-link to="/profile"> here </router-link> to see your profile
</span>
JS
Registration() {
if(true) {
this.displayRouter = true
...
Generally I believe it's much clearer to keep all the html in the html, not in the JS.
Register () {
if(ok) {
this.$store.commit('SET_MESSAGE', {
type: 'success',
title: 'Registration Success',
content: 'please click here to see your profile'
}
}
You can use <a> tag.

How to bind DOM in vue.js2 for generate tweet in javaScript factory function?

How can i bind Document.getElementById in vue.js2. I am using embed tweet with javaScript factory function. its working fine if i only take DOM in .html file and generate tweet in vue file but not working if both is in .vue file. i need to use DOM in my .vue file and binds it with to factory function.
I have tried with ref and v-el but not worked.
<template>
<div class="row" id="home">
<h3 v-if="msg"><span class="label label-warning">{{msg}}</span></h3>
</div>
<!--<div v-for="tweet in tweetHtml">
<div v-html="tweet"></div>
</div>-->
</div>
<div id="container" ></div> <!-- generated tweet from fun should bind here -->
</template>
<script>
import appService from '../service'
import bus from '../service/bus'
export default {
data() {
return {
searchedTopic: '',
user_id: '',
tweets: {},
tweetHtml: []
}
},
created() {
// generate tweet with javaScript factory fun <<---=-=-=-=
twttr.widgets.createTweet('20',
document.getElementById('container'), // <<-- what to do to bind container with vue file's div.
{
theme: 'light'
})
.then( function( el ) {
console.log('Tweet added.');
});
},
methods: {
}
}
</script>
If you just get the DOM node, use ref can work.
twttr? what's this?

Categories

Resources