8

I need to validate a form textfield that's bound to a property of a model using EmberJS. I want the user to only be able to type valid, positive numbers.

I know of jQuery.isNumber(), but I don't know how to wire it to the field. I tried writing explicit getter/setter functions on the model's property using Ember.computed(...), but it didn't work.

Is there something similar to WinForms onChanging() event that I can hook up to?

Cristian Diaconescu
  • 34,633
  • 32
  • 143
  • 233
  • If you dont care for old browsers, you can take leverage of html5 input type "number" as well, like: {{view Ember.TextField type="number" valueBinding="someBinding"}} – Rajat May 21 '12 at 17:58
  • @Rajat So what would happen with old browsers? Broken functionality or simply no validation? And what is considered 'old' ? (Sorry, I'm a noob in this area) – Cristian Diaconescu May 29 '12 at 16:38
  • They will simply fallback to a regular textfield. On browser support check quirksmode: http://www.quirksmode.org/html5/inputs.html – Rajat May 29 '12 at 17:24

4 Answers4

14

There are a number of ways to do this sort of thing. Here we can accomplish this using bindings and observers.

First lets create a function that will always return a number.

var onlyNumber = function(input) {
    return input.toString().replace(/[^\d.]/g, "");
};

Using that we can do the following

App = Ember.Application.create();

App.person = Ember.Object.create({
    age: 42
});

App.NumberField = Ember.TextField.extend({
    valueBinding: Ember.Binding.from("App.person.age").transform(onlyNumber),

    _cleanValue: function() {
        this.set('value', onlyNumber(this.get('value')));
    }.observes('value')
});

1) We are creating a Binding to the Person's age, but anything that passes through this binding can only be a number. See Ember.Binding to/from transforms for more details.

2) We are observing the text field's value and setting it to be only a number when it changes. If the user enters '42a' we will immediately set it back to '42'. Note that even though '42a' was in the text input for a brief second it would not have been able to pass through the binding because of our transform.

Here is a fiddle showing this example: http://jsfiddle.net/nDBgC/

Ryan
  • 3,594
  • 1
  • 24
  • 23
  • I looked at transforms and apparently there's a shorthand if both directions of the transform work the same. I've modified the answer to reflect it. – Cristian Diaconescu May 29 '12 at 17:18
  • 3
    I think the validation code makes more sense in the model than in the view, Your 'observer' idea fits the bill perfectly, and no custom bindings (or custom views) are needed. I've forked a fiddle (btw this sounds dirty) from your example: http://jsfiddle.net/L6vmc/4/ – Cristian Diaconescu May 29 '12 at 17:41
  • Yup. If they are validations then they should be in the model. – Ryan May 30 '12 at 01:04
  • 1
    Binding.transform doesn't seem to exist anymore, any suggestions how to do this on a recent EmberJS? – Seth Mar 05 '13 at 14:11
  • If you have multiple fields that you want to use this view, do you need to create a view for each field? That doesn't seem very DRY imo. – max Oct 31 '13 at 15:32
3

You could add a keyDown event handler on your TextField, see http://jsfiddle.net/pangratz666/SKJfF/:

App.NumberTextField = Ember.TextField.extend({
    // implementation of this function, see http://stackoverflow.com/a/995193/65542
    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: 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();
            }
        }
    }
});​
pangratz
  • 15,875
  • 7
  • 50
  • 75
  • 4
    Man, this looks like ASM to me. It kind of is browser-ASM. – Cristian Diaconescu May 29 '12 at 16:42
  • You stated "I want the user to only be able to type valid, positive numbers." That's what this snippet does... – pangratz May 29 '12 at 17:20
  • "That's what this snippet does" -> I have seen tons of code which do what they should... But they can still be a horror. – yagooar Oct 18 '12 at 15:04
  • 1
    This snippet may not be as succinct or whatever, but it doesn't require connecting the view to a specific field. What if you need this view 10 times on different fields? – max Oct 29 '13 at 22:27
1

Code (handles only numeric input and allows binding of number rather than string, if desired):

App.NumberFieldComponent = Ember.TextField.extend({
  tagName: "input",
  type: "number",

  numericValue: function(key, value) {
    if (arguments.length === 1) {
      return parseFloat(this.get("value"));
    } else {
      return this.set("value", (value !== void 0 ? "" + value : ""));
    }
  }.property("value"),

  didInsertElement: function() {
    return this.$().keypress(function(key) {
      if ((key.charCode !== 46) && (key.charCode !== 45) && (key.charCode < 48 || key.charCode > 57)) {
        return false;
      }
    });
  }
});

Template:

{{number-field numericValue=someNumericProperty}}
neverfox
  • 6,680
  • 7
  • 31
  • 40
0

This is an updated answer (from @neverfox) for the latest Ember version (Ember-cli 2.0). It is written in coffeescript, and a bit altered to match the becomeFocus example (http://guides.emberjs.com/v1.10.0/cookbook/user_interface_and_interaction/focusing_a_textfield_after_its_been_inserted/)

  1. Generate the component:

    ember g component number-field

  2. Change the blueprint code (app/components/number-field.coffee) to

  `import Ember from 'ember'`
    
    NumberFieldComponent = Ember.Component.extend
        tagName: "input"
        type: "number"
    
     numericValue: ( (key, value) ->
      if arguments.length == 1
       parseFloat(this.get('value'))
      else
       return this.set("value", (value? ? "" + value : ""))
     ).property('value')
    
     assignFilter: ( ->
      this.$().keypress (key) ->
       if ((key.charCode != 46) && (key.charCode != 45) && (key.charCode < 48 || key.charCode > 57))
        false
     ).on('didInsertElement')
            
    
    `export default NumberFieldComponent`
Hugo Logmans
  • 2,202
  • 1
  • 19
  • 14