1

I'd like to achieve something similar as "include" in android but in aurelia:

How to inject a plain html file content into my view, with binding evaluated within the parent View, and without using a custom element? Binding innerhtml is not enough as, according to the doc, the bindings expressions are bypassed.

Benoit
  • 1,922
  • 16
  • 25
  • 1
    You can use the compose element for this, but I have to ask why you wouldn't want to do an html only custom element? – Ashley Grant Oct 12 '16 at 00:31
  • Because of issues with validation and custom elements. Thanks "" works. – Benoit Oct 12 '16 at 01:21
  • 1
    `compose` is a custom element itself. If it works with `compose` then it will work with a custom element you build yourself. Here's the source code for `compose`: https://github.com/aurelia/templating-resources/blob/master/src/compose.js#L12 I am constantly pushing people away from using `compose` because custom elements you create yourself are pretty much always going to be faster than the dynamic setup of `compose`. – Ashley Grant Oct 12 '16 at 01:27

1 Answers1

4

As already said by Ashley, using <compose view="./your-view.html"></compose> element will work with an existing HTML file and it will inherit the parent context.

If you want to compose HTML dynamically (from a file, database, or built-up programmatically) then using the ViewCompiler will give you the best performance and flexibility, as this is one layer less than compose compared to how aurelia builds custom elements internally.

I gave a similar answer to a different (but related) question here:

Aurelia dynamic binding

You'd use the text plugin to load your HTML file as text into a variable, and then pass that to the ViewCompiler. I have a custom element for this which, in terms of performance, is probably not better than compose but it does allow for more control when working with raw html as input and you could do your own performance optimizations specific to your situation as needed:

import * as markup from "text!./your-element.html";

export class SomeViewModel {
    constructor() {
        this.markup = markup;
    }
}

And the view:

<template>
    <dynamic-html html.bind="markup"></dynamic-html>
</template>

For completeness sake, here is the custom element I encapsulated the ViewCompiler in:

import {
    customElement,
    TaskQueue,
    bindable,
    ViewCompiler,
    ViewSlot,
    View,
    ViewResources,
    Container,
    ViewFactory,
    inlineView,
    inject,
    DOM
} from "aurelia-framework";

@customElement("dynamic-html")
@inlineView("<template><div></div></template>")
@inject(DOM.Element, TaskQueue, Container, ViewCompiler)
export class DynamicHtml {
    @bindable()
    public html: string;

    public element: HTMLElement;

    private tq: TaskQueue;
    private container: Container;
    private viewCompiler: ViewCompiler;

    private runtimeView: View;
    private runtimeViewSlot: ViewSlot;
    private runtimeViewFactory: ViewFactory;
    private runtimeViewAnchor: HTMLDivElement;

    constructor(element, tq, container, viewCompiler) {
        this.element = <HTMLElement>element;
        this.tq = tq;
        this.container = container;
        this.viewCompiler = viewCompiler;
    }

    public bindingContext: any;
    public overrideContext: any;
    public bind(bindingContext: any, overrideContext: any): void {
        this.bindingContext = bindingContext;
        this.overrideContext = overrideContext;

        if (this.html) {
            this.htmlChanged(this.html, undefined);
        }
    }

    public unbind(): void {
        this.disposeView();

        this.bindingContext = null;
        this.overrideContext = null;
    }

    public needsApply: boolean = false;
    public isAttached: boolean = false;
    public attached(): void {
        this.runtimeViewAnchor = <HTMLDivElement>this.element.firstElementChild;

        this.isAttached = true;
        if (this.needsApply) {
            this.needsApply = false;
            this.apply();
        }
    }

    public detached(): void {
        this.isAttached = false;

        this.runtimeViewAnchor = null;
    }

    private htmlChanged(newValue: string, oldValue: void): void {
        if (newValue) {
            if (this.isAttached) {
                this.tq.queueMicroTask(() => {
                    this.apply();
                });
            } else {
                this.needsApply = true;
            }
        } else {
            if (this.isApplied) {
                this.disposeView();
            }
        }
    }

    private isApplied: boolean = false;
    private apply(): void {
        if (this.isApplied) {
            this.disposeView();
        }

        this.compileView();
    }

    private disposeView(): void {
        if (this.runtimeViewSlot) {
            this.runtimeViewSlot.unbind();
            this.runtimeViewSlot.detached();
            this.runtimeViewSlot.removeAll();
            this.runtimeViewSlot = null;
        }

        if (this.runtimeViewFactory) {
            this.runtimeViewFactory = null;
        }

        if (this.runtimeView) {
            this.runtimeView = null;
        }

        this.isApplied = false;
    }

    private compileView(): void {
        this.runtimeViewFactory = createViewFactory(this.viewCompiler, this.container, this.html);

        this.runtimeView = createView(this.runtimeViewFactory, this.container);

        this.runtimeViewSlot = createViewSlot(this.runtimeViewAnchor);
        this.runtimeViewSlot.add(this.runtimeView);
        this.runtimeViewSlot.bind(this.bindingContext, this.overrideContext);
        this.runtimeViewSlot.attached();

        this.isApplied = true;
    }

}

function createViewFactory(viewCompiler: ViewCompiler, container: Container, html: string): ViewFactory {
    if (!html.startsWith("<template>")) {
        html = `<template>${html}</template>`;
    }
    let viewResources: ViewResources = container.get(ViewResources);
    let viewFactory = viewCompiler.compile(html, viewResources);
    return viewFactory;
}

function createView(viewFactory: ViewFactory, container: Container): View {
    let childContainer = container.createChild();
    let view = viewFactory.create(childContainer);
    return view;
}

function createViewSlot(containerElement: Element): ViewSlot {
    let viewSlot = new ViewSlot(containerElement, true);
    return viewSlot;
}
Community
  • 1
  • 1
Fred Kleuver
  • 7,797
  • 2
  • 27
  • 38