2
votes

We're storing all our page content and blog posts etc inside WordPress, using its API to render the data in our Aurelia app. This is working well;

<div class="excerpt" innerhtml.bind="post.excerpt.rendered"></div>

Authors would now like to be able to link to popups or use route-href or other custom Aurelia attributes from within their blog posts, however code added to the page using innerhtml.bind doesn't get parsed by Aurelia.

I love that a normal <a href="..."> "just works" in Aurelia - but we have plenty of custom attributes (like <button popup="name-of-popup">...</button> which can't be used by authors.

How can we overcome this?

Edit: With the comments from @bigopon I've started on something, but still can't quite get it to work. Either I suck at searching or the documentation is a little lacking on the TemplatingEngine.enhance() method, but I tried creating a custom attribute like this:

import {Aurelia, inject, TemplatingEngine} from 'aurelia-framework';

@inject(Element, Aurelia, TemplatingEngine)
export class AureliaEnhanceCustomAttribute {
    constructor (el, aurelia, templatingEngine) {
        this.el = el;
        this.aurelia = aurelia; // NOTE: I've never done this before - is it even correct?
        this.templatingEngine = templatingEngine;
    }

    attached () {
        this.el.innerHTML = this.value;

        // NOTE: Without checking for this we get an endless loop of attached() calls
        if (!this.el.classList.contains('au-target')) {
            this.templatingEngine.enhance({
                element: this.el,
                container: Aurelia.container, // NOTE: I'm guessing here
                resources: Aurelia.resources, // NOTE: Same here, but I need all my global resources to be available inside the enhanced element too
                bindingContext: {} // NOTE: Not sure what to pass in here at all.. :/
            });
        }
    }
}

And I'm using it like so:

<div aurelia-enhance.bind="exampleContent"></div>

Where exampleContent is a string fetched from an API call that could look something like this: '<my-custom-element></my-custom-element><button my-custom-attribute="some-value">Log in</button>'.

1
What do you mean by "parse", when using innerhtml ? - bigopon
Well if authors use a custom attribute or element inside their blog posts or pages, they aren't "parsed" (perhaps it's the wrong word?) by Aurelia so the elements or attributes never run. - powerbuoy
What you need is not innerhtml, use TemplatingEngine compose / enhance functions, it will help - bigopon
Alright, any chance you could post an answer with an example? - powerbuoy
I should be be able to write one this weekend or later today. If its urgent for you, have a look at aurelia dialog or compose element in aurelia templating resources repo. - bigopon

1 Answers

3
votes

You are on right track. There are few things to consider

  • bindingContext / overrideContext: these two you can get by hooking into bind lifecycle of the custom attribute. So you will be able to pass them to the enhance instruction (requires only bindingContext, but passing both is better, helps traversing the scope). About bindingContext, 99% it will be the view model you are in, but you can always use a different object. In this case, this (custom attribute view model), is the right one.

  • resources: depends on how you want the resource scope of the view returned by TemplatingEngine.prototype.enhance will be. Passing global Aurelia instance's resources will not yield the local resources scope of the custom element it resides in. In order to have the same resources with the element the attribute annotates on, hook in to created lifecycle of the attribute, store the first parameter as owningView. This is the view of the custom element containing the attribute. Then you can access its resources by owningView.resources

  • cleaning: TemplatingEngine.prototype.enhance returns a View, you need to store this reference to detached and unbind in your attribute life cycle too

  • Sanitizing

An example: https://gist.run/?id=9dd32bc8a772526ae527f593e26b275b

The gist above is an example I of inheritance / enhance I made to answer another question on Discourse. You can have a look at the select-field to see more example there. It's similar to what you did there.

P/S: If you are happy with the solution, maybe consider writing a blog post / article or open a topic on Aurelia Discourse forum to help others, who may also struggle there.


Updated example:

import {Aurelia, inject, TemplatingEngine, DOM} from 'aurelia-framework';

@inject(Element, TemplatingEngine)
export class AureliaEnhanceCustomAttribute {
    constructor (el, aurelia, templatingEngine) {
        this.el = el;
        this.templatingEngine = templatingEngine;
    }

    // Custom attribute doesn't have its own view
    // Only the view of the custom element that owns it
    created(owningElementView, thisView) {
        this.owningElementView = owningElementView;
    }

    bind(bindingContext, overrideContext) {
        this.bindingContext = bindingContext;
        this.overrideContext = overrideContext;
    }

    attached () {
        this.el.innerHTML = this.value;

        // NOTE: Without checking for this we get an endless loop of attached() calls

        if (!this.el.classList.contains('au-target')) {
            this.dynamicView = this. this.templatingEngine.enhance({
                element: this.el,
                // we have two choices here, either the container of owning element, or this attribute
                // Lets go with the element, since it propbably has everything we need
                container: this.owningElementView.container,
                // the resources has information about its parent,
                // So we just need the resources of the element containing this attribute
                resources: this.owningElementView.resources,
                // Or this.bindingContext (same)
                bindingContext: this,
                // this helps travel up
                overrideContext: this.overrideContext
            });
        }
    }

    detached() {
        this.dynamicView.detached();
    }

    unbind() {
        if (this.dynamicView) {
            this.dynamicView.unbind();
        }
    }
}