Passing Any Attribute through a directive

192 views
Skip to first unread message

Jarrett Lusso

unread,
Mar 22, 2014, 4:52:49 PM3/22/14
to ang...@googlegroups.com
So I am trying to replicate a simple_form_for like alternative in angularjs to make it easy to build forms.

Here is an input example 

%div{data: {sf_field: "text", ng_model: "user.name", label: "Name", help: "What is your name?", placeholder: "enter your name", ng_required: "true"}}

Now my issue is I need to be able to take any parameter and pass it to the input. For instance the ng_required attribute. It would be silly to make my directive have a scope variable for each possible attribute because what happens if I add soemthing like angular-payments which has its own attributes you can drop on an input. Now I already through about maybe just using transclude and putting the input code in myself but I really was hoping to be able to build a dynamic form input directive that would manage, hints, errors / validations, etc

Note, this generates a bunch of HTML

<div class="input-wrapper text" data-ng-class="{'field-error': isError}">
  <label class="field-wrap">
    <div class="label ng-binding">
      Name
      <i class="help-icon fa fa-question-circle ng-scope" data-ng-show="help" data-tooltip="What is your name?"></i>
    </div>
    <div class="field">
      <!-- ngInclude: fieldTemplate --><div data-ng-include="fieldTemplate" class="ng-scope"><input data-ng-model="ngModel" placeholder="enter your name" type="text" class="ng-scope ng-pristine ng-valid" name="name">
</div>
    </div>
  </label>
</div>

Anyone with an suggestions on the best way to pass any attribute through would be greatly appreciated. I already thought about putting the "dynamic" attributes inside like a attrs parameter but I don't know how to make them work. That works for certain things but expressions/functions don't work.

Kamal

unread,
Mar 24, 2014, 1:18:24 AM3/24/14
to ang...@googlegroups.com
Hi Jarrett,

As i was not sure how you would provide the input to directive, i made the assumption that the input would be given inside the directive starting and ending tags and had made an plnkr http://plnkr.co/edit/wS18n477zSN4kt5CNGNx?p=preview which transcludes the element and creates the input based on the data, this is one way of doing it as i had also assumed there would be one directive with multiple lines of input seeing off https://github.com/plataformatec/simple_form. And in the case you want dynamic attrs with working expressons/functions please check the $parse service and $compile for dynamically binding the attr to the elements. An other thought which comes to my mind is providing the data as scope variable as input. Let me know assumptions clearly or share an example for which i can come to an better understanding and see what i can help you out with.

    <trans-from>

      %div{"data": {"sf_field": "text", "ng_model": "user.name", "label": "Name", "help": "What is your name?", "placeholder": "enter your name", "ng_required": "true"}}
      %div{"data": {"sf_field": "password", "ng_model": "user.pass", "label": "Password", "help": "What is your name?", "placeholder": "enter passcode", "ng_required": "true"}}
    </trans-from>

Regards,
Kamal

Jarrett Lusso

unread,
Mar 24, 2014, 2:52:58 AM3/24/14
to ang...@googlegroups.com
Hey Kamal,

To be completely honest I'm very new to angular and was just trying to put together a simple form alternative. sf_field was my directive and i actually didn't have a form directive. I'm not really sure the best way to go about it. My biggest concern was the fact that i wanted it to be able to accept any time of field that another angular module might make available. For instance, angular-payments creates payments-format and payments-validate. I didn't wanted to be able to just have those be appended on to the input if I put them on my directive. I wasn't sure if there was any simple way to just pass an attribute on the directive directly onto the input inside the template.

The way simple form works is you create a simple_form "form" and then you use their helpers to create inputs. You can then use any of the special attributes they provide on the input or any standard or custom ones. The biggest issue with angular is some of the attributes are strings or expressions and I don't know how I can pass them onto the input. Currently what I've done (which I'm not satisfied fully with) is setup the directive and just put the input code inside and have it transclude. This is slightly more code then my optimal solution, but its gives me the flexibility of assigning any attribute without any problem. 


--
You received this message because you are subscribed to a topic in the Google Groups "AngularJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/angular/OEKoyDIJpog/unsubscribe.
To unsubscribe from this group and all its topics, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/d/optout.

Kamalakar Gadireddy

unread,
Mar 24, 2014, 4:00:13 AM3/24/14
to ang...@googlegroups.com
Jarrett,
I understood a couple of things now "sf_field" is the directive how do you pass "%div{data: {sf_field: "text", ng_model: "user.name", label: "Name", help: "What is your name?", placeholder: "enter your name", ng_required: "true"}}" to id or are you translating you given syntax to some thing like the following (lets say using your own compiler of some sort)
<sf_field ng-model="user.name" label="Name" help="What is your name?" placeholder="enter your name" ng-required="true"/>
which would then generate the HTML out you have shared. In this case i dont see why you need an other directive, if its not the case then please let me know how and where your translating "%div{data: {sf_field: "text", ng_model: "user.name", label: "Name", help: "What is your name?", placeholder: "enter your name", ng_required: "true"}}" into sf_field directive is it in the browser or on the server it self.
--
Thanks,
Kamalakar Gadireddy

Jarrett Lusso

unread,
Mar 24, 2014, 10:55:05 AM3/24/14
to ang...@googlegroups.com
Sorry that I'm showing you haml and html. I write haml but the directive generates HTML obviously. I have two HTML files. I have a baseField HTML template which then transcludes the fieldTemplate.

This

%div{data: {sf_field: "text", ng_model: "user.name", label: "Name", help: "What is your name?", placeholder: "enter your name", ng_required: "true"}}

generates this

<div class="input-wrapper text" data-ng-class="{'field-error': isError}">
  <label class="field-wrap">
    <div class="label ng-binding">
      Name
      <i class="help-icon fa fa-question-circle ng-scope" data-ng-show="help" data-tooltip="What is your name?"></i>
    </div>
    <div class="field">
      <!-- ngInclude: fieldTemplate --><div data-ng-include="fieldTemplate" class="ng-scope"><input data-ng-model="ngModel" placeholder="enter your name" type="text" class="ng-scope ng-pristine ng-valid" name="name">
</div>
    </div>
  </label>
</div>

BaseFieldHTML
<div class="input-wrapper text" data-ng-class="{'field-error': isError}">
  <label class="field-wrap">
    <div class="label ng-binding">
      Name
      <i class="help-icon fa fa-question-circle ng-scope" data-ng-show="help" data-tooltip="What is your name?"></i>
    </div>
    <div class="field">
      <!-- ngInclude: fieldTemplate --><div data-ng-include="fieldTemplate" class="ng-scope">
</div>
    </div>
  </label>
</div>

FieldTemplateHTML
<input data-ng-model="ngModel" placeholder="enter your name" type="text" class="ng-scope ng-pristine ng-valid" name="name">

Kamalakar Gadireddy

unread,
Mar 24, 2014, 11:34:38 AM3/24/14
to ang...@googlegroups.com
If you can the best thing to do is generate the complete html in HAML it self for some reason you don't want to do it, then there are two options a head using $parse or $compile and "dynamic" attributes. I do see some concerns here not all the attributes are mapped to the input some are for label and hint also u need to figure out how to differentiate them. And one more thing is your are including the the field template which has the attributes & there values fixed, instead of this generating the input as need based on attributes would be better.


  demo.directive('passCompile', ['$compile', function($compile) {
    return {
      restrict: 'E',
      transclude: 'element',
      link: function(scope, element, attrs, model, transcludeElm) {
        var origElement = transcludeElm();
        var node = angular.element('<input/>');
        angular.forEach(attrs.$attr, function(name) {
          node.attr(name,origElement.attr(name))
        });
        element.after($compile(node)(scope));
      }
    };
  }]);


The above example shows a way to move the attributes off from the input to Using $compile service but i had transclude the element which makes it easy as if you have same attributes on both the directive and input they might cause some unexpected behavior. In case you want to use $parse service ngModel, ngBind and some other attributes wont work as the don't watch the attribute for changes which in this case you would be doing base on the parents attributes. Do let me know if this help you out.

Jarrett Lusso

unread,
Mar 24, 2014, 12:17:57 PM3/24/14
to ang...@googlegroups.com
The biggest problem is I don't know all the fields. Some modules create attributes like payments-format or payments-validate or w.e. I'm trying to get it so that if I do 

<div sf-field name="creditcard" label="My Label" help="Whats your card" ng-model="creditcard" payments-validate="card" />
 
that it will generate

<div >
<!-- all the wrapper generated html -->

<input type="text" name="creditcard" payments-validate="card" />

</div>

I know what fields on the directive don't need to be passed on to the input but I don't know what does. So for instance, label and help don't get passed to the input but anything else should. Is that possible?

Kamalakar Gadireddy

unread,
Mar 24, 2014, 1:15:40 PM3/24/14
to ang...@googlegroups.com
There you go you know what things that should not got in input so you can block them and rest u can pass them to the input. In the example i have shared you can see an for each loop

     angular.forEach(attrs.$attr, function(name) {
          // if condition when to copy the attribute, in your case you will check to see if its no label or hint or any other thing that should not got on to the input.
          node.attr(name,origElement.attr(name))
     });

Instead of the simple input node you can replace it with your html. You can play around with the example in plnkr.co which i had shared and see it would copy all the attributes of the directive to the input as explained above you need to block thing you don't need. Play around and let me know.

  demo.directive('simpleForm', ['$compile', function($compile) {
    var template = 
    '<div class="input-wrapper text" data-ng-class="{\'field-error\': isError}">' +
    '<label class="field-wrap">' +
      '<div class="label">' +
        '<i class="help-icon fa fa-question-circle" data-tooltip=""></i>' +
      '</div>' +
      '<div class="field"><input></div>' +
    '</div>' +
    '</label>' +
  '</div>';
    var notForInput = ['simple-form', 'label', 'help'];
    return {
      restrict: 'EA',
      transclude: 'element',
      link: function(scope, element, attrs, model, transcludeElm) {
        var origElement = transcludeElm();
        var node = angular.element(template);
        var input = node.find('input');
        var toolTip = $('i', node);
        angular.forEach(attrs.$attr, function(name) {
          if( notForInput.indexOf(name) === -1 ) {
            input.attr(name,origElement.attr(name))
          }
        });
        toolTip.before(origElement.attr('label'))
        toolTip.attr('data-tooltip', origElement.attr('help'));
        element.after($compile(node)(scope));
      }
    };
  }]);

Jarrett Lusso

unread,
Mar 24, 2014, 10:54:17 PM3/24/14
to ang...@googlegroups.com
I will check this out on my flight tomorrow. Hopefully this is the fix! :)

Jarrett Lusso

unread,
Mar 28, 2014, 3:00:13 AM3/28/14
to ang...@googlegroups.com
Hey,

So i was playing around with it all and the issue I'm having now is that I don't have access to the scope in the same way I believe.

watchExpression = [ctrl.$name, attrs.name, '$invalid'].join('.')
$scope.$parent.$watch watchExpression, (value) ->
     $scope.isError = value

I had that setup so that I could put a class on the fields when an error occurred. Not sure how to get this working now since I'm getting undefined when I try and get inputs on the form. ctrl was the form because I had require: '^form' in my directive

Kamalakar Gadireddy

unread,
Mar 28, 2014, 3:25:25 AM3/28/14
to ang...@googlegroups.com
Jarrett,

Can you just have a log to check and see if `$scope.$parent` is defined, this is the only thing i see which might be undefined. And my suggestion is do not create a new or isolated scope for your directives if thats case the case you can change the code to  `$scope.$watch watchExpression` and it should work.

Jarrett Lusso

unread,
Mar 28, 2014, 12:27:59 PM3/28/14
to ang...@googlegroups.com
I'll have to give that a shot. 


On Friday, March 28, 2014, Kamalakar Gadireddy <kama...@gmail.com> wrote:
Jarrett,

Jarrett Lusso

unread,
Mar 28, 2014, 12:38:13 PM3/28/14
to ang...@googlegroups.com
Yep, I guess I can't have an isolate scope cause then the form elements and stuff won't work.  
Reply all
Reply to author
Forward
0 new messages