How to use the component when interpolating a string?
For example, I have a component that do something like this:
Component-A
<template>
<div>{{someProp}}</div>
</template>
When I use that component in others components I pass someProp which is a vue-component and some plain text.
Other component
<component-A someProp="Some text and <component-B/>"/>
How do I prevent the text from being escaped? And instead of the "<component-B/>" the component was substituted?
Props can only be used to pass javascript values, such as strings, numbers, objects, arrays, things like that. In your example, someProp has the string value Some text and <component-B/>, so it will render literally Some text and <component-B/> in the template.
If you want to pass a template fragment (my own terminology), then you will need to use a slot instead of a prop:
Vue.component('component-a', {
template: '<span>Component A: <slot></slot></span>',
});
Vue.component('component-b', {
template: '<strong>Component B</strong>',
});
new Vue({
el: '#app',
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<component-a>
Some text and <component-b></component-b>
</component-a>
</div>
Related
I have this component, and would like to pass a parameter/prop to the component saying which x-template to use. When I do it like this, it fails:
JS:
Vue.component('custom-table', {
props: ['template'],
template: '#' + this.template
})
new Vue({ el: '#app' })
HTML:
<custom-table template="my-template"></custom-table>
<script type="text/x-template" id="my-template">
<p>My Template</p>
</script>
Error:
vue.js:3 [Vue warn]: Cannot find element: #undefined
How can I use dynamic templates like this?
I'm not sure whether this is actually a good idea but it does come pretty close to what you've requested:
Vue.component('custom-table', {
props: ['template'],
template: `<component :is="{ template: '#' + template }" />`
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script type="text/x-template" id="button-template">
<button>My Template</button>
</script>
<script type="text/x-template" id="em-template">
<em>My Template</em>
</script>
<div id="app">
<custom-table template="button-template"></custom-table>
<custom-table template="em-template"></custom-table>
</div>
The trick here is to use the object version of is, which allows you to pass in a component definition inline. Strictly speaking there are two components in play here, a parent and a child, and the x-template is assigned to the child. That said, the resulting DOM should be as desired as the parent doesn't add any extra elements of its own.
You can't have dynamic templates for a single component.
You could create various components, and then dynamically pick which component to render for the particular tag. For this, Vue supports dynamic component:
<component v-bind:is="currentTabComponentName"></component>
Alternatively, if you want caller to fill-in-the-blanks of your component with arbitrary HTML, then you can use slots.
Or, if it is just static HTML, then you can just pass the HTML itself as string, and render the content without escaping it:
<div v-html="task.html_content"> </div>
Maybe one of these works for you...
Other options could be to use render functions or JSX.
I'm trying to do this. It seems trivial, but is not working.
In my parent component, I'm instantiating the child EndpointDetailsForm component, and passing it the settingsDetails prop, like this:
<EndpointDetailsForm :endpointDetails="modalDetails.content" />
Inside the EndpointDetailsForm component, I'm retrieving the endpointDetails object, like this:
props: {
endpointDetails: {
type: Object
}
}
and trying to use its various properties as attributes, like this:
<b-form-input id="nameInput"
type="text"
v-model="form.name"
:placeholder="endpointDetails.name">
</b-form-input>
When I inspect the EndpointDetailsForm component, it shows me the endpointDetails as a prop, but when I inspect the input above, it tells me that the placeholder is null.
What am I missing?
In your template you have to use kebab-cased attributes. Vue will convert them to camelCased props:
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you’re using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents.
Therefore if your prop is named endpointDetails, you should refer to it as an attribute as endpoint-details. Therefore:
<EndpointDetailsForm :endpoint-details="modalDetails.content" />
Code example:
Vue.component('b-form-input', {
template: '#b-form-input',
props: {
placeholder: String,
},
});
Vue.component('endpointetailsform', {
template: '#EndpointDetailsForm',
props: {
// Vue converts kebab-case to camelCase.
endpointDetails: {
type: Object
},
},
});
new Vue({
el: '#app',
data: {
content: {
name: 'my placeholder',
},
},
});
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<!-- Use kebab-cased attributes -->
<endpointetailsform :endpoint-details="content" />
</div>
<template id="EndpointDetailsForm">
<b-form-input :placeholder="endpointDetails.name"></b-form-input>
</template>
<template id="b-form-input">
<input :placeholder="placeholder" />
</template>
Given the following, simple 'controlled component' in Vue.js, I expected the input value to be fixed, impossible to change, since it's bound (using v-bind) by Vue:
<body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<input type="text" v-bind:value="fixedValue">
</div>
<script>
var app = new Vue({
el: '#app',
computed: {
fixedValue: function () {
return 'This should NOT change'
}
}
})
</script>
</body>
But, in reality, the input text only respects this on initial load. I can click on the input field and type anything and it WILL change. Why is that and how to prevent that?
Here's the fiddle: https://jsfiddle.net/6w74yj28/
EDIT:
To compare with React (here's the fiddle: https://jsfiddle.net/ko7duw5x/), if you create a text input and bind it's value, it's impossible to change the text input value by typing inside (which is the behavior I'm trying to achieve with Vue).
I am fairly new to Vue and have started with a project with vue-cli.
I am looking into conditional rendering based on a prop sent from parent.
Home.vue (parent)
<template>
<Header have-banner="true" page-title="Home">
</Header>
</template>
<script>
import Header from "./Header";
export default {
components: {
Header,
},
name: "Home",
data() {
return {
header: "Hello Vue!",
};
},
};
</script>
Header.vue (child)
<template>
<header>
<div v-if="haveBanner == 'true'">
...
</div>
...
</header>
</template>
I have looked at another conventional way to achieve this but vue-cli renders templates differently.
As passing the prop in the HTML markup, the prop haveBanner evaluates as a string and, therefore, even if I did:
Parent
<Header have-banner="false"></Header>
Child
<div v-if="haveBanner"`>
...
</div>
That <div> would still display and, because of this, I am having to do an explicit check to see if it evaluates to 'true'. I am not a fan of this due to possible issues with type coercion and I am thrown a warning with a type check (===) saying:
Binary operation argument type string is not compatible with type string
Is there a way to for either the child to evaluate this prop as a boolean or for the parent to pass it as a boolean in the markup?
If passing in JS keywords such as boolean values or references to variables, you will need to use v-bind (or :), i.e.:
<Header v-bind:have-banner="true" page-title="Home">
This will have the effect of binding the boolean true to the prop, not a "true" string. If you are not using v-bind, the haveBanner prop will always be truthy because it is a string of non-zero length, no matter if you assign "true" or "false" to it.
Friendly hint: HTML tags are not case-sensitive, so you might want to use custom-header or my-header-component instead of Header:
<custom-header v-bind:have-banner="true" page-title="Home">
See proof-of-concept:
Vue.component('custom-header', {
template: '#customHeader',
props: {
haveBanner: Boolean,
pageTitle: String
}
});
new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js"></script>
<div id="app">
<custom-header v-bind:have-banner="true" page-title="Home"></custom-header>
<custom-header v-bind:have-banner="false" page-title="Home"></custom-header>
</div>
<script type="text/x-template" id="customHeader">
<header>
<div v-if="haveBanner">
<code>haveBanner</code> is true!
</div>
<div v-else>
<code>haveBanner</code> is false!
</div>
</header>
</script>
Pro tip: Use : shorthands to make your template more readable, i.e.:
<custom-header :have-banner="true" page-title="Home">
To use a Boolean type as a prop, you have to use v:bind
<Header v-bind:have-banner="true" page-title="Home">
or using a short syntax
<Header :have-banner="true" page-title="Home">
I am wondering if it is possible to allow a user to decide which html tag will be rendered in a VueJS Component.
For instance, imagine that we have <my-component>Some Text</my-component>.
I want to be able to have a property called tag which will determine which html tag is rendered. So, for example:
<my-component tag='a' href="http://some-url.com">Some Text</my-component>
Will render to:
<a href="http://some-url.com">Some Text</my-component>
And:
<my-component tag="div">Some Text</my-component>
Will render to:
<div>Some Text</my-component>
And so on for h1-h6, p, span and other tags.
Any ideas?
Thanks.
Sure, you can do that with the render function, using createElement and $slots. What you described is just writing HTML in a slightly weird way, but here's how you might do it:
new Vue({
el: '#app',
components: {
myComponent: {
props: ['tag', 'attributes'],
render(createElement) {
return createElement(this.tag || 'div', {attrs: this.attributes || {}}, this.$slots.default);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.2/vue.min.js"></script>
<div id="app">
<my-component tag="h1">A header</my-component>
<my-component tag="a" :attributes="{href:'http://www.google.com'}">a link to google</my-component>
<my-component>Default is div</my-component>
</div>