Open mbest opened 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.
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).
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.
I like the _someProperty
idea, although it could be made even more obvious with somePropertyObservable
. It seems this might also eliminate the WeakMap dependency.
Could this issue be the cause of brianmhunt/knockout-secure-binding#23?
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.
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.
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.