Zauker
Zauker

Reputation: 2394

Angularjs Form Validation of a group of input

With Angular I use specific directive for validate single input field. (i.e. email, username etc.)

In this project I have this scenario:

I need to set an Advance Payment field, this Advance must be between min/max value. To set the Advance I have three input text:

the sum of those three input is my Advance value.

So I need to validate Advance but I have no a single input with ng-model setted on.

What is the best way to check and validate the Advance Payment value?

I need to add a directive to all those three input fields? or is better a directive for the form?

Any suggestion will be appreciated.

jsfiddle example

(function () {
    var app = angular.module("myApp", ['ngMessages']);

    app.controller('myFormCtrl', function($scope) {
        $scope.plan = {};
        $scope.plan.advance = 0;
        $scope.plan.min_advance = 3000;   
        $scope.plan.max_advance = 6000;        

        $scope.updateAdvance = function(){
            var advance = 0;
            if ($scope.quotationAdvanceType && !isNaN($scope.plan.quotation))
                advance += $scope.plan.quotation;
            if ($scope.cashAdvanceType && !isNaN($scope.plan.cash))
                advance += $scope.plan.cash;
            if ($scope.financedAdvanceType && !isNaN($scope.plan.financed))
                advance += $scope.plan.financed;
            $scope.plan.advance = advance;
        }

    });

})();

Upvotes: 4

Views: 2411

Answers (2)

jme11
jme11

Reputation: 17372

Just to give you (and perhaps others) another option.

To simplify, you can use a number input with its ng-model set to plan.advance. This will allow you to use the min/max attributes. You can set the input to hidden if you prefer not to display it.

<input type="number" name="advance" ng-model="plan.advance" min="{{plan.min_advance}}" max="{{plan.max_advance}}" hidden />

Then you can use the built in validation for min and max such as:

<span ng-messages="myForm.advance.$error" role="alert">
  <span ng-message="min">Minimum not reached.</span>
  <span ng-message="max">Maximum exceeded.</span>
</span>

Run the code snippet below to see it in action:

(function () {
    var app = angular.module("myApp", ['ngMessages']);

    app.controller('myFormCtrl', function ($scope) {
        var advance = [];
        
        $scope.plan = {};
        $scope.plan.advance = 0;
        $scope.plan.min_advance = 3000;
        $scope.plan.max_advance = 6000;

        $scope.reset = function(val) {
            $scope.plan[val] = undefined;
            $scope.updateAdvance();
        };
        
        $scope.updateAdvance = function () {
            advance = [$scope.plan.quotation, $scope.plan.cash, $scope.plan.financed];
            $scope.plan.advance = advance.reduce(function (a, b) {
                a = a ? a : 0;
                b = b ? b : 0;
                return a + b;
            }, 0);
        }

    });

})();
@import url("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css");
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<script src="https://code.angularjs.org/1.3.15/angular-messages.min.js"></script>
<div ng-app="myApp" class="container">
    <form name="myForm" ng-controller="myFormCtrl" class="form-horizontal">
        <div class="form-group" ng-class="{ 'has-error' : myForm.plan.min_advance.$invalid && myForm.plan.min_advance.$touched}">
            <label for="plan.min_advance" class="control-label col-xs-4 col-sm-2">Min Advance</label>
            <div class="col-xs-8 col-sm-10">
                <input type="number" ng-model="plan.min_advance" class="form-control" />
            </div>
        </div>
    
        <div class="form-group" ng-class="{ 'has-error' : myForm.plan.max_advance.$invalid && myForm.plan.max_advance.$touched}">
            <label for="plan.max_advance" class="control-label col-xs-4 col-sm-2">Min Advance</label>
            <div class="col-xs-8 col-sm-10">
                <input type="number" ng-model="plan.max_advance" class="form-control" />
            </div>
        </div>
        <div class="form-group" ng-class="{ 'has-error' : myForm.surname.$invalid && myForm.surname.$touched}">
            <div class="col-xs-12">
                <label for="surname" class="control-label">Surname</label>
                <input type="text" value="" name="surname" id="surname" ng-model="surname" ng-minlength="3" ng-mAXlength="20" required="required" class="form-control" />
                <span ng-messages="myForm.surname.$error" class="help-block" role="alert" ng-show="myForm.surname.$touched">
                    <span ng-message="required">You forgot to enter your surname</span>
                    <span ng-message="minlength">Surname is too short</span>
                    <span ng-message="maxlength">Surname is too long</span>
                </span>
            </div>
        </div>
        <p>Set the Advance between minimun <strong>{{plan.min_advance | currency}}</strong> and maximum <strong>{{plan.max_advance | currency}}</strong>
        </p>
        <div class="form-group">
            <label for="quotation" class="col-xs-4 col-sm-2 control-label">
                <input type="checkbox" name="quotationAdvanceType" ng-model="quotationAdvanceType" ng-change="reset('quotation')"/> Quotation:
            </label>
            <div class="col-xs-8 col-sm-10">
                <input type="number" name="quotation" ng-value="plan.quotation" ng-model="plan.quotation" ng-change="updateAdvance()" class="form-control"  ng-disabled="!quotationAdvanceType" placeholder="{{'max '+ (plan.max_advance-plan.advance)}}" />
            </div>
        </div>
        <div class="form-group">
            <label for="cash" class="col-xs-4 col-sm-2 control-label">
                <input type="checkbox" name="cashAdvanceType" ng-model="cashAdvanceType" ng-change="reset('cash')" /> Cash:
            </label>
            <div class="col-xs-8 col-sm-10">
                <input type="number" name="cash" ng-value="plan.cash" ng-model="plan.cash" ng-change="updateAdvance()" class="form-control" ng-disabled="!cashAdvanceType" placeholder="{{'max '+ (plan.max_advance-plan.advance)}}" />
            </div>
        </div>
        <div class="form-group">
            <label for="financed" class="col-xs-4 col-sm-2 control-label">  
                <input type="checkbox" name="financedAdvanceType" ng-model="financedAdvanceType" ng-change="reset('financed')" /> Financed:
            </label>
            <div class="col-xs-8 col-sm-10">
                <input type="number" name="financed" ng-value="0" ng-model="plan.financed" ng-change="updateAdvance()" class="form-control" ng-disabled="!financedAdvanceType" placeholder="{{'max '+ (plan.max_advance-plan.advance)}}" />
            </div>
        </div>
        <div ng-class="{'has-error' : myForm.advance.$invalid && (quotationAdvanceType || cashAdvanceType || financedAdvanceType) && (myForm.quotation.$dirty || myForm.cash.$dirty || myForm.financed.$dirty)}">
            <input type="number" name="advance" ng-model="plan.advance" min="{{plan.min_advance}}" max="{{plan.max_advance}}" hidden />
            <p class="help-block">Total Advance: <strong>{{plan.advance ? (plan.advance | currency) : 'none'}}</strong>
                <span ng-messages="myForm.advance.$error" role="alert" ng-show="myForm.quotation.$dirty || myForm.cash.$dirty || myForm.financed.$dirty">
                    <span ng-message="min">Minimum not reached.</span>
                    <span ng-message="max">Maximum exceeded.</span>
                </span>
            </p>
        </div>  
        <button type="submit" class="btn btn-primary" ng-disabled="myForm.$invalid">Send</button>
    </form>
</div>

Is there an advantage over your custom directive approach? I would say yes and here's why:

  1. Your directive is tightly coupled to your controller scope, so you can't reuse it elsewhere in your application and it will require you to separately update it if you change one of your scope variables in the future.
  2. You are essentially duplicating code for functionality that already exists in the framework. That results in more upfront cost of development and maintenance effort down the line.

Upvotes: 2

Zauker
Zauker

Reputation: 2394

I solve my question with a brand new directive. I don't know if this is the right way to do that, but it works fine and the behaviour is correct.

app.directive('totalAdvance', function() {
    return {
        restrict: 'E',
        require: '^form',
        link: function(scope, element, attrs, formCtrl) {
            var advanceValue = 0;
            var advanceValidator = function() {
                if (advanceValue >= attrs.min && advanceValue <= attrs.max){
                    formCtrl.$setValidity('minAdvance', true);
                    formCtrl.$setValidity('maxAdvance', true);
                    return advanceValue;
                }else if (advanceValue < attrs.min) {
                    formCtrl.$setValidity('minAdvance', false);
                    formCtrl.$setValidity('maxAdvance', true);
                    return null;
                }else if (advanceValue > attrs.max){
                    formCtrl.$setValidity('minAdvance', true);
                    formCtrl.$setValidity('maxAdvance', false);
                    return null;
                }
            };

            scope.$watch('plan.advance', function(value) {
                advanceValue = value;
                return advanceValidator();
            });
            scope.$watch('plan.min_advance', function() {
                return advanceValidator();
            });
            scope.$watch('plan.max_advance', function() {
                return advanceValidator();
            });

        }
    };
});

jsfiddle example

Upvotes: 1

Related Questions