Providing the model for a component as a slot - aurelia

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>

Related

My dynamic component (layout) doesn't work with named slots in vuejs

I have problems to combine dynamic generated layouts with named slots.
To define my layouts I'm using "component :is"
//app.vue
<template>
<component :is="layout">
<router-view />
</component>
</template>
<script>
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
},
</script>
//layouts/default.vue
<template>
<div>
<div>
<slot name="header" />
</div>
<div>
<div>
<slot name="sidebar" />
</div>
<div>
<slot name="default"/>
</div>
</div>
</div>
</template>
// views/page.vue
<template>
<div>
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</div>
</template>
But now I get this error inside my code
'v-slot' directive must be owned by a custom element, but 'div' is not.
and console displays this error
<\template v-slot> can only appear at the root level inside the receiving component
If I remove the main div I get the error
The template root requires exactly one element.
What I'm doing wrong?
This is not easy to explain so please cope with me...
I really understand what you are trying to do but unfortunately it is not possible in Vue.
Reason for that is slots are more template compiler feature than runtime feature of Vue. What I mean by that ? When Vue template compiler sees something like <template #header>, it will take the inner content and compile it into a function returning virtual DOM elements. This function must be passed to some component which can call it and include the result in it's own virtual DOM it is generating. To do that template compiler needs to know to what component it should pass the function (that is the real meaning of 'v-slot' directive must be owned by a custom element, but 'div' is not. error message...ie compiler is "looking" for a component to pass the slot content to...)
But you are trying to use the slots as if they were "discoverable" at runtime. For your code to work the dynamic layout component must at runtime somehow discover that it's child (also dynamic thanks to <router-view />) has some slot content it can use. And this is not how slots work in Vue. You can pass the slot content your component receives from parent to a child components but do not expect that parent component (layout in this case) can "discover" slot content defined in it's child components...
Unfortunately only solution for your problem is to import the layout component in every "page" and use it as a root element in the template. You can use mixins to reduce code duplication (to define layout computed)
#/mixins/withLayout.js
export default = {
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
}
}
views/page.vue
<template>
<component :is="layout">
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</component>
</template>
<script>
import withLayout from '#/mixins/withLayout'
export default {
mixins: [withLayout]
}
</script>

how to select whole row in ant-design-vue table?

i have an ant-design-vue table which has the function to select the row and open a new drawer with the record.
this is the template of the table.
<a-table
:dataSource="employeeData"
:rowKey="record => record.sgid"
:pagination="{ pageSize: size }"
:columns="columns"
:loading="loading"
:scroll="{ x: 1300, y: 400 }"
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
>
<template slot="name" slot-scope="text, record">
<div class="click-event" #click="select(record)">{{ text }}</div>
</template>
<template slot="id" slot-scope="text, record">
<div class="click-event" #click="select(record)">{{ text }}</div>
</template>
<template slot="mobile" slot-scope="text, record">
<div class="click-event" #click="select(record)">{{ text }}</div>
</template>
</a-table>
with this code i add a <div> to all slot so that user can click anywhere in every column. but there is still some empty space between two column in a row that cannot be click. what should i do to make the user can click on a row to run the select function?
Wrap your column around a Link for redirection instead of an HTML anchor tag <a> <slots /> </a>.
react-router-dom
You could use Link from react-router-dom and use it to trigger your component or container. This will also isolate your slot function logic in a separate component or container
import { Link } from 'react-router-dom';
...
<Link to={`/redirect/to/slot-function-component/`}>
<Row Content />
</Link>
...
It is important to register this component in a routes.js
Ant Design: Anchor Component
If you're going to stick with Ant Design, then you might wanna explore Anchor Component API. Anchor Props and Link Props provides you with lots of options to implement your use case in multiple ways.
Explore the API and see what suits your requirements.

Why isn't the value of the object properties inserted here?

I started learning vue yesterday and I'm now fiddling around on the CLI3.
Currently I'm trying out the different approaches to inserting data into my markup.
Here, I basically want to make a "list of Lists".
This here is list1:
<template>
<div>
<ul v-for="item in items">
<li :text="item"></li>
</ul>
</div>
</template>
<script>
export default{
name:"list1",
data() {
return {
items: {
item1 : "itemA",
item2 : "itemB",
item3 : "itemC"
}
}
}
}
</script>
This is the list of lists:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
And this is inserted into myComplexView.vue inside views (im working with routing as well, though it doesnt work perfectly yet as you will see on the screenshot), which you can see here:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
</script>
This is the result Im getting:
https://imgur.com/H8BaR2X
Since routing doesnt work correctly yet, I had to enter the url into the browser manually. Fortunately, the site at least loaded that way as well, so I can tackle these problems bit by bit ^^
As you can see, the data was iterated over correctly by the v-for.
However, the data wasn't inserted in the text attribute of the li elements.
I'm a bit clueless about the cause though.
Maybe I'm not binding to the correct attribute? Vue is using its own naming conventions, based off standard html and jquery as far as I understood.
You've got this in your template:
<li :text="item"></li>
This will bind the text attribute to the value, outputting, e.g.:
<li text="itemA"></li>
You should be able to see this in the developer tools. In the picture you posted you hadn't expanded the relevant elements so the attributes can't be seen.
I assume what you want is to set the content. For that you'd either use v-text:
<li v-text="item"></li>
or more likely:
<li>{{ item }}</li>
Either of these will output:
<li>itemA</li>
On an unrelated note, I would add that this line will create multiple lists:
<ul v-for="item in items">
It's unclear if that's what you want. You're going to create 3 <ul> elements, each with a single <li> child. If you want to create a single <ul> then move the v-for onto the <li>.

Vue.js dynamic layout renderless component layout with multiple slots

I am trying to build a dynamic layout for my application. I have two different layouts, one being DefaultLayout.vue:
<template>
<div>
<main>
<slot/>
</main>
</div>
</template>
and a second one being LayoutWithFooter.vue, with two slots:
<template>
<div>
<main>
<slot/>
</main>
<footer>
<slot name="footer"/>
</footer>
</div>
</template>
My renderless component to handle the dynamic layout looks like this:
<script>
import Vue from 'vue';
import DefaultLayout from './DefaultLayout';
import LayoutWithFooter from './LayoutWithFooter';
export default {
props: {
name: {
type: String,
required: true
}
},
created(){
this.registerComponent("DefaultLayout", DefaultLayout);
this.registerComponent("LayoutWithFooter", LayoutWithFooter);
this.$parent.$emit('update:layout', this.name);
},
methods: {
registerComponent(name, component) {
if(!Vue.options.components[name]) {
Vue.component(name, component);
}
}
},
render() {
return this.$slots.default[0];
},
}
</script>
All of this works fine for the DefaultLayout.vue but when I want to use the LayoutWithFooter.vue, it cannot handle the two slots inside it. Here's an example usage:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
Problem now is, that the "content for the footer slot" does not get rendered inside of the footer slot of the LayoutWithFooter.vue.
First of all I want you to pay an attention to defining slots level in your example. You provided this code:
<template>
<layout name="LayoutWithFooter">
<div>
<div>some content</div>
<div slot="footer">content for the footer slot</div>
</div>
</layout>
</template>
But actually your div slot="footer" does not refer to footer slot of LayoutWithFooter.vue component. It because of anyway the very first child refers to default slot. And as a result it looks like:
"You want to set content for default slot and inside this default slot you tried to set content for footer slot" - but it's two different scopes.
The right options would look like on the next example:
<template>
<layout name="LayoutWithFooter">
<!-- default slot content -->
<div>
<div>some content</div>
</div>
<!-- footer slot content -->
<div slot="footer">content for the footer slot</div>
</layout>
</template>
I prepared some example based on code and structure you've provided. There you are able to switch layouts and check out how it works and use different components slot in one layout.
Check it here:
https://codesandbox.io/s/sad-fog-zr39m
P.S. Maybe some point are not totally clear, please reply on my answer and I will try to explain more and/or provide you with more links and sources.

Reference dynamically created component in Aurelia

I know I can create a reference to my component in my view model like this:
.html:
<template>
<mdfield view-model.ref="ref"></mdfield>
</template>
.ts:
export class Vm {
ref: any;
test(){
console.log(this.ref);
}
}
This works, but what is the syntax if I'm creating the components dynamically? Like this:
<template>
<div repeat.for="field of fields">
<mdfield view-model.ref="<what goes here?>"></mdfield>
</div>
</template>
I guess I want to add them to an array in my viewmodel for later reference, but how?
$index gives you the current index of the repeat.for. So, if you want to add the view-model references to an array:
<div repeat.for="field of fields">
<mdfield view-model.ref="refArray[$index]"></mdfield>
</div>