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.

Sam
  • 6,167
  • 30
  • 39

1 Answers1

1

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 ;)

Fred Kleuver
  • 7,797
  • 2
  • 27
  • 38
  • Great, this sort of custom element is exactly what I needed, thanks! I'll integrate it into my solution and post here if I find issues. – Sam Jul 09 '18 at 23:40
  • It's working really well - I tweaked the code to remove the redundant outer
    container element and just used the inherited binding context. Just a question on performance, does this add a lot of extra processing overhead to the overall rendering time? Might it be valuable to add, say, a caching layer for the view compilation for each column template (as it only needs to happen once per column?) Thanks!
    – Sam Jul 10 '18 at 00:54
  • Just one more tweak - I renamed the html bindable to view, and made it a union type of string | ViewFactory. Then if a ViewFactory is provided, skip the compilation step as we assume compilation has already happened somewhere else. This allows the data-column elements to compile the view once, and provide it as a factory to elements, so there is less overhead per cell. – Sam Jul 10 '18 at 23:44
  • Hey, glad to hear it works. You make some good points - I once created this custom element for dynamically generating larger components/pages. I didn't look too deeply at performance because of the fairly negligible overhead it brings for this purpose. With how you're using it there are plenty of performance optimizations I can think of. I don't have time to do that right now, but if you can create an issue and/or submit a PR with some rough proposal I'd be happy to look into it and improve this area. – Fred Kleuver Jul 11 '18 at 00:15