Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transcluding Attributes in an AngularJS Directive

Tags:

angularjs

I was creating a select replacement directive to make it easy to style up selects according to the design without having to always right a bunch of markup (i.e. the directive does it for you!).

I didn't realize that attributes don't transclude to where you put ng-transclude and just go to the root element.

I have an example here: http://plnkr.co/edit/OLLntqMzbGCJS7g7h1j4?p=preview

You can see that it looks great... but there's one major flaw. The id and name attributes aren't being transferred. Which, ya know, without name, it doesn't post to the server (this form ties into an existing system, so AJAXing the model isn't an option).

For example, this is what I start with:

<select class="my-select irrelevant-class" name="reason" id="reason" data-anything="banana">
    <option value="">Reason for Contact...</option>
    <option>Banana</option>
    <option>Pizza</option>
    <option>The good stuff</option>
    <option>This is an example of a really, really, really, really, really, really long option item</option>
</select>

...this is what I want it to look like:

<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">
    <span class="faux-value">{{viewVal}}</span>
    <span class="icon-arrow-down"></span>
    <select ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude class="my-select irrelevant-class" name="reason" id="reason" data-anything="banana">
        <option value="">Reason for Contact...</option>
        <option>Banana</option>
        <option>Pizza</option>
        <option>The good stuff</option>
        <option>This is an example of a really, really, really, really, really, really long option item</option>
    </select>
</div>

...but this is what actually happens:

<div class="faux-select my-select irrelevant-class" ng-class="{ placeholder: default == viewVal, focus: obj.focus }" name="reason" id="reason" data-anything="banana">
    <span class="faux-value">{{viewVal}}</span>
    <span class="icon-arrow-down"></span>
    <select ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude>
        <option value="">Reason for Contact...</option>
        <option>Banana</option>
        <option>Pizza</option>
        <option>The good stuff</option>
        <option>This is an example of a really, really, really, really, really, really long option item</option>
    </select>
</div>

Specifically, the issue is that there's no name attribute on the select, so it doesn't actually send the data to the server.

Obviously, I can use a pre-compile phase to transfer the name and id attributes (that's what I am doing for now), but it would be nice if it would just automatically transfer all of the attributes so they can add any classes, arbitrary data, (ng-)required, (ng-)disabled attributes, etc, etc.

I tried getting transclude: 'element' to work, but then I couldn't the other attributes from the template onto it.

Note, I saw the post here: How can I transclude into an attribute?, but it looks like they just manually transfer the data, and I am aiming to get it to auto-transfer all the attributes.

like image 377
CWSpear Avatar asked Sep 14 '13 17:09

CWSpear


1 Answers

You could use the compile function to access the element's attributes and build the template.

app.directive('mySelect', [function () {
  return {
    transclude: true,
    scope: true,
    restrict: 'C',
    compile: function (element, attrs) {
        var template = '<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">' +
                       '<span class="faux-value">{{viewVal}}</span>' +
                       '<span class="icon-arrow-down entypo-down-open-mini"></span>' +
                       '<select id="' + attrs.id + '" name="' + attrs.name  + '" ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude>';
                       '</select>' +
                     '</div>';
        element.replaceWith(template);

        //return the postLink function 
        return function postLink(scope, elem, attrs) {
          var $select = elem.find('select');
          scope.default = scope.viewVal = elem.find('option')[0].innerHTML;

          scope.$watch('val', function(val) {
              if(val === '') scope.viewVal = scope.default;
              else scope.viewVal = val;
          });

          if(!scope.val) scope.val = $select.find('option[selected]').val() || '';
        }
     }
  };
}]);

The compile function is returning the postLink function, there are other ways to do this, you'll find more info here.

Here is a plunker

like image 88
Bertrand Avatar answered Sep 27 '22 17:09

Bertrand