Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 718
Alan2 Avatar asked Jan 20 '15 16:01

Alan2


3 Answers

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.
like image 125
Mikko Viitala Avatar answered Nov 18 '22 07:11

Mikko Viitala


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
    });
};
like image 36
Chris Frank Avatar answered Nov 18 '22 09:11

Chris Frank


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.

like image 34
Luis Masuelli Avatar answered Nov 18 '22 07:11

Luis Masuelli