Passing mutilple HTML fragments / other components to a component - aurelia

Consider a simple component in Aurelia:
export class FrameComponent
{
#bindable str1;
#bindable str2;
}
And the corresponding HTML template:
<template>
${str1}
${str2}
</template>
As simple as it gets. Now I can use this component in a parent like this:
<template>
<require from="./frame-component"></require>
<frame-component str1="Hello" str2="World"></frame-component>
</template>
Question 1
What if I don't want to provide the child component just with simple strings, but instead I want to set them with HTML fragments?
Question 2
What if the I want to pass whole complete components as in str1 and str2?

Here's a gist demo: https://gist.run/?id=0bf83980935015217cfb83250643c13f
Content Projection
It applies for Question 1 and Question 2.
[Documentation]
By using slots, you can define the content of your custom component declaratively. It can contain other custom components as well.
frame-slot.html
<template>
<div>
<slot name="str1">Default str1</slot>
</div>
<div>
<slot name="str2">Default str2</slot>
</div>
</template>
usage in app.html
<require from="./frame-slot"></require>
<frame-slot>
<div slot="str1"><h3>${slotStr1}</h3></div>
<div slot="str2">
<h3>${slotStr2}</h3>
<div>
<frame-slot></frame-slot>
</div>
</div>
</frame-slot>
Inline ViewStrategy
It applies for Question 1 and Question 2.
[Documentation]
By using InlineViewStrategy, you can define templates as plain string variables and display it with the help of <compose> element [Composition docs].
frame-inline.js
import { bindable, InlineViewStrategy } from 'aurelia-framework';
export class FrameInline {
#bindable template;
#bindable model;
viewStrategy;
attached() {
this.viewStrategy = new InlineViewStrategy(`<template>${this.template}</template>`);
}
}
frame-inline.html
<template>
<compose view.bind="viewStrategy" model.bind="model"></compose>
</template>
app.js
export class App {
inlineModel = {
name: "inline-template",
description: "This is an inline template",
slot: "Frame-slot Str1 content within frame-inline"
};
inlineTemplate = '<require from="./frame-slot"></require>' +
'<div>${model.name}: ${model.description}</div>' +
'<br>' +
'<frame-slot>' +
'<div slot="str1">${model.slot}</div>' +
'</frame-slot>';
}
usage in app.html
<require from="./frame-inline"></require>
<frame-inline template.bind="inlineTemplate" model.bind="inlineModel"></frame-inline>
InnerHTML binding
It applies for Question 1 only.
You can bind content to an element's innerHTML property. I'm mentioning that, but you should use it with caution or not at all.
[Documentation]
Binding using the innerhtml attribute simply sets the element's innerHTML property. The markup does not pass through Aurelia's templating system. Binding expressions and require elements will not be evaluated.
<div innerHTML.bind="content | sanitizeHTML"></div>

Related

Vue: bind dynamic and static classes

A bit new to Vue so I am looking for a better way(if there's one) to set a default class without using the class attribute twice.
Component.vue
<template>
<input class="form__control" :class="class" v-bind="$attrs"/>
<template>
<script>
export default {
props: ['class']
}
<script>
Usage
<component class="big-red" data-id="1" type="text"/>
You can bind dynamic and static classes within a single property, using the array declaration:
<template>
<input :class="['form__control', class]" v-bind="$attrs"/>
<template>

Cannot pass data to slot from owning component and parent context in Vue.JS

In the code below I have parent/child components where some data (I'm just using literals) is passed from the root context (data1) whilst other data is applied to the slot inside the parent component (data2). The output shows that data1 is available but data2 is not.
Why is the data2 literal not passed to the child component? I've google'd and seen references to using template with a scope but I don't understand why that would work when I'm not using v-bind in this example.
I suspect I've misunderstood something but would certainly appreciate some help!
Vue.component(
'child', {
props: ['data1', 'data2'],
template: `
<div>
<p>The child component - data1: "{{data1}}", data2: "{{data2}}"</p>
</div>`
}
)
Vue.component(
'parent', {
template: `
<div>
<p>The parent component</p>
<slot data2="data from parent component"/>
</div>`
}
)
new Vue({
el: '#el',
template: `
<div>
<parent>
<child data1="data from root"></child>
</parent>
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el" />
Update 04/JAN
I have an alternative use case where I'm trying to encapsulate Bootstrap syntax within a component. Given this is a more concrete example it may be easier to consider. Here I want all the Bootstrap specifics inside the form-group component but I can't seem to define class="form-control" on the slot instead of the template being passed in.
I have to admit that given what #collapsar has said I think this may be a different problem but I'm still in the process of getting my head around this!
Vue.component('formGroup', {
props: ['label'],
template: `
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">{{label}}</label>
<div class="col-sm-10">
<!-- I'd like to see this the class defined here merged with the template as described
in https://v2.vuejs.org/v2/guide/class-and-style.html#With-Components although admittedly
that's for components and not slots -->
<slot class="form-control"/>
</div>
</div>`
})
new Vue({
el: '#el',
template: `
<div class="container">
<form class="form-horizontal">
<form-group label="Email">
<!-- I don't want the class defined on this control but added by the form-group instead -->
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</form-group>
</form>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el">
</div>
You appear to conceptualize the slot mechanism in a slightly wrong way:
Data transfer from parent to child happens through properties which are declared in the child and defined in the parent.
Parts of the child component's template may be defined in the parent. The child component integrates these parts by means of the slot mechanism.
The template snippet may contain data placeholders. These placeholders are resolved by the child component (ie. the user of the snippet), which is where the slot element comes into play: its attributes instantiate the parameters
The rationale for why these placeholders exist in the first place might be that they are to be computed by the child component.
Finally a syntactic hook in the template snippet is needed as a placeholder mark, since the placeholder will be instantiated by the child component. This is the name of a JS object which will serve as a container for actual parameter values. As these values are specified by the child there is no way to know this container object's structure beforehand without abandoning the (child) component's data encapsulation which would would defeat the very purpose of component use. The 'slot-scope' attribute in the snippet definition contains the name of the said JS object (the necessary declaration takes place automagically in the background).
See it in code:
Vue.component(
'child', {
props: ['data1', 'data2'],
template: `
<div>
<p>The child component</p>
<p>
<span>Passed through parent: {{data1}}</span><br/>
<span>Passed from parent: {{data2}}</span><br/>
<slot data3="Data from the child template funnelled into a parent-defined slot"/>
</p>
</div>`
}
)
Vue.component(
'parent', {
props: ['data1'],
template: `
<div>
<p>The parent component</p>
<child
:data1="data1"
:data2="'data from parent'"
>
<template slot-scope="mixme">
<span>{{mixme.data3}}</span><br/>
</template>
</child>
</div>`
}
)
//
// Vue instance
// The template references the component 'parent' only.
// As 'child' is embedded into 'parent', this component will be referenced in _parent_'s template.
//
// Also note that 'data1' is passed down as a genuine property instead of a DOM attribute.
//
new Vue({
el: '#el',
template: `
<div>
<parent
:data1 = "'data from root'"
/>
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el" />

v-if on a component template tag

From the docs:
Because v-if is a directive, it has to be attached to a single
element. But what if we want to toggle more than one element? In this
case we can use v-if on a element, which serves as an
invisible wrapper. The final rendered result will not include the
element.
But on my template in my component:
<template v-if="false">
<div>
....
</div>
</template>
But the component still renders.
I ask because I want a hook on the component so if v-if is true, I can do some code in beforeMounted and beforeDestroyed if false.
If I undestood what are you doing...
You're putting v-if int the template tag ina .vue file right?
Like this
// component.vue
<template v-if="false">
<div>
My Component
</div>
</template>
<script>
export default {
name: 'my-component'
};
</script>
<styles>
</styles>
Right?
If YES, you are doing it wrong.
The template there is a tag for Webpack Vue Loader to load the component template.
So the if must go inside the template tag.
// component.vue
<template>
<div v-if="false">
My Component
</div>
</template>
<script>
export default {
name: 'my-component'
};
</script>
<styles>
</styles>
If you need to "hide" multiple elements, just encapsulate into another div.
As Lucas Katayama said, you cannot use v-if inside SFC, but another way to hide you component is use v-if on this component in its parent component.
Your reference to the docs is correct, you can use a v-if on a template tag. However, I believe conditionals on the top-level <template> in a Single File Component are ignored.
To achieve the effect showed in the docs (conditional render a template) that template needs to be within the top-level template section.
Example:
<script>
// your script section
</script>
<template>
<template v-if="false">
<div>
....
</div>
</template>
</template>
<style>
// your style section
</style>

Are custom attribute bindings possible with Vue templates?

I'm trying to bind a custom attribute value in my Vue template. How can I do this?
(EDIT: The following code actually binds correctly. A third party library (Foundation) was interfering with the binding. Leaving the question up as it may be useful to others.
<template>
<span v-bind="{ 'aria-controls': inputControlId }"></span>
<input v-bind="{ 'id': inputControlId }">
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
#Component
export default class Slider extends Vue {
inputControlId = "TheBourneId";
}
}
</script>
The common syntax for binding attributes is
<template>
<span v-bind:aria-controls="inputControlId"></span>
<input v-bind:id="inputControlId">
</template>
There is also a shorthand.
<template>
<span :aria-controls="inputControlId"></span>
<input :id="inputControlId">
</template>
You can bind multiple properties at once using the syntax in your question, it's just not commonly used outside class or style, especially for single attributes.
It sounds like the real issue was your CSS framework.

Providing the model for a component as a slot

Consider the following two custom elements in Aurelia (list & row):
row.html
<template>
<span>${name}</span>
</template>
row.js
export class Row
{
name = "Marry";
}
list.html
<template>
The List
<ol>
<li repeat.for="r of rows">
<slot name="rowItem" model.bind="r"></slot>
</li>
</ol>
</template>
list.js
import { bindable } from 'aurelia-framework';
export class List
{
#bindable
rows = [{name: "John"}];
}
The app will tie them together:
app.html
<template>
<require from="./list"></require>
<require from="./row"></require>
<list rows.bind="users">
<row slot="rowItem"></row>
</list>
</template>
app.js
export class App
{
users = [{name: "Joe"}, {name: "Jack"}, {name: "Jill"}];
}
The problem is that the model for the row is not set correctly. All I get as the output is the following:
The List
1.
2.
3.
So the question is; how can I provide the model for a slot in Aurelia?
Here's a Gist to demonstrate the problem in action.
Slots aren't going to work for what you want to do. It's a known limitation of slots in Aurelia. Slots can't be dynamically generated (such as inside a repeater).
Luckily, there's another option to accomplish what you want: template parts.
Template parts aren't well documented (my fault, I should have written the docs for them). But we have some docs in our cheat sheet. I've modified your gist to show how to use them: https://gist.run/?id=1c4c93f0d472729490e2934b06e14b50
Basically, you'll have a template element in your custom element's HTML that has the replaceable attribute on it along with a part="something" attribute (where something is replaced with the template part's name. Then, when you use the custom element, you'll have another template element that has the replace-part="something" attribute (again, where something is replaced with the template part's name). It looks like this:
list.html
<template>
The List
<ol>
<li repeat.for="row of rows">
<template replaceable part="row-template">
${row}
</template>
</li>
</ol>
</template>
app.html
<template>
<require from="./list"></require>
<require from="./row"></require>
<list rows.bind="users">
<template replace-part="row-template">
<row name.bind="row.name"></row>
</template>
</list>
</template>