简体   繁体   中英

decimal validation directive in angularjs

I wanted to create directive in angular that would display error message if entered value is not in valid format. What I finally came with is:

http://plnkr.co/edit/l2CWu8u6sMtSj3l0kdvd?p=preview

app.directive('kbDecimalValidation', function ($parse, $rootScope, $compile) {
        return {
            restrict: 'E',
            scope: {
                inputFieldRef: '=?',
                kbModel: '=ngModel',
                kbRequired: '@required',
                inputName: '@'
            },
            template: '<span ng-form="kbDecimalValidationForm">' +
                        '<input ng-model="kbModel" ng-required="kbRequired" ' +
                            'size="6"' +
                            'ng-pattern="/^[0-9]+(\\.[0-9][0-9]?)?$/" ' +
                            '/>' +
                        '<div ng-show="!kbDecimalValidationForm[inputName].$valid && kbDecimalValidationForm[inputName].$error.required"' +
                            'style="color: red; font-weight: bold">Field is required</div>' +
                        '<div ng-show="!kbDecimalValidationForm[inputName].$valid && kbDecimalValidationForm[inputName].$error.pattern"' +
                            'style="color: red; font-weight: bold">Bad format format,<br />allowed: "0.00"' +
                        '</div>' +
                    '</span>',
            replace: true,
            priority: 50,
            controller: function($scope){
                $scope.$watch(
                        'kbDecimalValidationForm[inputName]',
                        function (value) {
                            $scope.inputFieldRef = value;
                });
            },
            compile: function (tElement, tAttrs, transclude) {
                if($.tempKbDecimalValidationGUID == undefined){
                    $.tempKbDecimalValidationGUID = 0;
                }
                var guidInputName = 'XXX' + ++$.tempKbDecimalValidationGUID + 'XXX';
                $(tElement).find('input').attr('name', guidInputName); //it is here to force angular to assign value to: $scope.kbDecimalValidationForm[guidInputName]
                                                                       //there is no expression in name, so angular won't add it to $$watchers
                return {
                    pre: function preLink($scope, iElement, iAttrs, controller) {
                        //$(iElement).find('input').attr('name', iAttrs.inputName); //it doesn't work if there is expression in inputName,
                                                                                    // expression will be evaluated later (after linkFunction) 
                                                                                    // and the name assigned here will be updated (re-parsed by angular watch)
                    },
                    post: function postLink($scope, iElement, iAttrs, controller) {
                        $scope.kbDecimalValidationForm[iAttrs.inputName] = $scope.kbDecimalValidationForm[guidInputName]; //rewrite value to make it available by parsed name
                        $(iElement).find('input').attr('name', iAttrs.inputName); //assign parsed name - GUID didn't contain expression, so it is not in $$watchers,
                                                                                  // so it won't be replaced by angular

                    }
                }
            }

        };
    });

but I'm sure it is not propper way to do it. I expirience a lot of problems with it. Can somebody tell me what is the propper way to achieve it?

PS: The problem I'm facing right now with the above directive is: when I use it in ng-repeat, and reorder repeated source the directive does not work correctly. I suspect the problem is with my "hacking coding" (the tempKbDecimalValidationGUID, and $scope.kbDecimalValidationForm variables)

For Angular 1.2.x, you will have to use the ngModel.$parsers and $formatters pipelines for validation. Angular 1.3 has the dedicated $validators and even $asyncValidators pipelines. So the outline of a validation solution for 1.2.x would be:

.directive("kbDecimalValidation", function() {
    function parseDecimal(value) {
        // implement the conversion from a string to number, e.g. (simpistic):
        var val = parseFloat(value);
        // return a number (for success), null (for empty input), or a string (describing the error on error)
    }

    function formatDecimal(value) {
        // format a number to a string that will be displayed; the inverse of parseDecimal()
        // throw error if something goes wrong
    }

    return {
        restrict: "A",
        require: "ngModel",
        link: function(scope, elem, attrs, ngModel) {
            ngModel.$parsers.push(function(value) {
                var val = parseDecimal(value);
                if( typeof val === "string" ) {
                    // an error occured
                    ngModel.$setValidity("kbDecimal", false);
                    // return undefined!
                }
                else {
                    ngModel.$setValidity("kbDecimal", true);
                    return val;
                }
            });

            ngModel.$formaters.push(function(value) {
                if( value == null || typeof value === "number" ) {
                    ngModel.$setValidity("kbDecimal", true);
                    try {
                        return formatDecimal(value);
                    }
                    catch(e) {
                        ngModel.$setValidity("kbDecimal", false);
                        return "";
                    }
                }
                else {
                    ngModel.$setValidity("kbDecimal", false);
                    return "";
                }
            });
        }
    };
})

Many details will need work, but hopefully you get the idea. The parseDecimal() / formatDecimal() functions could even go to a dedicated Angular service, if they become too complex, or need to be reusable.


About the display of error messages

A quick and dirty way is to use DOM manipulation through the elem argument of link() . Eg:

link: function(scope, elem, attrs, ngModel) {
    ...
    scope.$watch(
        function() { return ngModel.$error.kbDecimal; },
        function(newval) {
            var container = elem.parent();
            // append or remove the message as necessary
            ...
        }
    );
}

Another way, less quick but more componentized is to make 2 more directives. One will be placed on the <span ng-form> element (the container), another will display the messages. The HTML would be like:

<span ng-form="..." validation-container>
    <input ... kb-decimal-validation />
    <validation-messages></validation-messages>
</span>

Both kbDecimalValidation and validationMessages will require the validationContainer ; the controller of the validationContainer will have a method, called by the kbDecimalValidation , to get notified about the $error object. It will also expose a copy of the $error object. The validationMessages will $watch that object and display or hide the appropriate messages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM