aureliaaurelia-templating

How to consume manually compiled template from repeater?


I'm building a custom data grid framework for a LOB-style Aurelia app and need help with how to template the main grid element so it can pick up custom cell templates from child column elements for rendering.

This is what I've done so far:

grid-example.html

<data-grid items-source.bind="rowItems">                    
    <data-column property-name="Name" t="[displayName]fields_Name">
        <div class="flex -va-middle">
            <div class="user-avatar avatar-square">
                <img
                    if.bind="row.dataItem.avatarUri" 
                    src.bind="row.dataItem.avatarUri" />
            </div>

            <span class="name">${row.dataItem.name}</span>
        </div>
    </data-column>

    <data-column property-name="StatusText" t="[displayName]fields_Status">
        <span class="label ${row.statusClass}">${row.dataItem.statusText}</span>
    </data-column>

    <data-column property-name="Location" t="[displayName]fields_Location">
        <span>${row.dataItem.location}</span>
    </data-column>

    <data-column property-name="DateCreated" t="[displayName]fields_MemberSince">
        <span tool-tip.bind="row.dataItem.dateCreated | dateToString:'long-date-time'">
            ${row.dataItem.dateCreated | dateToString:'short-date'}
        </span>
    </data-column>   
</data-grid>

data-column.ts

import {
    autoinject,
    bindable,
    noView,
    processContent,
    ViewCompiler,
    ViewFactory } from "aurelia-framework";
import { DataGridCustomElement } from "./data-grid";

@autoinject
@noView
@processContent(false)
export class DataColumnCustomElement {
    @bindable
    propertyName: string;

    @bindable
    displayName: string;

    cellTemplate: ViewFactory;

    constructor(private readonly _element: Element,
                private readonly _dataGrid: DataGridCustomElement,
                private readonly _viewCompiler: ViewCompiler) {
        this.cellTemplate = this._viewCompiler.compile(`<template>${this._element.innerHTML}</template>`);
        this._dataGrid.columns.push(this);
    }
}

data-grid.html

<template>
    <div class="table-wrapper -data-list -sticky-header">
        <table class="hover unstriped">
            <tbody>
                <tr class="labels">
                    <th repeat.for="column of columns">
                        <span>${column.displayName}</span>
                    </th>
                </tr>
                <tr repeat.for="row of itemsSource">
                    <td repeat.for="column of columns">
                        <!-- inject view for column.cellTemplate here? -->
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

data-grid.ts

import { autoinject, bindable } from "aurelia-framework";
import { DataColumnCustomElement } from "./data-column";

@autoinject
export class DataGridCustomElement {
    @bindable
    itemsSource: any[] = [];

    columns: DataColumnCustomElement[] = [];

    constructor(private readonly _element: Element) {
    }
}

The data-column elements declare a cell template which is parsed manually into a ViewFactory instance - what I'm stuck on is how to use the cell template for each data-column in the corresponding td repeater in the data-grid template, so it behaves as if I had directly declared the template content there.

Is this possible to do with the default repeat.for syntax? Or do I need a custom template controller to do this, which can additionally accept a ViewFactory instance as a bindable parameter from the scope?

If there is a better way to achieve this requirement then I'm open to that too.


Solution

  • You're essentially trying to compile+render dynamic html. There is nothing special about this specific to repeat.for or tables, but depending on what you're trying to achieve this is usually a bit more involved than simply passing html through the viewCompiler.

    You can see an example in a plugin I wrote: https://github.com/aurelia-contrib/aurelia-dynamic-html/

    I would probably either use that plugin (or simply copy+paste the code and tweak/optimize it to your needs) and then, keeping the rest of your code as-is, do something like this:

    data-column.ts

    this.cellTemplate = this._element.innerHTML; // just assign the raw html
    

    data-grid.html

    <tr repeat.for="row of itemsSource">
        <td repeat.for="column of columns"
            as-element="dynamic-html"
            html.bind="column.cellTemplate"
            context.bind="row[column.propertyName]">
        </td>
    </tr>
    

    In any case, you'll make this easier for yourself if you just use a custom element like this or in some other form. Making your own repeater will be very difficult ;)