How can I modify an AngularJS Bootstrap dropdown / select so that it does not use jQuery?

13,304

Solution 1

Idea in that Fiddle was nice but I found the implementation kinda messy. So what can one do? Well, write an alternative dropdown directive on top of ui-bootstrap, of course!

Hope you find this helpful. It should be really easy to use.

Usage

<dropdown is-button 
          ng-model="vm.item" 
          items="vm.items" 
          callback="vm.callback(item)">
</dropdown>

So you pass in the ng-model which holds the initial selection, if any. New value is set from the directive. In items you have collection of id-name pairs to choose from and a callback function if you need it. If you specify the is-button attribute, you'll get a button-stylish dropdown control.

Then the controller could look like the following.

Controller

// Controller
app.controller('Ctrl', function() {
  var vm = this;

  // items collection
  vm.items = [{
      id: 0,
      name: 'London'
    },{
      id: 1,
      name: 'Paris'
    },{
      id: 2,
      name: 'Milan'
    }];

  // current item
  vm.item = null; // vm.items[1];

  // directive callback function    
  vm.callback = function(item) {
    vm.fromCallback = 'User selected ' + angular.toJson(item);
  };
});

Logic for the dropdown directive is pretty simple, really.

Directive javaScript

// Dropdown directive 
app.directive('dropdown', function() {
  return {
    restrict: 'E',
    require: '^ngModel',
    scope: {
      ngModel: '=', // selection
      items: '=',   // items to select from
      callback: '&' // callback
    },
    link: function(scope, element, attrs) {
      element.on('click', function(event) {
        event.preventDefault();
      });

      scope.default = 'Please select item';
      scope.isButton = 'isButton' in attrs;

      // selection changed handler
      scope.select = function(item) {
        scope.ngModel = item;
        if (scope.callback) {
          scope.callback({ item: item });
        }
      };
    },
    templateUrl: 'dropdown-template.html'
  };
});

Directive HTML template

<div class="btn-group" dropdown>

  <!-- button style dropdown -->
  <button ng-if="isButton" 
          type="button" 
          class="btn btn-primary"
          ng-bind="ngModel.name || default">
  </button>
  <button ng-if="isButton" 
          type="button" 
          class="btn btn-primary dropdown-toggle" 
          dropdown-toggle>
    <span class="caret"></span>
  </button>

  <!-- no button, plz -->
  <a ng-if="!isButton" 
     class="dropdown-toggle"
     dropdown-toggle href
     ng-bind="ngModel.name || default">
  </a>
  <span ng-if="!isButton" class="caret"></span>

  <!-- dropdown items -->
  <ul class="dropdown-menu" role="menu"> 
    <li ng-repeat="item in items">
      <a href="#" 
         ng-bind="item.name" 
         ng-click="select(item)"></a>
    </li> 
  </ul>

</div>

Here's the look & feel of the initial implementation. In addition to the sample you provided, you'll get back the whole object and not just the id.

enter image description here


Related Plunker here http://plnkr.co/edit/bLWabx using angularjs 1.4.0-beta.3 and ui-bootstrap 0.12.0.

Solution 2

This solution only supports IE9+ if you need to support legacy browsers I can find another solution and update.

http://jsfiddle.net/3DS49/160/

scope.selectVal = function (item) {
    function selector(sel, htmlCont)
    {
        var elems = document.querySelectorAll(sel);
        Array.prototype.forEach.call(elems, function(el, i){
            el.innerHTML = htmlCont;
        });
    }
    switch (attrs.menuType) {
        case "button":
            selector('button.button-label', item.name);
            break;
        default:
            selector('a.dropdown-toggle', '<b class="caret"></b> ' + item.name);
            break;
    }
    scope.doSelect({
        selectedVal: item.id
    });
};

Solution 3

Edit: I was right, you don't need to use .html() in this example, and is a bad practice in general. Notice how I used the scope object to accomplish the same result: http://jsfiddle.net/3DS49/162/

For the minimal example you showed here, You could use angular.element instead of $ or jQuery. angular.element is a subset but it supports .html().

However, it is still a bad practice, since the content could be put in scope and the html content could be set as a direct binding in the button templates.

Edit: angular.element does not support selectors. You should use a native approach for that (getElementById and its friends). However, ensure you're not using jquery-specific selectors, like :first. E.g.:

var elements = document.querySelectorAll(".myclass");

//it is up to you to iterate over the elements and call angular.element(element).html(yourValueHere) on each element.

//NOTE: you can get document from $windowProvider.$get().document, or $window.document, depending on the place (you will use $window.document in this case).

Edit: check browser support here - there's no native angular way to support selectors.

For a practical implementation of the particular case, it is safe to see @ChrisFrank 's answer. However, remember you can wrap each of the elements iterated (e.g. in his answer) with angular.element function to get a jqLite (AngularJS's built in subset of jQuery - you don't need any additional library) object.

Share:
13,304

Related videos on Youtube

Alan2
Author by

Alan2

Updated on October 19, 2022

Comments

  • Alan2
    Alan2 over 1 year

    I saw a really good example of a AngularJS Bootstrap type of Select directive:

    http://jsfiddle.net/cojahmetov/3DS49/

    This meets most of my needs but it uses jQuery and we are not using that library.

    The jQuery used in this example is very minimal but I do not know how I could replace the element locators that look like this:

    Can anyone give me any pointers as to how I could replace this:

    switch (attrs.menuType) {
      case "button":
        $('button.button-label', element).html(item.name);
        break;
      default:
        $('a.dropdown-toggle', element).html('<b class="caret"></b> ' + item.name);
        break;
    }
    

    So that it would work without jQuery? Ideally I am hoping that someone knows enough to maybe come up with a version based on the version in the ui-bootstrap. Perhaps this could even be something that might be added to ui-bootstrap for others to use.

  • Betlista
    Betlista about 9 years
    It's not vorking well, when you create second dropdown: when value is selected in second, it is changed in first also... jsfiddle.net/6jj222w7 the same worked in original - jsfiddle.net/v33sbzgy
  • Luis Masuelli
    Luis Masuelli about 9 years
    It is safer to assign correctly the values to the scope, instead of raw-using .html calls. You also need to properly define the template with the desired bindings
  • Alan2
    Alan2 about 9 years
    Thank you for taking the time to come up with this very nice solution. I will try this out today and see if I have any questions.
  • Thibs
    Thibs almost 9 years
    This is a very elegant solution! However, when I select an item, my page loads to the root. How can we stop the default behaviour of 'ui.bootstrap.dropdown' to load page upon selection?
  • Thibs
    Thibs almost 9 years
    I found something that works. I passed the $event into the select method of the directive and then call preventDefault() on this event. Will wait to see your solution, not sure if I am doing the correct way. thank-you!
  • Mikko Viitala
    Mikko Viitala almost 9 years
    Yes, that does the trick. I have updated my answer/plunker to prevent default behaviour on element's click event. Or you can just remove the href="#" part of items but then you need to apply CSS to show "nicer" cursor.
  • Michael Markieta
    Michael Markieta almost 8 years
    If you are using the latest ui.bootstrap make sure you replace dropdown and dropdown-toggle with the new uib-dropdown and uib-dropdown-toggle