Zenorbi
Zenorbi

Reputation: 2634

Create a new binding context for a custom attribute

What I want

<div amazingattr.bind="foo">
    ${$someValueFromAmazingattr}
</div>

Just like how this works:

<div repeat.for="bar of bars">
    ${$index}
</div>

Where I got stuck

import {customAttribute} from "aurelia-framework";

@customAttribute("amazingattr")
export class AmazingattrCustomAttribute {
    bind(binding, overrideContext) {
        this.binding = binding;
    }

    valueChanged(newValue) {
        this.binding.$someValueFromAmazingattr = newValue;
    }
}

While this works, the $someValueFromAmazingattr is shared outside the custom attribute's element, so this doesn't work:

<div amazingattr.bind="foo">
    Foo: ${$someValueFromAmazingattr}
</div>
<div amazingattr.bind="bar">
    Bar: ${$someValueFromAmazingattr}
</div>

Both of the "Foo:" and the "Bar:" show the same last modified value, so either foo or bar changes, both binding change to that value.

Why I need this?

I'm working on a value animator, so while I cannot write this (because value converters cannot work this way):

${foo | animate:500 | numberFormat: "0.0"}

I could write something like this:

<template value-animator="value:foo;duration:500">
    ${$animatedValue | numberFormat: "0.0"}
</template>

I imagine I need to instruct aurelia to create a new binding context for the custom attribute, but I cannot find a way to do this. I looked into the repeat.for's implementation but that is so complicated, that I could figure it out. (also differs in that is creates multiple views, which I don't need)

Upvotes: 4

Views: 612

Answers (1)

Zenorbi
Zenorbi

Reputation: 2634

After many many hours of searching, I came accross aurelia's with custom element and sort of reverse engineered the solution.

Disclaimer: This works, but I don't know if this is the correct way to do it. I did test this solution within embedded views (if.bind), did include parent properties, wrote parent properties, all seem to work, however some other binding solution also seem to work.

import {
    BoundViewFactory,
    ViewSlot,
    customAttribute,
    templateController,
    createOverrideContext,
    inject
} from "aurelia-framework";

@customAttribute("amazingattr")
@templateController //This instructs aurelia to give us control over the template inside this element
@inject(BoundViewFactory, ViewSlot) //Get the viewFactory for the underlying view and our viewSlot
export class AmazingattrCustomAttribute {
    constructor(boundViewFactory, viewSlot) {
        this.boundViewFactory = boundViewFactory;
        this.viewSlot = viewSlot;
    }
    bind(binding, overrideContext) {
        const myBindingContext = {
            $someValueFromAmazingattr: this.value //Initial value
        };

        this.overrideContext = createOverrideContext(myBindingContext, overrideContext);

        //Create our view, bind it to our new binding context and add it back to the DOM by using the viewSlot.
        this.view = this.boundViewFactory.create();
        this.view.bind(this.overrideContext.bindingContext, overrideContext);
        this.viewSlot.add(this.view);
    }

    unbind() {
        this.view.unbind(); //Cleanup
    }

    valueChanged(newValue) {
        //`this.overrideContext.bindingContext` is the `myBindingContext` created at bind().
        this.overrideContext.bindingContext.$someValueFromAmazingattr = newValue;
    }
}

Upvotes: 1

Related Questions