1

I have attempted implementing the suggestions below but neither seemed to trigger the required reaction each time a value was updated. This may due to my implementation as I'm new to Knockout.

I have an observable (Amount) which is bound to a TextBoxFor.

@Html.TextBoxFor(m => m.Amount, new { type = "number", data_bind = "value: Amount" })

When a value is entered it needs to be validated to ensure it doesn't exceed 999999. In addition to this, if the value is less than 50 then it needs to be set to 50. These checks needs to be made each time the 'Amount' changes.

var viewModel = {
    Amount: ko.observable().extend({numeric: 0, max: 999999})
};

I've tried implementing the solutions (in the answer to my previous post) but I couldn't get these to work.

The best I have come up with is creating a computed as follows. This checks the input value and updates correctly on the first attempt. Subsequent value changes do not trigger a change on the screen but stepping through the code, do seem to update the ViewModel.Amount value.

@Html.TextBoxFor(m => m.Amount, new { type = "number", data_bind = "value: amountMin" })


quoteVehicleViewModel.VehicleMileage = ko.observable(0);


viewModel.amountMin = ko.computed({
    read: function () {
        return viewModel.Amount();  
    },
    write: function (value) {
        if (value < 50) {
            viewModel.Amount(50);
        }
    }
}).extend({ numeric: 0, max: 999999 });

** A "viewModel.Amount()" entered in the Console shows the value as being 1000 but the value on screen still shows as the value entered.

Any assistance gratefully received

John

JohnT
  • 25
  • 1
  • 6
  • Many thanks for your response Joseph - I have seen your JSFiddle working. I have tried implementing in my solution and while I can see the coerceMin function runs on page load, it isn't hit when I change a value in the textbox. Is there anything else key (from your JSFiddle) that is required to do this, other than the code you added below? – JohnT Apr 22 '13 at 16:15
  • There's a sneaky line `ko.validation.registerExtenders();` that I didn't put into the actual extender snippet in the answer. Did you include this line into your code? – Joseph Gabriel Apr 22 '13 at 18:08
  • Hello. Yes I had included the registerExtenders and the coerceMin code is triggered as the page loads, just not when values are entered. Thanks – JohnT Apr 22 '13 at 21:10

2 Answers2

1

As far as I know knockout validation doesn't provide any ability to access the observable directly from the rule definition. If that were possible you could coerce the value right in a custom validation rule.

The best alternative that I've found is to create a custom extender for each rule you want to coerce.

Here's an example coercion extender for the "min" rule:

  ko.extenders.coerceMin = function (target, enable) {
      //collect rule values
      var minRule = ko.utils.arrayFirst(target.rules(), function (val) {
          return val.rule === "min"
      });
      if (minRule === null) {
          throw Error("coerceMin extender must be used in conjunction with the 'min' knockout validation rule");
      }
      var minValue = minRule.params;
      //listen for changes and coerce when necessary
      var subscription = null;
      if (enable && !subscription) {
          subscription = target.subscribe(function (newValue) {
              if (!isNaN(newValue)) {
                  var newValueAsNum = parseFloat(+newValue);
                  var valueToWrite = newValueAsNum < minValue ? minValue : newValueAsNum;
                  //only write if it changed
                  if (valueToWrite !== newValue) {
                      target(valueToWrite);
                  }
              }
          });
      } else {
          if (subscription) {
              subscription.dispose();
          }
      }

      //return the original observable
      return target;
  };

You can apply it in your view model like so:

var viewModel = {
    Amount: ko.observable()
              .extend({numeric: 0, min: 50, max: 999999})
              .extend({coerceMin: true});
};

Here's a fiddle to demonstrate: http://jsfiddle.net/f2rTR/1/

Joseph Gabriel
  • 8,339
  • 3
  • 39
  • 53
0

Very similar problem is here: Make Knockout applyBindings to treat select options as number

You could make computed value despite of subscribe. It'll look like:

var viewModel = {
    _amount: ko.observable(100).extend({numeric: 0, max: 999999, reset: true}),
    Amount : ko.computed(function(){
       return _amount < 50 ? 50 : _amount;
   })
};


ko.applyBindings(viewModel);

Edit

I also looked at extenders which you used here. You could add to your extender min value and pass it argument of your observable. You have to obviously write some code in write method of your extender

something like:

ko.extenders.numeric = function(target, min) {
...
   write:function(v){
      ...
      if(v<50) v=50;
      ...
      target(v)


   }
...
}
Community
  • 1
  • 1
SiMet
  • 614
  • 7
  • 18
  • Hi there SiMet, thanks for your response. I had a couple of further questions.......... In your first example, using computed instead of subscribe, which value do you bind your value to? eg . I tried both _amount and Amount but the computed wasn't triggered. Is there a way to implement an extenders solution that is specific to the observable? I'm new to KO but it looks like ko.extenders.numeric would affect all observables? Thanks – JohnT Apr 22 '13 at 09:07