Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Number input box in Knockout JS

I'm trying to create a number input box which will accept numbers only.

My initial value approach was to replace value and set it again to itself.

Subscribe approach

function vm(){
  var self = this;
  self.num = ko.observable();
  self.num.subscribe(function(newValue){
    var numReg = /^[0-9]$/;
    var nonNumChar = /[^0-9]/g;
    if(!numReg.test(newValue)){
      self.num(newValue.toString().replace(nonNumChar, ''));
    }
  })
}

ko.applyBindings(new vm())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="textInput: num" />

Now this approach works but will add another cycle of subscribe event, so I tried to use a custom binding so that I can return updated value only. New to it, I tried something but not sure how to do it. Following is my attempt but its not working. Its not even updating the observable.

Custom Binding attempt

ko.bindingHandlers.numeric_value = {
  update: function(element, valueAccessor, allBindingsAccessor) {
    console.log(element, valueAccessor, allBindingsAccessor())
    ko.bindingHandlers.value.update(element, function() {
      var value = ko.utils.unwrapObservable(valueAccessor());
      return value.replace(/[^0-9]/g, '')
    });
  },
};

function vm() {
  this.num = ko.observable(0);
  this.num.subscribe(function(n) {
    console.log(n);
  })
}

ko.applyBindings(new vm())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
  <input type="text" data-bind="number_value: num, valueUpdate:'keyup'">
  <span data-bind="text: num"></span>
</div>

So my question is, Can we do this using custom bindings and is it better approach than subscribe one?


Edit 1:

As per @user3297291's answer, ko.extenders looks more like a generic way for my subscribe approach. I'm looking for an approach (if possible in Knockout), which would clean value before it is set to observable.


I have taken reference from following articles:

  • How to update/filter the underlying observable value using a custom binding?
  • How can i update a observable in custom bindings?

Note: In the first example, they are using jQuery to set the value. I would like to avoid it and do it using knockout only

like image 481
Rajesh Avatar asked Sep 08 '16 15:09

Rajesh


People also ask

How do I assign a value to knockout observable?

To create an observable, assign the ko. observable function to the variable. A default value can be specified in the constructor of the call. Knockout then converts your variable into a function and tracks when the value changes, in order to notify the UI elements associated with the variable.

What is $data in knockout?

The $data variable is a built-in variable used to refer to the current object being bound. In the example this is the one of the elements in the viewModel. folders array.

What is $root in knockout?

$root. This is the main view model object in the root context, i.e., the topmost parent context. It's usually the object that was passed to ko. applyBindings . It is equivalent to $parents[$parents.

What does KO represent in knockout JS?

KO is that it updates your UI automatically when the view model changes.


1 Answers

I´m on favor of use extender as user3297291's aswer.

Extenders are a flexible way to format or validate observables, and more reusable.

Here is my implementation for numeric extender

//Extender

ko.extenders.numeric = function(target, options) {
  //create a writable computed observable to intercept writes to our observable
  var result = ko.pureComputed({
    read: target, //always return the original observables value
    write: function(newValue) {
      var newValueAsNum = options.decimals ? parseFloat(newValue) : parseInt(newValue);
      var valueToWrite = isNaN(newValueAsNum) ? options.defaultValue : newValueAsNum;
      target(valueToWrite);
    }
  }).extend({
    notify: 'always'
  });

  //initialize with current value to make sure it is rounded appropriately
  result(target());

  //return the new computed observable
  return result;
};

//View Model

var vm = {
  Product: ko.observable(),
  Price: ko.observable().extend({
    numeric: {
      decimals: 2,
      defaultValue: undefined
    }
  }),
  Quantity: ko.observable().extend({
    numeric: {
      decimals: 0,
      defaultValue: 0
    }
  })
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Edit

I get your point, what about and regular expression custom binding to make it more reusable?

Something like this.

function regExReplace(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

  var observable = valueAccessor();
  var textToReplace = allBindingsAccessor().textToReplace || '';
  var pattern = allBindingsAccessor().pattern || '';
  var flags = allBindingsAccessor().flags;
  var text = ko.utils.unwrapObservable(valueAccessor());
  if (!text) return;
  var textReplaced = text.replace(new RegExp(pattern, flags), textToReplace);

  observable(textReplaced);
}

ko.bindingHandlers.regExReplace = {
  init: regExReplace,
  update: regExReplace
}


ko.applyBindings({
  name: ko.observable(),
  num: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>


<input type="text" data-bind="textInput : name, regExReplace:name, pattern:'(^[^a-zA-Z]*)|(\\W)',flags:'g'" placeholder="Enter a valid name" />
<span data-bind="text : name"></span>
<br/>
<input class=" form-control " type="text " data-bind="textInput : num, regExReplace:num, pattern: '[^0-9]',flags: 'g' " placeholder="Enter a number " />
<span data-bind="text : num"></span>
like image 137
AldoRomo88 Avatar answered Sep 17 '22 11:09

AldoRomo88