Custom widget to validate user input

1
+2
-1

Not really a question but I could not find a lot of information in the documentation or the existing contributed projects.

I have a form with SIREN / SIRET fields (those are identification numbers for french companies). A siren is 9 digits and a siret is 14. I could easily use standard widgets for the length but that was all.

So I created a custom widget starting with pbInput standard widget (workspace/default/web_widgets). I removed minLength, maxLength, type, min value, max value and and added an inputType property which value can be siren or siret (default: siren).

The template I used is derivated from pbInput:

    <!-- The custom widget template is defined here
       - You can use standard HTML tags and AngularJS built-in directives, scope and interpolation system
       - Custom widget properties defined on the right can be used as variables in a templates with properties.newProperty
       - Functions exposed in the controller can be used with ctrl.newFunction()
     -->
     
    <div ng-class="{
        'form-horizontal': properties.labelPosition === 'left' && !properties.labelHidden,
        'row': properties.labelPosition === 'top' && !properties.labelHidden || properties.labelHidden
        }"
>
        <div class="form-group">
            <label
                ng-if="!properties.labelHidden"
                ng-class="{ 'control-label--required': properties.required }"
                class="control-label col-xs-{{ !properties.labelHidden && properties.labelPosition === 'left' ? properties.labelWidth : 12 }}">
                {{ properties.label | uiTranslate }}
            </label>
            <div class="col-xs-{{ 12 - (!properties.labelHidden && properties.labelPosition === 'left' ? properties.labelWidth : 0) }}">
                <input
                    type="text"
                    class="form-control {{ properties.inputType }}"
                    placeholder="{{ properties.placeholder | uiTranslate }}"
                    ng-model="properties.value"
                    name="{{ ctrl.name }}"
                    sirensiret
                    ng-required="properties.required"
                    ng-readonly="properties.readOnly">
                <div ng-messages="$form[ctrl.name].$dirty && $form[ctrl.name].$error" ng-messages-include="forms-generic-errors.html" role="alert">
                    <div class="text-danger" ng-show="$form[ctrl.name].$error.integer">Cette valeur n'est pas un nombre valide</div>
                    <div class="text-danger" ng-show="$form[ctrl.name].$error.inputLength">La longueur d'
un {{ properties.inputType | uppercase }} est de {{ ctrl.inputLen }} chiffres.</div>
                    <div class="text-danger" ng-show="$form[ctrl.name].$error.sirenSiretKey">La clé de validation de ce {{ properties.inputType | uppercase }} est invalide</div>
                </div>
            </div>
        </div>
    </div>

You will note the following changes: I added some new messages to the ng-messages div and added a "sirensiret" directive. I also added a class to detect input type.

The controller is really simple:

    /**
     * The controller is a JavaScript function that augments the AngularJS scope and exposes functions that can be used in the custom widget template
     *
     * Custom widget properties defined on the right can be used as variables in a controller with $scope.properties
     * To use AngularJS standard services, you must declare them in the main function arguments.
     *
     * You can leave the controller empty if you do not need it.
     */

    function ($scope, $log, widgetNameFactory) {
   
        this.name = widgetNameFactory.getName($scope.properties.inputType);
        this.inputLen = $scope.properties.inputType == 'siren' ? 9 : 14;
   
        $log.error ("tiersSirenSiretInput controler is here to serve!");
   
        if (!$scope.properties.isBound('value')) {
            $log.error('the pbInput property named "value" need to be bound to a variable');
        }
    }

This is almost the standard controller. I just initialize the default length for error messages.

Then I added a custom asset checkSirenSiret.js that add some directives to bonitasoft.ui.extensions:

    var app = angular.module ('bonitasoft.ui.extensions', []);
   
    var INTEGER_REGEXP = /^\-?\d+$/;
    var SIREN_REGEXP = /^\d{9}$/;
    var SIRET_REGEXP = /^\d{14}$/;
   
    app.directive('sirensiret', function() {
      return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ctrl) {
               
                function validateSirenSiret(ngModelValue, ngViewValue) {
                       
                        // Default is OK.
                        ctrl.$setValidity('integer', true);
                        ctrl.$setValidity('inputLength', true);
                        ctrl.$setValidity('sirenSiretKey', true);
                       
                        // non empty => make additional controls!
                        if (!ctrl.$isEmpty(ngModelValue)) {
                                // Only numbers
                                ctrl.$setValidity('integer', INTEGER_REGEXP.test(ngModelValue));
                               
                                // Check length
                                var isSiren = angular.element(elm).hasClass('siren');
                                var lengthOk = false;
                               
                                if (isSiren) {
                                        lengthOk = SIREN_REGEXP.test(ngModelValue);
                                } else {
                                        lengthOk = SIRET_REGEXP.test(ngModelValue);
                                }
                               
                                ctrl.$setValidity('inputLength', lengthOk);
                               
                                // Check key
                                if (lengthOk) {
                                        var nb = ngModelValue.split('');
                                        var i;
                                        var check = 0;
                                        var x;
                                       
                                        for (i = 0; i < nb.length; i++) {
                                                x = nb[nb.length - i - 1] * ((i % 2) + 1);
                                                if (x < 10) {
                                                        check += x;
                                                } else {
                                                        check += Math.floor(x / 10) + (x % 10);
                                                }
                                        }
                                       
                                        if ((check % 10) != 0) {
                                                ctrl.$setValidity('sirenSiretKey', false);
                                        }
                                }
                        }
                       
                        // Return ngModelValue to display it
                        return ngModelValue;
                }
               
                ctrl.$parsers.push(validateSirenSiret);
        }
      };
    });

I use $parsers as it is a little bit more flexible than $validators (I would have to write multiple validators, I can use only one parser). Nothing special about this code: first check if the input is a number, then the length, then the Luhn key used to validate the input.

Hope this helps some others.

Comments

Submitted by g.lapierre on Fri, 04/29/2016 - 16:18

code blocks are not really working... I will have to put a .zip somewhere!

Submitted by Sean McP on Fri, 04/29/2016 - 16:55

Add it to projects?

Whenever I use code blocks I type it - works much better.

< code >

< /code >

(remove the spaces) and it formats wonderfully

regards
Seán

Submitted by g.lapierre on Fri, 04/29/2016 - 17:28

thanks for the tip!
I will give a look at projects.

No answers yet.
Notifications