SteveSanderson / knockout-es5

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

Question: How would you handle observable extensions ? #5

Open slaneyrw opened 11 years ago

slaneyrw commented 11 years ago

We use an extender to force type stored in the underlying value ( value binding in text boxes pushes strings into observable ).

How would I wire this up if I was to use ko.track ?

var amount = ko.observable().asInteger(0);

ko.observable.fn['asInteger'] = function (defaultValue) {
    var target = this;
    var interceptor = ko.computed({
        read: target,
        write: function (value) {
            var parsed = parseInt(value, 10);
            var manualNotifyFlag = false;
            if(isNaN(parsed)) {
                parsed = defaultValue;
                manualNotifyFlag = (target() === parsed);
            }
            if(!manualNotifyFlag) {
                target(parsed);
            } else {
                target.valueHasMutated();
            }
        }
    });
    interceptor(target());   // Ensure target is properly initialised.
    return interceptor;
};
ghost commented 11 years ago

I have the same issue: How is the new syntax to write this?:

result.name = ko.observable(item.name).extend({required: true});

jods4 commented 9 years ago

You can create an observable on your objects, ko-es5 will turn them into properties just the same.

var obj = { amount: ko.observable().asInteger(0) };
ko.track(obj);
obj.amount = 3;

This code should work.

Or you can use getObservable to grab and configure an underlying observable.

var obj = { amount: 0 };
ko.track(obj);
ko.getObservable(obj, 'amount').asInteger(0);
obj.amount = 3;

Trying to fit this with ko-validation is more difficult because the validation binding handlers expect to receive the observable itself, not his value.

grofit commented 9 years ago

Did you ever get it working with knockout validation?

I raised a similar sort of question on their board a while back but didnt get an answer: https://github.com/Knockout-Contrib/Knockout-Validation/issues/550

jods4 commented 9 years ago

@grofit I did, although it's not very pretty.

I used a binding preprocessor http://knockoutjs.com/documentation/binding-preprocessing.html

A binding preprocessor receives the source text of your binding and you're free to manipulate it anyway that you want.

There are several ways you can go with this. What I did is that I wanted explicit opt-in into validation, so I did this: if the binding text ended with a bang !, in my project it means that validation should be used for this binding. So the preprocessor removed the ! from the binding text and then added a validationCore binding with value ko.getObservable(context,text).

I would then use it like so: <input data-bind='value: amount!' />.

Note that it's been quite some time since I last looked into that. Maybe there's a better way now.

grofit commented 9 years ago

So the setup is basically a list of ko.getObservable(myVM, myPropertyName).someValidationRule(someValidationProperty);

I assume the asInteger is exposed directly from KO validation? and all other validation rules are exposed as such?

grofit commented 9 years ago

After playing about a bit here is what I came up with:

<input id="some-value" data-bind="value: amount">

    var vm = {
        amount: 2
    };

    ko.track(vm);

    ko.getObservable(vm, "amount").extend({ min: 3, max: 5});

    ko.getObservable(vm, "amount").isValid.subscribe(function(val) {
       console.log("Is Valid: " + val + " - " + vm.amount);
    });

    ko.applyBindings(vm);

Works fine, although it seems the group and validatedObservable seem to ignore the validation rules.

jods4 commented 9 years ago

@grofit That was pretty much my setup, except that I used a helper function to declare validation rules easily. It was used something like this:

var vm = { amount: 2, name: 'jods' };
ko.track(vm);
setupValidation(vm, { amount: { min: 3, max: 5 }, name: { required: true } });

For group and validatedObservable I don't think I used them. But at the core the problem is the same: they read object properties hoping to find an observable and get a value.

I think you have three options to make those things work:

  1. Fork or monkey patch those functions (really ugly).
  2. Automatically create properties that grant access to the underlying observable, e.g. get amount$obs() { return ko.getObservable(this, 'amount') } and pass those instead to functions that want to fiddle with observables.
  3. Set everything up before calling ko.track.
var vm = { 
  amount: ko.observable(2).extend({ min: 3, max: 5 }) 
};
// call `group` and `validatedObservable` if you want to.
// Then:
ko.track(vm);
grofit commented 9 years ago

Yeah I am currently just making a custom object to check over the isValid stuff. The latter option is not really an option here as I am trying to get away from the ko.observable calls.

Thanks for info though.