vue.js and slot in attribute - javascript

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>

Related

Vue js render text with html content

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>

Interpolation in a Vue component that creates HTML

I am building a web app using Vue 2.6.x. In the prototype that was created we have a line like this repeated many times:
<label class="title">On Time</label><span class="score float-right">Score: {{ score.ap_ontime }}
The only part of this whole line that changes is the data within the {{ }}. I would like to turn this into a component that I can call many times. For example:
<MyLabel title="On Time" score="score.ap_ontime" />
...so that I don't have to type this score interpolation over and over in a long HTML file. I know how pass props to the component and add text to the template of the component:
<template>
...
<label class="title">{{title}}</label>
...
</template>
..but what I can't figure out is how to take a string (e.g.score.ap_ontime) and have the template inject something that will render that value from a score plain old JavaScript object that exists in the application and is imported into the template. The original code I show at the top of this post renders fine and reacts to changes in the JavaScript object I just can't figure out how to do this in a component that creates HTML and Vue template syntax based on props.
Your new component should look something like this:
<template>
<label class="title">{{ caption }}</label>
<span class="score float-right">Score: {{ onTime }}</span>
</template>
<script>
export default
{
name: 'MyNewComponent',
props:
{
caption:
{
type: String,
default: ''
},
onTime:
{
type: [String, Number],
default: 0
}
}
}
</script>
Then you can call your component in this way:
<my-new-component :caption="Big title" :on-time="score.ap_ontime" />

How pass a v-model on the parent into a template

I'm trying to compose the UI for a search page, but wanna use components to reuse code. However, I need a way to pass the model of the page to the search component, and don't see how:
In index.html:
<template id="search">
<q-search inverted placeholder="Look" float-label="Search" v-model="search" /> <-- BIND HERE
</template>
<template id="ListCustomersPage">
<q-layout>
<q-layout-header>
<search v-model="search"></search> <-- HOW PASS INTO THIS
</q-layout-header>
</q-layout>
</template>
And the code:
const search = {
template: '#search',
props: ['search']
};
const ListCustomersPage = {
key: 'ListCustomersPage',
template: '#ListCustomersPage',
components: { search },
data() {
return {
title: 'Select Customer',
search:'' <-- FROM THIS TO 'BIND HERE'
}
}
};
I'm not sure if I'm 100% following what you're asking, but it seems like you just want to pass a property to a child component?
<search :search="search"></search> <-- HOW PASS THIS
Passing a prop to a child is done with v-bind or the colon short hand for it.
<child-component :property="parent_data"></child-component>
<child-component v-bind:property="parent_data"></child-component>
See the documentation here.

Parent onChange sets child property to true

Form (parent) component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<slot></slot>
</form>`,
methods: {
onChange:function(){
console.log("something changed");
}
}
};
and a c-tool-bar component (child) that is (if existing) inside the c-form's slot
export default {
template: `
<div class="c tool bar m006">
<div v-if="changed">
{{ changedHint }}
</div>
<!-- some other irrelevant stuff -->
</div>`,
props: {
changed: {
type: Boolean,
default: false,
},
changedHint: String
}
};
What I want to achieve is, that when onChange of c-form gets fired the function
checks if the child c-tool-bar is present && it has a changedHint text declared (from backend) it should change the c-tool-bar's prop "changed" to true and the c-bar-tool updates itself so the v-if="changed" actually displays the changedHint. I read the vue.js docs but I don't really know where to start and what the right approach would be.
You can use the two components like this:
<parent-component></parent-component>
You will be passing in the :changed prop a variable defined in the parent component in data(). Note the :, we are using dynamic props, so once the prop value is changed in the parent it will update the child as well.
To change the display of the hint, change the value of the variable being passed in as prop to the child component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<child-component :changed="shouldDisplayHint"></child-component>
</form>`,
data() {
return {
shouldDisplayHint: false,
};
},
methods: {
onChange:function(){
this.shouldDisplayHint = true; // change the value accordingly
console.log("something changed");
}
}
};
One simple way is to use $refs.
When you will create your child component inside your parent component, you should have something like: <c-tool-bar ref="child"> and then you can use it in your parent component code:
methods: {
onChange:function(){
this.$refs.child.(do whatever you want)
}
}
Hope it helps but in case of any more questions: https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs

How can i pass a property to a directive on vue js?

how can i get the property value on a v-show or v-if directive? i've already try to pass like the example bellow but not succeed.
v-show="cabin >= {{ number }}" number="5"
i'm stuck with this and since vue.js it's kind of new its so hard to find documentation and examples.
If you're using Vue Components, then you could do something like this:
Vue.Component('my-comp', {
template: '#my-template',
props: [
'number',
],
data: function(){
return{
cabin: 4
};
}
}):
and then in your view, use it like this:
<my-comp v-show="cabin >= number" number="5"></my-comp>
<template id="my-template">
<div>Lorem Ipsum</div>
</template>
Once you used a custom attribute (number), I guess that you used a component.
So, as he said #user3324298, you need something like this:
Vue.Component('my-comp', {
template: '#my-template',
props: ['number'],
data: function() {
return {
cabin: 4
}
}
})
But the template, should be something like this:
<template id="my-template">
<div v-show="cabin >= number" number="5">
<div>Lorem Ipsum</div>
</div>
</template>
<my-comp></my-comp>
v-show should be into the scope of component.

Categories

Resources