[SOLVED] How to consume manually compiled template from repeater?


#1

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.


#2

You can create a view slot at the data column element and then add the view to it:



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

    bind() {
      this._view = this.cellTemplate.create();
      this._viewSlot.add(this.view);
    }

    unbind() {
      this._viewSlot.remove(this.view);
      this._view = null;
    }

The idea is the manipulate View / ViewSlot combo


#3

Thanks for the reply!

How does the view get attached to the DOM though in the correct cell location? I understand the View / ViewSlot mechanics, but the data-column elements aren’t rendered in the DOM at all once processed as the content of the parent data-grid element, so they aren’t anchored anywhere in the data-grid template.

I think I might need another custom element at the cell location that provides the view and view slot, using the view factory from the data-column element in the current binding scope.


#4

I think I misunderstood the scenario. From the look of grid-example.html, you want to delay the compilation of data-column html content, is this correct ?


#5

The data-column elements are used to provide the cell template for each column when rendered, but those data-column elements are never rendered directly, they just provide a template that needs to be injected as a view into the relevant location in the data-grid template.

I actually just found a solution - https://github.com/aurelia-contrib/aurelia-dynamic-html/
provides a custom element that emits a dynamic view based on bound HTML and context. By using this element, I can bind the cell template as raw HTML and the element will render a view for that HTML as it’s own template. Works really well.