Conditionally add the "multiple" attribute to ui-select
Asked Answered
F

2

9

I'm trying to add the multiple attribute to a ui-select directive based on the value of a certain property using the ng-attr- directive. Unfortunately, that's not working for me. I've setup a plunker example to showcase what is happening.

Plunker Example

Footpace answered 7/8, 2015 at 7:44 Comment(0)
M
6

Edit

I finally got it after reading through the mentioned GitHub Issue in the Angular Repo.

You need to set up a directive with a higher priority and a terminal attribute set to true (which skips the compilation of all other directives, after the compilation of our directive). Then in the postLink function we will compile the whole element itself. But before that our own directive needs to be removed (infinite loop!).

Big shot outs to Add directives from directive in AngularJS

Directive Code

angular.module('app')
  .directive('multiSelectChecker', function ($compile) {
    return {
      restrict: 'A',
      replace: false, 
      terminal: true, //terminal means: compile this directive only
      priority: 50000, //priority means: the higher the priority, the "firster" the directive will be compiled
      compile: function compile(element, attrs) {
        element.removeAttr("multi-select-checker"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-multi-select-checker"); //also remove the same attribute with data- prefix in case users specify data-multi-select-checker in the html

        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {  },
          post: function postLink(scope, iElement, iAttrs, controller) { 
            if(scope.options.Multiple == true) {
              iElement[0].setAttribute('multiple',''); //set the multiple directive, doing it the JS way, not jqLite way.
            }
            $compile(iElement)(scope);
          }
        };
      }
    };
  });

HTML Code

  <ui-select ng-model="model.choice" multi-select-checker>
    <ui-select-match>{{$item.Title}}</ui-select-match>
    <ui-select-choices repeat="item.Id as item in options.SuggestedValues | filter: { Title: $select.search }">
      <div ng-bind="item.Title | highlight: $select.search"></div>
    </ui-select-choices>
  </ui-select>

Working Plnkr:

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


Original answer (also working, but with duplicate code)

I did the following:

  1. Created a wrapping directive called multi-select-checker
  2. In that directive check wether the options.Multiple is true or false
  3. Return two different template URLs for each case. Case 1): return single-select.tpl.html or Case 2): return mutli-select.tpl.html (which includes the 'multiple' directive.

Directive code:

app.directive('multiSelectChecker', function() {
return {
    template: '<ng-include src="getTemplateUrl()"/>',
    controller: function($scope) {
      $scope.getTemplateUrl = function() {
        if($scope.options.Multiple == true) {
          console.log("multi-select");
          return "multi-select.tpl.html"
        }
        else {
          console.log("single select");
          return "single-select.tpl.html"
        }
      }
    }
  }
})

Usage in HTML:

<body ng-controller="DemoCtrl">
  <multi-select-checker>
  </multi-select-checker>
</body>

Template 1: single select

<ui-select ng-model="model.choice">
    <ui-select-match>{{$item.Title}}</ui-select-match>
    <ui-select-choices repeat="item.Id as item in options.SuggestedValues | filter: { Title: $select.search }">
        <div ng-bind="item.Title | highlight: $select.search"></div>
    </ui-select-choices>
</ui-select>

Template 2: multi-select

<ui-select ng-model="model.choice" multiple>
    <ui-select-match>{{$item.Title}}</ui-select-match>
    <ui-select-choices repeat="item.Id as item in options.SuggestedValues | filter: { Title: $select.search }">
        <div ng-bind="item.Title | highlight: $select.search"></div>
    </ui-select-choices>
</ui-select>

As you can see the two templates only differ by one single directive: 'multiple'. Maybe there are better solutions.

I even can't understand, why the ng-attr-multiple approach isn't working.

In addition I have realized, that there are two seperate input fields being rendered via the ng-attr-multiple approach.

And the single selection case seems to be broken (by removing the multiple directive) - which was in your intial Plnkr as well.

Working Code

See the working Plnkr here: http://plnkr.co/edit/T9e5tcAkcQLsDV3plfEl?p=preview

Motorway answered 7/8, 2015 at 8:29 Comment(6)
This is a correct solution but I'd like to avoid duplicating code, which is why I tried using the ng-attr in the first place. Thanks anyway :)Footpace
You could transclude the children of your ui-select -> which decreases your repeated code.Motorway
I have investigated further: it seems that the ng-attr-multiple doesn't evaluate (it's content is still "{{options.Multiple}}". I have checked the ui-select code, which checks if the multipe attribute is set - which it is true both for ng-attr-multiple="{{options.Multiple}}" and even setting ng-attr-multiple="" will evaluate to true (as it just checks if the multiple attribute is defined LoC https://github.com/angular-ui/ui-select/blob/master/src/uiSelectDirective.js#L21 - thus being rendered as a multi-select.Motorway
Even some more info on the exact thing what you are trying to achieve: github.com/angular/angular.js/issues/5524Motorway
With regards to your final solution, if anyone wants to pass a variable from the controller (without using $scope) you need to create an isolate scope and then compile the $parent. Thank you!Legislation
The ui-select itself is using 2 separate templates too. check the code. return theme + (angular.isDefined(tAttrs.multiple) ? '/select-multiple.tpl.html' : '/select.tpl.html');Escape
F
3

Is this what you want to achieve:

<body ng-controller="DemoCtrl">
    This works perfectly well:
    <ui-select ng-model="model.choice" multiple>
        <ui-select-match>{{$item.Title}}</ui-select-match>
        <ui-select-choices repeat="item.Id as item in options.SuggestedValues | filter: { Title: $select.search }">
            <div ng-bind="item.Title | highlight: $select.search"></div>
        </ui-select-choices>
    </ui-select>
    <br />
    <br />
    This does not work:
    <ui-select ng-model="model.choice2" multiple="{{options.Multiple}}">
        <ui-select-match>{{$item.Title}}</ui-select-match>
        <ui-select-choices repeat="item.Id as item in options.SuggestedValues | filter: { Title: $select.search }">
            <div ng-bind="item.Title | highlight: $select.search"></div>
        </ui-select-choices>
    </ui-select>
  </body>
Fractional answered 7/8, 2015 at 7:58 Comment(4)
Yes but it does not work. Because whatever the value of options.Multiple is, it will always be assumed to be a multiple choice select.Footpace
Yes, I see now. It seems that it is an open issue in angular please check github.com/angular/angular.js/issues/7714 and Narretz's answer: "Binding to multiple is currently not supported. It was forbidden because it caused issues with the select directive."Fractional
Yes, I was using the ng-attr directive to get it to work but it failed. It did add the multiple attribute, but it screwed up the ui-select-match nested directive: apparently, it got compiled before the multiple attribute was added and consequently it behaved like it does in the context of a single choice ui-select. Any ideas how to get around this?Footpace
I am sorry man, I don't have an idea how to go round this issue. It is not supported by framework.Fractional

© 2022 - 2024 — McMap. All rights reserved.