make an input only-numeric type on knockout
Asked Answered
C

10

20

i read many tutorials but i dont know how to do this, this is the input

input(type="text",name="price",id="price"data-bind="text: price,valueUpdate:['afterkeydown','propertychange','input']")

and this is my viewModel

price: ko.computed(function()
{
    return parseFloat(this.replace(' ','').replace(/[^0-9\.]+/g,"")) || '';
},this)

but this cause error: this has no method replace??? how can i pass the price value to the computed function??

Counterclockwise answered 11/6, 2013 at 15:51 Comment(7)
Instead of input type="text", what if you change it to input type="number"? I think those limit what you put into them to only numeric values.Sapajou
type="number" has less compatibility with some browsersCounterclockwise
Jon is correct in that you can change type to number but if I remember correctly not all browsers support that type. For example, I believe anything before IE10 let's you put text in there with no problemsSelfdelusion
this can be usefull to see compatibilites caniuse.com/#search=numberCounterclockwise
so i cant use type number cause i have to work for IE8+ and firefox tooCounterclockwise
There is a topic about this in knockout's documentation. You may need to create an extender. Their sample actually did what you require. knockoutjs.com/documentation/extenders.htmlHypnotize
tnx! i resolve the problem tnx to you @HypnotizeCounterclockwise
S
46

Is better to create custom binding http://knockoutjs.com/documentation/custom-bindings.html which accept only allowed characters [0-9,.] as numeric representation.

put this line into your view

<input id="text" type="text" data-bind="numeric, value: number">

put this line into your model (remember to bind number as observable property)

ko.bindingHandlers.numeric = {
    init: function (element, valueAccessor) {
        $(element).on("keydown", function (event) {
            // Allow: backspace, delete, tab, escape, and enter
            if (event.keyCode == 46 || event.keyCode == 8 || event.keyCode == 9 || event.keyCode == 27 || event.keyCode == 13 ||
                // Allow: Ctrl+A
                (event.keyCode == 65 && event.ctrlKey === true) ||
                // Allow: . ,
                (event.keyCode == 188 || event.keyCode == 190 || event.keyCode == 110) ||
                // Allow: home, end, left, right
                (event.keyCode >= 35 && event.keyCode <= 39)) {
                // let it happen, don't do anything
                return;
            }
            else {
                // Ensure that it is a number and stop the keypress
                if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105)) {
                    event.preventDefault();
                }
            }
        });
    }
};
Susannesusceptibility answered 12/6, 2013 at 7:46 Comment(6)
Does this really work? For me it doeesn't update Observable if it is somewhere on the UI? For example, you have <input ...> as above and also some <span data-bind="text: number"></span>. In this case, if user edits number inside input, UI is not updated after edit is done.Fanchet
Changes will be apply after focus change from input field. Here is simple example with live edit (without focus change). jsfiddle.net/jakethashi/L7UEmSusannesusceptibility
@Fanchet I left the textInput binding, and added the numeric as a second binding, works greatEpiscopate
How do you handle the dutch keyboard ? where numbers are Shift + Key ?Ahem
Also the Czech keyboard does not work which also need Shift+number to work.Tereasaterebene
It also doesn't work for a French keyboard layout that uses Shift for entering numbersArchimandrite
E
9

Knockout has extenders for this. Check This from knockoutjs.com explaining how to use observable extenders to force input to be numeric. I paste the code from the documentation here:

Source code: View

<p><input data-bind="value: myNumberOne" /> (round to whole number)</p>
<p><input data-bind="value: myNumberTwo" /> (round to two decimals)</p>

Source code: View model

ko.extenders.numeric = function(target, precision) {
    //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 current = target(),
                roundingMultiplier = Math.pow(10, precision),
                newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue),
                valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;

            //only write if it changed
            if (valueToWrite !== current) {
                target(valueToWrite);
            } else {
                //if the rounded value is the same, but a different value was written, force a notification for the current field
                if (newValue !== current) {
                    target.notifySubscribers(valueToWrite);
                }
            }
        }
    }).extend({ notify: 'always' });

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

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

function AppViewModel(one, two) {
    this.myNumberOne = ko.observable(one).extend({ numeric: 0 });
    this.myNumberTwo = ko.observable(two).extend({ numeric: 2 });
}

ko.applyBindings(new AppViewModel(221.2234, 123.4525));
Entwine answered 21/5, 2015 at 10:46 Comment(0)
C
6

I had a similar problem.

I also needed to ensure inter values only, and for IE9 and up (so type=numberical was not enough), and since we have a lot of international customers, i could not rely on keycodes either, so the following is what i ended up with:

//In my js class method (self is this as usual)
self.ensureNumberical = function (data, e) {
    var keyValue = e.key;
    if (keyValue.match(/[0-9]/g)) {
        return true;
    }
    return false;
}

//In my MVC View
data-bind="event: { keypress: ensureNumberical }"
Chuckhole answered 15/2, 2018 at 14:7 Comment(0)
H
2

An alternative approach: I have found that Knockout works well in combination with jQuery-validate. You just need to make sure that you validate the form before you attempt to use the numeric value.

Say you have a form DOM element, you can set up validation rules via

$(".yourform").validate({
    rules: {
        year: {
            digits: true,
            minlength: 4,
            maxlength: 4
        }
    },
    messages: {
        year: "Please enter four digits (e.g. 2009).",
    }
});

In your viewmodel you set the two-way binding up as usual, e.g. via self.year = ko.observable(""). Now make sure that you call $(".yourform").valid() before you are further processing self.year(). In my case, I am doing var year = parseInt(self.year(), 10). Right after form validation this is expected to always produce a meaningful result.

Hexylresorcinol answered 25/9, 2014 at 22:53 Comment(0)
Y
2

Best for today's scenerio https://github.com/Knockout-Contrib/Knockout-Validation

run the snippet below. entering a non digit or something more than 255 will cause the message to display.

function model() {
  var self = this;
  this.myObj = ko.observable().extend({ digit: true }).extend({ max: 255});
  }
  
  var mymodel = new model();

$(document).ready(function() {
  ko.validation.init();
  ko.applyBindings(mymodel);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.min.js"></script>

enter a digit less than or equal to  255 <input type="text" data-bind="textInput: myObj">

<p>
  Enter something other than a digit or over 255 will cause an error.
</p>

courtesy: Bryan Dellinger: https://mcmap.net/q/661769/-max-value-and-numerical-validation-in-knockoutjs

Yeld answered 10/5, 2018 at 10:38 Comment(0)
M
1
 <input type="text" id="alpha-validation" data-bind="value: YourDataName, valueUpdate: 'afterkeydown' , event: { 'input': AlphaCheck}" style="text-transform:uppercase">

create AlphaCheck Function and add that.

    $('#alpha-validation').keyup(function () {
        if (this.value.match(/[^0-9 ]/g)) {
            this.value = this.value.replace(/[^0-9 ]/g, '');
        }
    });

That will works!

Monocyclic answered 22/4, 2016 at 12:24 Comment(0)
Y
1

We can restrict user to input user more than two decimal number Ex. 23.81, 3452.83 Modified code is as below. The reference code is taken from the @Martin Surynek answer.

HTML -

<p>
    <input data-bind="value: myNumberOne" /> (round to whole number)</p>
  <p>
    <input data-bind="num, value: myNumberTwo" /> (round to two decimals)</p>

Script -

<script>
    ko.bindingHandlers.num = {
      init: function (element, valueAccessor) {
        $(element).on("keypress", function (event) {
          //debugger
          console.log(event.keyCode);
          var $this = $(this);
          var text = $this.val();

          // Stop insert two dots
          if ($this.val().indexOf('.') != -1 && (event.which == 190 || event.which == 110)) {
            event.preventDefault();
          }

          // Allow: backspace, delete, tab, escape, and enter
          if (event.keyCode == 46 || event.keyCode == 8 || event.keyCode == 9 || event.keyCode == 27 || event.keyCode ==
            13 ||
            // Allow: Ctrl+A
            (event.keyCode == 65 && event.ctrlKey === true) ||
            // Allow: .   ,event.keyCode == 188 ||
            ( event.keyCode == 190 || event.keyCode == 110) ||
            // Allow: home, end, left, right
            (event.keyCode >= 35 && event.keyCode <= 39)) {
            // let it happen, don't do anything
            return;
          }

          // Ensure that it is a number and stop the keypress
          if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode >
              105)) {
            event.preventDefault();
          }

          if ((event.which == 46) && (text.indexOf('.') == -1)) {
            setTimeout(function () {
              if ($this.val().substring($this.val().indexOf('.')).length > 3) {
                $this.val($this.val().substring(0, $this.val().indexOf('.') + 3));
              }
            }, 1);
          }

          if ((text.indexOf('.') != -1) &&
            (text.substring(text.indexOf('.')).length > 2) &&
            (event.which != 0 && event.which != 8) &&
            ($(this)[0].selectionStart >= text.length - 2)) {
            event.preventDefault();
          }          
          //console.log($(this)[0].selectionStart >= text.length - 2);
        });
      }
    };


    ko.extenders.numeric = function (target, precision) {
      //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 current = target(),
            roundingMultiplier = Math.pow(10, precision),
            newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue),
            valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;

          //only write if it changed
          if (valueToWrite !== current) {
            target(valueToWrite);
          } else {
            //if the rounded value is the same, but a different value was written, force a notification for the current field
            if (newValue !== current) {
              target.notifySubscribers(valueToWrite);
            }
          }
        }
      }).extend({
        notify: 'always'
      });

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

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

    function AppViewModel(one, two) {
      this.myNumberOne = ko.observable(one).extend({
        numeric: 0
      });
      this.myNumberTwo = ko.observable(two).extend({
        numeric: 2
      });
    }

    ko.applyBindings(new AppViewModel(221.2234, 123.4525));
  </script>
Yalu answered 16/1, 2018 at 6:22 Comment(0)
M
0

I know this question is a year old but let me post this for the sake of feature visitor of the page.

Check this out:

ko.bindingHandlers.numericnumbers = {
init: function (element) {
    $(element).on('keypress', function (number) {
        number = (number) ? number : window.event;
        var charcode = (number.which) ? number.which : number.keyCode;
        if (charcode > 31 && (charcode < 48 || charcode > 75))
            number.preventDefault();
    });
}};
Mitzimitzie answered 8/1, 2017 at 8:29 Comment(0)
B
0

Create you data-bind pointing at your shiny new code:

<input id="price" name="price" type="text" data-bind="numeric">

Shiny new knockout code:

price = ko.observable();
price.subscribe(function(newValue) {
    price = newValue.replace(/[\D\.]/g, '');
});

This means that every time you update the price, it will apply the logic in the function (in this case stripping out anything that isn't a number or a period), and apply it directly to the price. You can also add other validation or cool features here, like adding a currency sybmol at the start, keeping it to 2 decimal places, etc...

Blastomere answered 16/6, 2017 at 3:56 Comment(0)
C
0

With the help of "keydown" event we can restrict other key's in text box which should hold numeric values.

$(document).ready(function(){                
        $("selector").on("keydown", function (e) {
            //numbers, delete, backspace, arrows
            var validKeyCodes = [8, 9, 37, 38, 39, 40, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57];
            if (!($.inArray(e.keyCode, validKeyCodes) >= 0))
                    e.preventDefault();                 
        });           
    });
Crwth answered 3/7, 2017 at 4:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.