Dynamic binding expression


#1

Following a great article by Ashley Grant (https://blog.ashleygrant.com/2017/07/09/getting-my-hands-dirty-with-aurelias-binding-engine/) I tried to accomplist task for dynamic binding to expressions coming from my backend server as json. This approach works great until I tried to apply value converters to this rebased binding chain. After that it fails with error “Cannot read property ‘valueConverters’ of undefined”.

My repro gist.run: https://gist.run/?id=6e8a1ef7825abbfe1ec93242d1adcbbc
Could anyone with deep aurelia binding knowledge help? :slight_smile:


#3

Sorry for confusion - there is local DebugValueConverter in app.js. Just noticed it has the same name as custom element - renamed it to LocalDebug for consistency


#5

It’s not easy to re-base value converter, as you need to “re-sort” the value converter outside in, unlike binding behaviors, which don’t affect the evaluation. As the name suggested, you only re-base, not re-construct the expression, so I doubt it’s easy. What you can do is to walk through the expression and create a new expression instead of re-basing it.


#6

cc @AshleyGrant
I have just read Ashely’s post.

I can do it in simpler alternative way, without deep understanding of aurelia-binding.

My approach is to use value converter, not binding behavior. The result is human understandable code, plus more permissive in chain of value converters.

Note: beware the fromView expects a simple propertyExpression like "address.city", otherwise the setExp will fail. But this doesn’t mean GetValueConverter cannot be used for complex expression like "address.state + ' ' + address.zip". You can still use GetValueConverter in one-way binding with complex expression, where fromView is never called.

Update: thx for @bigopon’s help, it now works correctly if used in more than 1 place.


@inject(Parser)
export class GetValueConverter {
    constructor(parser) {
     this.parser = parser;
    }
    
    toView(_: any, model: any, propertyExpression: string): any {
        let getExp = this.parser.parse(propertyExpression);
        return getExp.evaluate({ bindingContext: model });
    }
    
    fromView(value: any, model: any, propertyExpression: string): any {
        let setExp = this.parser.parse(propertyExpression + ' = $value');
        setExp.evaluate({
          bindingContext: model,
          overrideContext: { $value: value }
        });
        
        return model;
    }
}

The downside is the binding is now verbose, you have to call model twice in order to pass enough information to the value converter.

<input value.bind="model | get:model:property.expression | localDebug">

See it working here:


#7

Thanks, works like a charm :blush:


#8

Just found a problem with this solution - validation fails as validation lib is getting property by binding.sourceExpression

Seems like the ony way is to update sourceExpression somehow…
Gist here: https://gist.run/?id=6e8a1ef7825abbfe1ec93242d1adcbbc


#9

I have no idea how to make aurelia-validation to work with that. I am not an aurelia-validation user.

But I have something you might be interested.

I have a different view on validation. My view is simple, validation is a function that takes rule and model as input, produces a structured errors object as output. I don’t want my view template doing anything with this kind of model layer logic. So I created something I consider simple enough and flexible enough.

Have a look of my implementation of your gistrun with my own tool bcx-validation, the result is clean, flexible, and approachable code.

I updated my previous gistrun.

The current doc of bcx-validation is very mathematical, without showing how to use it in Aurelia app. I need to write some nice examples of Aurelia usage in order to promote it to the community. The current doc is not ready for that now.