Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS : Transclude a single input element into a directive template without using a container

I would like to create a wrapper for my input field with an input-help tool tip inside of it.

I am using angular 1.0.7, if it's significant.

I'm using transclude: true, along with isolate scope in order to allow errors at several different fields simultaneously, and still maintain the ng-model reference to the external $scope.

The Problem:

when I use this directive on the input element, the input element doesn't clone('Transclude') into the directive template.

As a result of that I am getting an empty div element at the DOM, with an ng-transclude attribute.

plunk: http://plnkr.co/edit/vFB9ih6x2NBmwhAes3Qh?p=preview

code:

<input data-my-validate-input data-value-required="true" type="password" class="loginItem" placeholder="Password" name="password" data-ng-model="formData.password" data-display-name="Password">

However when I wrap this input element in a span or div, the child input element is transcended just fine, but then I don't get the a reference to the correct external ng-model(ctrl) at the directive.

<span data-my-validate-input data-value-required="true" data-display-name="Password">
      <input type="password" class="loginItem" placeholder="Password" name="password" data-ng-model="formData.password" >    
</span>

Full code(the logic inside the link function is not relevant to the problem, but I preferred to post my full code)

directive('myValidateInput', function() {
    return {
        require: 'ngModel',
          restrict: 'A',
          transclude: true,
          scope: {
            displayName: '@',
            valueRequired: '@',
            maxLength: '@',
            minLength: '@',
            minLetters: '@',
            minNumbers: '@'
          },
          template: '<div class="validationContainer">\
                      <div ng-transclude></div>\
                      <div class="input-help">\
                        <h4>{{fieldErrorDisplay}}</h4>\
                        <ul>\
                          <li data-ng-repeat="rule in requirementSpec" ng-class="rule.class">\
                              {{rule.msg}}\
                          </li>\
                        </ul>\
                      </div>\
                    </div>',
         replace: true,
         link: function(scope, elm, attrs, ctrl) {
                 var validator = function(viewValue){
                          if(scope.valueRequired && viewValue.length == 0 && (!scope.maxLength && !scope.minLength && !scope.minLetters && !scope.minNumbers)){
                    scope.valid = false;  
                    scope.fieldErrorDisplay = scope.fieldName + ' is required';
                  }
                  else{
                        scope.fieldErrorDisplay = scope.fieldName + ' must meet the following requirements: ';
                        scope.requirementSpec = [];
                        if(scope.minLength){
                          var itemValidity = viewValue.length >= scope.minLength;
                          scope.valid = !itemValidity ? false : scope.valid;
                          var item = {
                            'msg' : 'Must be at least ' + scope.minLength + ' characters long',
                            'class' : itemValidity ? 'valid' : undefined
                          };
                          scope.requirementSpec[nameStr].push(item);
                        }
                        else if(scope.valueRequired){
                          var itemValidity = viewValue && viewValue.length >= 1;
                          scope.valid = !itemValidity ? false : scope.valid;
                          var item = {
                            'msg' : 'This field must be filled',
                            'class' : itemValidity ? 'valid' : undefined
                          };
                          scope.requirementSpec[nameStr].push(item);
                        }
                        if(scope.maxLength){
                          var itemValidity = viewValue.length <= scope.maxLength;
                          scope.valid = !itemValidity ? false : scope.valid;
                          var item = {
                            'msg' : 'Must be ' + scope.maxLength + ' characters long at most  ',
                            'class' : itemValidity ? 'valid' : undefined
                          };
                          scope.requirementSpec[nameStr].push(item);
                        }
                        if(scope.minLetters){
                          var itemValidity = (viewValue && /[A-z]/.test(viewValue));
                          scope.valid = !itemValidity ? false : scope.valid;
                          var item = {
                            'msg' : 'Must contain at least ' + scope.minLetters + ' letters',
                            'class' : itemValidity ? 'valid' : undefined
                          };
                          scope.requirementSpec[nameStr].push(item);
                        }
                        if(attrs.minNumbers){
                          var itemValidity = (viewValue && /\d/.test(viewValue));
                          scope.valid = !itemValidity ? false : scope.valid;
                          var item = {
                            'msg' : 'Must contain at least' + attrs.minNumbers + ' numbers',
                            'class' : itemValidity ? 'valid' : undefined
                          };
                          scope.requirementSpec[nameStr].push(item);
                        }
                  }

                  if(scope.valid) {
                      ctrl.$setValidity(nameStr, true);
                      return viewValue;
                  } else {
                      ctrl.$setValidity(nameStr, false);                    
                      return undefined;
                  }
             }

             scope.requirementSpec = {};

             ctrl.$parsers.unshift(function(viewValue) {
               return validator(viewValue);
             });
             ctrl.$formatters.unshift(function(viewValue) {
               // var before = scope.$eval(attrs.validateBefore);
               if(viewValue && viewValue != "" && viewValue.length > 0)
                 return validator(viewValue);

             });


        });
    }
});
like image 515
Oleg Belousov Avatar asked Oct 07 '13 08:10

Oleg Belousov


People also ask

What is transclude in AngularJS directive?

The ng-transclude directive facilitates AngularJS to capture everything that is put inside the directive in the markup and use it somewhere in the directive's template.

How to write a directive in AngularJS?

Create New Directives In addition to all the built-in AngularJS directives, you can create your own directives. New directives are created by using the . directive function. To invoke the new directive, make an HTML element with the same tag name as the new directive.

What is attrs in AngularJS?

Using attrs you are able to access the attributes defined in your html tag like <fm-rating ng-model="$parent.restaurant.price" symbol="$" readonly="true"> So in this case you will have access to the symbol and readonly attributes.

What is restrict option in directive?

Note: When you create a directive, it is restricted to attribute and elements only by default. In order to create directives that are triggered by class name, you need to use the restrict option. The restrict option is typically set to: 'A' - only matches attribute name. 'E' - only matches element name.


1 Answers

The solution: $transclude only takes the compiled content of the element, thus not the element it self.

Obviously I lack the understanding of this significant detail in my implementation.

like image 131
Oleg Belousov Avatar answered Oct 13 '22 11:10

Oleg Belousov