SteveSanderson / knockout-es5

Knockout.js meets ECMAScript 5 properties
157 stars 39 forks source link

Support for custom two-way bindings #2

Open mbest opened 11 years ago

mbest commented 11 years ago

As @rniemeyer mentioned in the comments of your blog post, when using this plugin, the bindings aren't provided with the observable, only with the value of the property. The built-in bindings use Knockout's internal _ko_property_writers feature to be able to write back to the property, but custom bindings may not be able to use that, especially if they use an options object.

rniemeyer commented 11 years ago

The other problem that I ran into when I had previously looked at this type of thing was the issue of passing by reference vs. by value. Lots of KO plugin code assumes that you can pass observables around as an argument or set a variable equal to it (or set sub-properties on it). With ES5 properties, you would lose the getter/setters (http://jsfiddle.net/rniemeyer/Mvw6H/) when doing that and instead would always need to interact with the property off of the parent object.

SteveSanderson commented 11 years ago

The built-in bindings use Knockout's internal _ko_property_writers feature to be able to write back to the property, but custom bindings may not be able to use that

Yep - very valid point. Maybe it's a good reason for us to look at making a public property writers API in KO v3.

especially if they use an options object

That may be a reason not to use options objects in those particular cases, now that v3 handles independent bindings properly anyway.

Lots of KO plugin code assumes that you can pass observables around as an argument or set a variable equal to it (or set sub-properties on it)

Also very true. A couple of possibilities:

(1) In KO 3, we could make it easy for bindings to receive the raw, unevaluated text they are being bound with. An extra option on the ko.bindingsHandler.someBinding object could trigger a preprocess step where we wrap the bound value in quotes, so the binding always receives a string. For example, if somebinding had this option on, then:

data-bind="somebinding: myProperty"

... would be preprocessed so that the binding receives the value as if the developer wrote:

data-bind="somebinding: 'myProperty'"

... and then the binding can read/write viewModel[propertyName], or can access ko.getObservable(viewModel, propertyName).

This doesn't help with options objects, but is natural and simple in the non-options-object case.

(2) As well as creating a wrapped property called someProperty, ko.track could also add a property called _someProperty that is the underlying observable. Then the developer can work with sub-observables on _someProperty. Doesn't help much with bindings though, unless you bind to _someProperty (which would be weird).

rniemeyer commented 11 years ago

My other thought was to use a binding provider that would supply the bindings with actual observables. It would be kind of like doing the opposite of ko.toJS on what was passed to the binding, where it actually returns an object with the ES5 props replaced with real observables. For passing an observable directly, it seems like this would work nicely. However, when passing objects there might be issues with providing the binding with a new object (copy with observables) rather than a reference to the exact object that they intended to pass.

mbest commented 11 years ago

I like the _someProperty idea, although it could be made even more obvious with somePropertyObservable. It seems this might also eliminate the WeakMap dependency.

brianmhunt commented 10 years ago

Could this issue be the cause of brianmhunt/knockout-secure-binding#23?

aldendaniels commented 9 years ago

FWIW, I'm working around this limitation like this:

var ViewModel = function() {
   this.field = '';
   this.getObservable = function(fieldName) {
      return ko.getObservable(this, fieldName);
   }.bind(this);
   ko.track();
}
<div data-bind="myTwoWayBinding: getObservable('field')"></div>

Not elegant, but it works.

mateubo commented 9 years ago

I would like to put my 2 cents in as well. When I started using ES5, I wanted it to be compatible with all the existing bindings I had, and also invisible for the developer. I wrote a custom preprocess function that kind of parses the original binding string to make the observable available in the binding:

prop1  -->  ($data.prop1__koObs__ ? prop1__koObs__ : prop1)
{ p1: prop1 }  -->  {p1:($data.prop1__koObs__ ? prop1__koObs__ : prop1)}
{ p1: prop1WithExtender.isValid() }  -->  {p1:($data.prop1WithExtender__koObs__ ? prop1WithExtender__koObs__ : prop1WithExtender).isValid()}
{ p1: { subp1: prop1 > 12, subp2: prop2.prop3.prop4 } }  -->  {p1:{subp1:prop1 > 12,subp2:(prop2.prop3.prop4__koObs__ ? prop2.prop3.prop4__koObs__ : prop2.prop3.prop4)}}

It works quite fine, yet to do so, I had to modify the library a bit to make the original observable available through a property named propertyName + "__koObs__" which is not nice, because for one thing it makes a mess in a viewmodel's property tree when you debug it later on.