Search code examples
aurelia

Access an element's Binding


I have a custom attribute that processes authentication data and does some fun stuff based on the instructions.

<div auth="disabled: abc; show: xyz; highlight: 123">

There's a lot of complicated, delicate stuff happening in here and it makes sense to keep it separate from semantic bindings like disabled.bind. However, some elements will have application-logic level bindings as well.

<div auth="disabled.bind: canEdit" disabled.bind="!editing">

Under the covers, my auth attribute looks at the logged in user, determines if the user has the correct permissions, and takes the correct action based on the result.

disabledChanged(value) {
    const isDisabled = this.checkPermissions(value);
    if (isDisabled) {
        this.element.disabled = true;
    }
}

This result needs to override other bindings, which may or may not exist. Ideally, I'd like to look for an existing Binding and override it ala binding behaviors.

constructor(element) {
    const bindings = this.getBindings(element); // What is the getBindings() function?
    const method = bindings['disabled']
    if (method) {
        bindings['disabled'] = () => this.checkPermission(this.value) && method();
    }
}

The question is what is this getBindings(element) function? How can I access arbitrary bindings on an element?

Edit: Gist here: https://gist.run/?id=4f2879410506c7da3b9354af3bcf2fa1


Solution

  • The disabled attribute is just an element attribute, so you can simply use the built in APIs to do this. Check out a runnable example here: https://gist.run/?id=b7fef34ea5871dcf1a23bae4afaa9dde

    Using setAttribute and removeAttribute (since the disabled attribute does not really have a value, its mere existence causes the element to be disabled), is all that needs to happen:

    import {inject} from 'aurelia-framework';
    
    
    @inject(Element)
    export class AuthCustomAttribute {
      constructor(element) {
        this.el = element;
      }
    
      attached() {
        let val = false;
        setInterval(() => {
          if(this.val) {
            this.el.setAttribute('disabled', 'disabled');
          } else {
            this.el.removeAttribute('disabled');
          }
          this.val = !this.val;
        }, 1000);
      }
    }
    

    NEW RESPONSE BELOW

    You need to work directly with the binding engine. A runnable gist is located here: https://gist.run/?id=b7fef34ea5871dcf1a23bae4afaa9dde

    Basically, you need to get the original binding expression, cache it, and then replace it (if auth === false) with a binding expression of true. Then you need to unbind and rebind the binding expression:

    import {inject} from 'aurelia-framework';
    import {Parser} from 'aurelia-binding';
    
    
    @inject(Element, Parser)
    export class AuthCustomAttribute {
      constructor(element, parser) {
        this.el = element;
        this.parser = parser;
      }
    
      created(owningView) {
        this.disabledBinding = owningView.bindings.find( b => b.target === this.el && b.targetProperty === 'disabled');
    
        if( this.disabledBinding ) {
          this.disabledBinding.originalSourceExpression =  this.disabledBinding.sourceExpression;
    
          // this expression will always evaluate to true
          this.expression = this.parser.parse('true');
        }
      }
    
      bind() {
        // for some reason if I don't do this, then valueChanged is getting called before created
        this.valueChanged();
      }
    
      unbind() {
        if(this.disabledBinding) {
          this.disabledBinding.sourceExpression =  this.disabledBinding.originalSourceExpression;
          this.disabledBinding.originalSourceExpression = null;
    
          this.rebind();
    
          this.disabledBinding = null;
        }
      }
    
      valueChanged() {
        if(this.disabledBinding ) {
          if( this.value === true ) {
            this.disabledBinding.sourceExpression =  this.disabledBinding.originalSourceExpression;
          } else {
            this.disabledBinding.sourceExpression = this.expression;
          }
    
          this.rebind();
    
        } else {
          if( this.value === true ) {
            this.el.removeAttribute('disabled');
          } else {
            this.el.setAttribute('disabled', 'disabled');
          } 
        }
      }
    
      rebind() {
        const source = this.disabledBinding.source;
        this.disabledBinding.unbind();
        this.disabledBinding.bind(source);
      }
    }
    

    It is important that the attribute clean up after itself, as I do in the unbind callback. I'll be honest that I'm not sure that the call to rebind is actually necessary in the unbind, but it's there for completeness.