6

I am working on one requirement where I want to allow only even numbers to text box or number box(input type number). with minimum and maximum limit like from 4 to 14 and it should only increase by step of 2 if we have number box.

I tried with HTML input type number with min max and step attributes it's working fine but we can edit the text box with any number so to restrict I tried using directive but it's not working out for me. I will be glad if anyone can help me out with this.

HTML :

<body ng-controller="ctrl">

new : <number-only-input step="2" min="4" max="14" input-value="wks.number" input-name="wks.name" >

</body>

Script :

var app = angular.module('app', []);
app.controller('ctrl', function($scope){
    $scope.name = 'Samir Shah';
    $scope.price = -10;
    $scope.wks =  {number: '', name: 'testing'};
});

app.directive('numberOnlyInput', function () {
    return {
        restrict: 'EA',
        template: '<input type="text" name="{{inputName}}" ng-model="inputValue" />',
        scope: {
            inputValue: '=',
            inputName: '=',
            min: '@',
            max: '@',
            step: '@'
        },
        link: function (scope) {
            scope.$watch('inputValue', function(newValue,oldValue) {
                var arr = String(newValue).split("");
                if (arr.length === 0) return;
                if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
                if (arr.length === 2 && newValue === '-.') return;
                if (isNaN(newValue)) {
                    scope.inputValue = oldValue;
                    return;
                }
                if(!isNaN(newValue)){
                        if(newValue < parseInt(scope.min) || newValue > parseInt(scope.max)){
                            scope.inputValue = oldValue;
                            return;
                        }
                }
            });
        }
    };
});

6
  • stackoverflow.com/questions/20973649/… Commented Jun 23, 2015 at 10:29
  • @Vineet validation is fine. as I need to have only even numbers but the text number is editable to any number. Commented Jun 23, 2015 at 11:53
  • Why don't you use readonly = "readonly" ? Commented Jun 23, 2015 at 12:08
  • readonly will not show increment decrement button of input type number. Commented Jun 23, 2015 at 13:59
  • on your watch can use modulo operator (%). If (newValue%step != 0) newValue = oldValue; Commented Jul 14, 2015 at 7:45

3 Answers 3

1
<form name="testForm">
<div ng-controller="MyCtrl">
    <input type="text" name="testInput" ng-model="number" ng-min="2" ng-max="14" required="required" numbers-only="numbers-only" />
    <div ng-show="testForm.testInput.$error.nonnumeric" style="color: red;">
        Numeric input only.
    </div>
    <div ng-show="testForm.testInput.$error.belowminimum" style="color: red;">
        Number is too small.
    </div>
    <div ng-show="testForm.testInput.$error.abovemaximum" style="color: red;">
        Number is too big.
    </div>
    <div ng-show="testForm.testInput.$error.odd" style="color: red;">
        Numeric is odd.
    </div>
</div>
</form>

angular.module('myApp', []).directive('numbersOnly', function () {
return {
    require: 'ngModel',
    link: function (scope, element, attrs, modelCtrl) {
        element.bind('blur', function () {
            if (parseInt(element.val(), 10) < attrs.ngMin) {
                modelCtrl.$setValidity('belowminimum', false);
                scope.$apply(function () {
                    element.val('');
                });
            }
        });
        modelCtrl.$parsers.push(function (inputValue) {
            // this next if is necessary for when using ng-required on your input. 
            // In such cases, when a letter is typed first, this parser will be called
            // again, and the 2nd time, the value will be undefined
            if (inputValue == undefined) return ''
            var transformedInput = inputValue.replace(/[^0-9]/g, '');
            if (transformedInput != inputValue || (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') || parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10) || (transformedInput % 2 !== 0 && transformedInput !== '1')) {
                if (transformedInput != inputValue) {
                    modelCtrl.$setValidity('nonnumeric', false);
                } else {
                    modelCtrl.$setValidity('nonnumeric', true);
                }
                if (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') {
                    modelCtrl.$setValidity('belowminimum', false);
                } else {
                    modelCtrl.$setValidity('belowminimum', true);
                }
                if (parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10)) {
                    modelCtrl.$setValidity('abovemaximum', false);
                } else {
                    modelCtrl.$setValidity('abovemaximum', true);
                }
                if (transformedInput % 2 !== 0 && transformedInput !== '1') {
                    modelCtrl.$setValidity('odd', false);
                } else {
                    modelCtrl.$setValidity('odd', true);
                }
                transformedInput = '';
                modelCtrl.$setViewValue(transformedInput);
                modelCtrl.$render();
                return transformedInput;
            }
            modelCtrl.$setValidity('nonnumeric', true);
            modelCtrl.$setValidity('belowminimum', true);
            modelCtrl.$setValidity('abovemaximum', true);
            modelCtrl.$setValidity('odd', true);
            return transformedInput;
        });
    }
};
});

Active fiddle http://jsfiddle.net/tuckerjt07/1Ldmkmog/

Sign up to request clarification or add additional context in comments.

Comments

0

You could define a property with getter and setter to process the entered value. If the value does not match the requrements display messages but not accept new value.

Using this method you could apply any validation logic, the second field editValue is needed because otherwise you could not enter an invalid number. Therefore editValue alows to enter numbers with numerous digits which will be partially invalid during entering the value.

Property:

  // Property used to bind input containing validation
  Object.defineProperty($scope, "number", {
    get: function() {
      return $scope.editValue;
    },
    set: function(value) {
      value = parseInt(value);
      $scope.editValue = value;
      var isValid = true;
      // Min?
      if (value < parseInt($scope.min)) {
        $scope.toSmall = true;
        isValid = false;
      } else {
        $scope.toSmall = false;
      }
      // Max?
      if (value > parseInt($scope.max)) {
        $scope.toBig = true;
        isValid = false;
      } else {
        $scope.toBig = false;
      }
      // Step not valid
      if (value % parseInt($scope.step) > 0) {
        $scope.stepNotValid = true;
        isValid = false;
      } else {
        $scope.stepNotValid = false;
      }
      $scope.isValid = isValid;
      if (isValid) {
        $scope.value = value;
      }
    }
  });


Working example

Below you can find a complete working example directive containing the property described above including increase/decrease buttons:

var app = angular.module('myApp', []);

app.directive('numberOnlyInput', function() {
  return {
    restrict: 'E',
    template: '<input type="text" ng-model="number" ng-class="{\'error\': !isValid}"/><button ng-click="increase()">+</button><button ng-click="decrease()">-</button> Value: {{value}} {{stepNotValid ? (" value must be in steps of " + step) : ""}} {{toSmall ? " value must be greater or equal to " + min : ""}} {{toBig ? " value must be smaler or equal to " + max : ""}}',
    scope: {
      value: '=value',
      min: '@',
      max: '@',
      step: '@'
    },
    link: function($scope) {
      // Increase value
      $scope.increase = function() {
        var newValue = parseInt($scope.value) + parseInt($scope.step);
        if (newValue <= $scope.max) {
          $scope.number = newValue;
          $scope.editValue = $scope.number;
        }
      };
      // Decrease value
      $scope.decrease = function() {
        var newValue = parseInt($scope.value) - parseInt($scope.step);
        if (newValue >= $scope.min) {
          $scope.number = newValue;
          $scope.editValue = $scope.number;
        }
      };
      // Property used to bind input containing validation
      Object.defineProperty($scope, "number", {
        get: function() {
          return $scope.editValue;
        },
        set: function(value) {
          value = parseInt(value);
          $scope.editValue = value;
          var isValid = true;
          // Min?
          if (value < parseInt($scope.min)) {
            $scope.toSmall = true;
            isValid = false;
          } else {
            $scope.toSmall = false;
          }
          // Max?
          if (value > parseInt($scope.max)) {
            $scope.toBig = true;
            isValid = false;
          } else {
            $scope.toBig = false;
          }
          // Step not valid
          if (value % parseInt($scope.step) > 0) {
            $scope.stepNotValid = true;
            isValid = false;
          } else {
            $scope.stepNotValid = false;
          }
          $scope.isValid = isValid;
          if (isValid) {
            $scope.value = value;
          }
        }
      });
      // Init actual Value of the input element
      $scope.number = parseInt($scope.value);
      $scope.editValue = parseInt($scope.value);
    }
  };
});
app.controller('controller', function($scope) {
  $scope.value = 10;
});
.error {
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="controller">
  Number:
  <number-only-input min="4" max="14" step="2" value="value"></number-only-input>
</div>

3 Comments

Why use a controller instead of a directive which is designed to do things like this?
Adjusted the example to use a directive, so it fits better to the original question.
The problem now is that you are abusing scope. You are putting things that do not have to be in scope onto scope and thus will have to unnecessarily parse those objects everyone a $digest or $apply is called.
0

Why are you doing too much of work of a simple thing. Max length will not work with <input type="number" the best way I know is to use oninput event to limit the maxlength. Please see the below code, Its a generic solution work with all the Javascript framework.

<input name="somename"
    oninput="javascript: if (this.value.length > this.maxLength) this.value = this.value.slice(0, this.maxLength);"
    type = "number"
    maxlength = "6"
 />

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.