Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add binding behavior to control two-way binding value update event #137

Closed
jdanyow opened this issue Nov 6, 2015 · 22 comments
Closed

Add binding behavior to control two-way binding value update event #137

jdanyow opened this issue Nov 6, 2015 · 22 comments
Assignees

Comments

@jdanyow
Copy link
Contributor

jdanyow commented Nov 6, 2015

https://gitter.im/Aurelia/Discuss?at=563c58e5c3b1996c03a23f0e

Equivalent of KO valueUpdate

cc @EisenbergEffect / @kdeberni

@jdanyow jdanyow self-assigned this Nov 6, 2015
@EisenbergEffect
Copy link
Contributor

@jdanyow I want to make a note that "valueUpdate" may not be the best name. One reason is that that seems associated with the value property of input elements....but there's another scenario this helps with: web components. A developer may be binding to an arbitrary property on a web component and need to do that two-way...in which case we would need them to tell us the update event name.

@jdanyow
Copy link
Contributor Author

jdanyow commented Nov 6, 2015

Agree- I should have been more clear with that comment. It's going to enable the equivalent scenarios that KO's valueUpdate binding but we're going to give ours a more precise name: updateTrigger. It's usage will be:

<!-- update on lost-focus only: -->
<input value.bind="foo & updateTrigger:'blur'">

<!-- update on lost-focus and on paste: -->
<input value.bind="foo & updateTrigger:'blur':'paste'">

Does this sound right? Or do we want to handle multiple events differently?

@EisenbergEffect
Copy link
Contributor

Beautiful. Love the idea of multiple events there too. I hadn't though of that...but it's needed. Thanks bro!

@fopsdev
Copy link

fopsdev commented Nov 6, 2015

i have a newbie question:
in my day job i'm using a sdk which fires a bunch of different events for nearly every user action.
but in most of the cases i've just wanted a valueChanged(oldValue,newValue) event with the possibility to cancel the user action.
sadly the supplier of this sdk didn't provide such an event. well they supply an similar one but the really bad thing about this event is that it doesn't always get fired (eg. it work on textboxes but not on comboboxes)
i don't wanna get too nostalgic here but i remember the vb5 days where the ui-controls where firing constantly a valueChanged(oldValue, newValue) with the cancel possibility. this was much more efficient that with the new framework i have to work today.
so now finally the question :) -> does html5, js as well provide such an valueChanged(..) event or is there something inside aurelia that could simplify this?
thanks for any insights

@alvarezmario
Copy link

@fopsdev yes indeed it has something like that.

@alvarezmario
Copy link

Here is an example:

class Foo {
   firstname = '';

   firstnameChanged(newVal, oldVal) {
      // do what you want...
   }
}

and the html side:

<input type="text" value.bind="firstname">

Whenever you change the input's value, the firstnameChanged method will be executed.

@fopsdev
Copy link

fopsdev commented Nov 6, 2015

oh ok :) thanks @Nomack84
do you know if it's also possible to cancel the model update then?

@fopsdev
Copy link

fopsdev commented Nov 6, 2015

...and i assume because this happens on model level somehow it will also work with checkbox, combobox, etc... right?

@alvarezmario
Copy link

yeah, work with every html element, and about the other question, you can put some logic in the firstnameChange method to cancel model update, but i'm not sure if can be done directly, by returning false or something like that.

@fopsdev
Copy link

fopsdev commented Nov 6, 2015

cool! will dig into propertyChanged then 👍

@EisenbergEffect
Copy link
Contributor

To have that work you need this code, actually:

class Foo {
  @bindable firstname = '';

   firstnameChanged(newVal, oldVal) {
      // do what you want...
   }
}

@alvarezmario
Copy link

Ah, true. To watch model property changes you need to use the new
BindingEngine class or the old and low level ObserverLocator .
El 06/11/2015 13:33, "Rob Eisenberg" [email protected] escribió:

To have that work you need this code, actually:

class Foo {
@bindable firstname = '';

firstnameChanged(newVal, oldVal) {
// do what you want...
}
}


Reply to this email directly or view it on GitHub
#137 (comment)
.

@alvarezmario
Copy link

@fopsdev here you will find and example: http://www.danyow.net/aurelia-property-observation/

@fopsdev
Copy link

fopsdev commented Nov 10, 2015

hey guys

what needs to be done if i would like to cancel the model update:
http://plnkr.co/edit/kzkP7ccAP7z2KjP43XMy?p=preview
(check the app.js)

@jdanyow
Copy link
Contributor Author

jdanyow commented Nov 10, 2015

If you want to truly cancel the update of the model property you'd need to implement a binding-behavior. Binding behaviors give you full access to the binding, including the ability to intercept the model property assignment and visa-versa. Something like this:

intercept-binding-behavior.js

const interceptMethods = ['updateTarget', 'updateSource', 'callSource'];

export class InterceptBindingBehavior {
  bind(binding, scope, interceptor) {
    let i = interceptMethods.length;
    while (i--) {
      let method = interceptMethods[i];
      if (!binding[method]) {
        continue;
      }
      binding[`intercepted-${method}`] = binding[method];
      let update = binding[method].bind(binding);
      binding[method] = interceptor.bind(binding, method, update);
    }
  }

  unbind(binding, scope) {
    let i = interceptMethods.length;
    while (i--) {
      let method = interceptMethods[i];
      if (!binding[method]) {
        continue;
      }
      binding[method] = binding[`intercepted-${method}`];
      binding[`intercepted-${method}`] = null;
    }
  }
}

Then you'd use it like this:

app.html

<template>
  <require from="./intercept-binding-behavior"></require>

  <div mousemove.delegate="mouseMove($event) & intercept:myInterceptorFunction"
       style="position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px">

     <input value.bind="foo & intercept:myInterceptorFunction">

     <button click.delegate="clearInput()">Clear</button>
  </div>
</template>

app.js

export class App {
  foo = "hello world";

  myInterceptorFunction =
    (method, update, value) => {
      console.log(`Intercepted the binding's [${method}] method!  The value is "${value}".`);

      // todo: if we want to cancel the update, skip the line below:
      update(value);
    };

  mouseMove(e) {
  }

  clearInput() {
    this.foo = '';
  }
}

@fopsdev
Copy link

fopsdev commented Nov 11, 2015

@jdanyow i've tried to include your proposal to my skeleton then running gulp watch but the i get a 404 when the html requires the .js file

image

(i've also renamed the file without the dashes and changed the require just to make sure there are no strange filename issues)

and yes, i can see the file in the \dist folder.

@EisenbergEffect
Copy link
Contributor

Make sure the file existing in the dist folder and that the name/reference matches.

@fopsdev
Copy link

fopsdev commented Nov 11, 2015

oops, it was a typo, thanks for the heads up!
now we are close to a working beforeUpdate(oldVal,newVal) - behaviour which support canceling and resetting the input field.
is there a possiblity to automagically reset the textbox to the "old" model value when canceling the update?

@fopsdev
Copy link

fopsdev commented Nov 11, 2015

@jdanyow @EisenbergEffect thanks a lot for your help so far!
maybe if we could get the plunk to work i could show you better what i would like to achieve in the end.
http://plnkr.co/edit/kzkP7ccAP7z2KjP43XMy?p=preview
(it's looking for an .html file instead of a .js file)
and for those interested to fiddle around with it. i'm trying to reset the control (textbox in this case) with the old model value. this would simulate the cancel-behaviour i've enjoyed back in the old days :)
Maybe the MicroTaskQueue would be my friend here but it's usage is a bit beyond me :(

@jdanyow
Copy link
Contributor Author

jdanyow commented Nov 11, 2015

is there a possiblity to automagically reset the textbox to the "old" model value when canceling the update?

let modelValue = binding.sourceExpression.evaluate(binding.source, binding.lookupFunctions);
binding.updateTarget(modelValue);

There's a plunker at the bottom of this post that you can adapt

@fopsdev
Copy link

fopsdev commented Nov 11, 2015

wow! binding behaviours will open up a whole new universe to us developers 👍
i do intercept the 'updateSource' method to cancel the model update and it works fine
but i'm still struggling with your suggestion above using the binding - object to set the controls - value directly.
i would like to call binding.updateTarget('Please enter again...') but i can't get hold of the binding object inside my interceptor function inside my viewmodel.
i'm learning a lot right now. so maybe i can figure it out...

@StrahilKazlachev
Copy link
Contributor

@jdanyow Can you give more info on the parameters types passed to bind and unbind, for those using TypeScript(possible types if the type can vary)?
From this:

let modelValue = binding.sourceExpression.evaluate(binding.source, binding.lookupFunctions);
binding.updateTarget(modelValue);

it looks like binding should be annotated with class Binding, but there is also a interface Binding, which doesn't expose .source and .lookupFunctions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants