arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/modules/js/chips/chips.js
www/lib/angular-material/modules/js/chips/chips.jsRaw
   1/*!
   2 * Angular Material Design
   3 * https://github.com/angular/material
   4 * @license MIT
   5 * v1.1.1
   6 */
   7(function( window, angular, undefined ){
   8"use strict";
   9
  10/**
  11 * @ngdoc module
  12 * @name material.components.chips
  13 */
  14/*
  15 * @see js folder for chips implementation
  16 */
  17angular.module('material.components.chips', [
  18  'material.core',
  19  'material.components.autocomplete'
  20]);
  21
  22
  23MdChipCtrl.$inject = ["$scope", "$element", "$mdConstant", "$timeout", "$mdUtil"];angular
  24  .module('material.components.chips')
  25  .controller('MdChipCtrl', MdChipCtrl);
  26
  27/**
  28 * Controller for the MdChip component. Responsible for handling keyboard
  29 * events and editting the chip if needed.
  30 *
  31 * @param $scope
  32 * @param $element
  33 * @param $mdConstant
  34 * @param $timeout
  35 * @param $mdUtil
  36 * @constructor
  37 */
  38function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {
  39  /**
  40   * @type {$scope}
  41   */
  42  this.$scope = $scope;
  43
  44  /**
  45   * @type {$element}
  46   */
  47  this.$element = $element;
  48
  49  /**
  50   * @type {$mdConstant}
  51   */
  52  this.$mdConstant = $mdConstant;
  53
  54  /**
  55   * @type {$timeout}
  56   */
  57  this.$timeout = $timeout;
  58
  59  /**
  60   * @type {$mdUtil}
  61   */
  62  this.$mdUtil = $mdUtil;
  63
  64  /**
  65   * @type {boolean}
  66   */
  67  this.isEditting = false;
  68
  69  /**
  70   * @type {MdChipsCtrl}
  71   */
  72  this.parentController = undefined;
  73
  74  /**
  75   * @type {boolean}
  76   */
  77  this.enableChipEdit = false;
  78}
  79
  80
  81/**
  82 * @param {MdChipsCtrl} controller
  83 */
  84MdChipCtrl.prototype.init = function(controller) {
  85  this.parentController = controller;
  86  this.enableChipEdit = this.parentController.enableChipEdit;
  87
  88  if (this.enableChipEdit) {
  89    this.$element.on('keydown', this.chipKeyDown.bind(this));
  90    this.$element.on('mousedown', this.chipMouseDown.bind(this));
  91    this.getChipContent().addClass('_md-chip-content-edit-is-enabled');
  92  }
  93};
  94
  95
  96/**
  97 * @return {Object}
  98 */
  99MdChipCtrl.prototype.getChipContent = function() {
 100  var chipContents = this.$element[0].getElementsByClassName('md-chip-content');
 101  return angular.element(chipContents[0]);
 102};
 103
 104
 105/**
 106 * @return {Object}
 107 */
 108MdChipCtrl.prototype.getContentElement = function() {
 109  return angular.element(this.getChipContent().children()[0]);
 110};
 111
 112
 113/**
 114 * @return {number}
 115 */
 116MdChipCtrl.prototype.getChipIndex = function() {
 117  return parseInt(this.$element.attr('index'));
 118};
 119
 120
 121/**
 122 * Presents an input element to edit the contents of the chip.
 123 */
 124MdChipCtrl.prototype.goOutOfEditMode = function() {
 125  if (!this.isEditting) return;
 126
 127  this.isEditting = false;
 128  this.$element.removeClass('_md-chip-editing');
 129  this.getChipContent()[0].contentEditable = 'false';
 130  var chipIndex = this.getChipIndex();
 131
 132  var content = this.getContentElement().text();
 133  if (content) {
 134    this.parentController.updateChipContents(
 135        chipIndex,
 136        this.getContentElement().text()
 137    );
 138
 139    this.$mdUtil.nextTick(function() {
 140      if (this.parentController.selectedChip === chipIndex) {
 141        this.parentController.focusChip(chipIndex);
 142      }
 143    }.bind(this));
 144  } else {
 145    this.parentController.removeChipAndFocusInput(chipIndex);
 146  }
 147};
 148
 149
 150/**
 151 * Given an HTML element. Selects contents of it.
 152 * @param node
 153 */
 154MdChipCtrl.prototype.selectNodeContents = function(node) {
 155  var range, selection;
 156  if (document.body.createTextRange) {
 157    range = document.body.createTextRange();
 158    range.moveToElementText(node);
 159    range.select();
 160  } else if (window.getSelection) {
 161    selection = window.getSelection();
 162    range = document.createRange();
 163    range.selectNodeContents(node);
 164    selection.removeAllRanges();
 165    selection.addRange(range);
 166  }
 167};
 168
 169
 170/**
 171 * Presents an input element to edit the contents of the chip.
 172 */
 173MdChipCtrl.prototype.goInEditMode = function() {
 174  this.isEditting = true;
 175  this.$element.addClass('_md-chip-editing');
 176  this.getChipContent()[0].contentEditable = 'true';
 177  this.getChipContent().on('blur', function() {
 178    this.goOutOfEditMode();
 179  }.bind(this));
 180
 181  this.selectNodeContents(this.getChipContent()[0]);
 182};
 183
 184
 185/**
 186 * Handles the keydown event on the chip element. If enable-chip-edit attribute is
 187 * set to true, space or enter keys can trigger going into edit mode. Enter can also
 188 * trigger submitting if the chip is already being edited.
 189 * @param event
 190 */
 191MdChipCtrl.prototype.chipKeyDown = function(event) {
 192  if (!this.isEditting &&
 193    (event.keyCode === this.$mdConstant.KEY_CODE.ENTER ||
 194    event.keyCode === this.$mdConstant.KEY_CODE.SPACE)) {
 195    event.preventDefault();
 196    this.goInEditMode();
 197  } else if (this.isEditting &&
 198    event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {
 199    event.preventDefault();
 200    this.goOutOfEditMode();
 201  }
 202};
 203
 204
 205/**
 206 * Handles the double click event
 207 */
 208MdChipCtrl.prototype.chipMouseDown = function() {
 209  if(this.getChipIndex() == this.parentController.selectedChip &&
 210    this.enableChipEdit &&
 211    !this.isEditting) {
 212    this.goInEditMode();
 213  }
 214};
 215
 216
 217MdChip.$inject = ["$mdTheming", "$mdUtil"];angular
 218    .module('material.components.chips')
 219    .directive('mdChip', MdChip);
 220
 221/**
 222 * @ngdoc directive
 223 * @name mdChip
 224 * @module material.components.chips
 225 *
 226 * @description
 227 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
 228 * chips.
 229 *
 230 *
 231 * @usage
 232 * <hljs lang="html">
 233 *   <md-chip>{{$chip}}</md-chip>
 234 * </hljs>
 235 *
 236 */
 237
 238// This hint text is hidden within a chip but used by screen readers to
 239// inform the user how they can interact with a chip.
 240var DELETE_HINT_TEMPLATE = '\
 241    <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\
 242      {{$mdChipsCtrl.deleteHint}}\
 243    </span>';
 244
 245/**
 246 * MDChip Directive Definition
 247 *
 248 * @param $mdTheming
 249 * @param $mdUtil
 250 * ngInject
 251 */
 252function MdChip($mdTheming, $mdUtil) {
 253  var hintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);
 254
 255  return {
 256    restrict: 'E',
 257    require: ['^?mdChips', 'mdChip'],
 258    compile:  compile,
 259    controller: 'MdChipCtrl'
 260  };
 261
 262  function compile(element, attr) {
 263    // Append the delete template
 264    element.append($mdUtil.processTemplate(hintTemplate));
 265
 266    return function postLink(scope, element, attr, ctrls) {
 267      var chipsController = ctrls.shift();
 268      var chipController  = ctrls.shift();
 269      $mdTheming(element);
 270
 271      if (chipsController) {
 272        chipController.init(chipsController);
 273
 274        angular
 275          .element(element[0]
 276          .querySelector('.md-chip-content'))
 277          .on('blur', function () {
 278            chipsController.resetSelectedChip();
 279            chipsController.$scope.$applyAsync();
 280          });
 281      }
 282    };
 283  }
 284}
 285
 286
 287MdChipRemove.$inject = ["$timeout"];angular
 288    .module('material.components.chips')
 289    .directive('mdChipRemove', MdChipRemove);
 290
 291/**
 292 * @ngdoc directive
 293 * @name mdChipRemove
 294 * @restrict A
 295 * @module material.components.chips
 296 *
 297 * @description
 298 * Designates an element to be used as the delete button for a chip. <br/>
 299 * This element is passed as a child of the `md-chips` element.
 300 *
 301 * The designated button will be just appended to the chip and removes the given chip on click.<br/>
 302 * By default the button is not being styled by the `md-chips` component.
 303 *
 304 * @usage
 305 * <hljs lang="html">
 306 *   <md-chips>
 307 *     <button md-chip-remove="">
 308 *       <md-icon md-svg-icon="md-close"></md-icon>
 309 *     </button>
 310 *   </md-chips>
 311 * </hljs>
 312 */
 313
 314
 315/**
 316 * MdChipRemove Directive Definition.
 317 * 
 318 * @param $timeout
 319 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
 320 * @constructor
 321 */
 322function MdChipRemove ($timeout) {
 323  return {
 324    restrict: 'A',
 325    require: '^mdChips',
 326    scope: false,
 327    link: postLink
 328  };
 329
 330  function postLink(scope, element, attr, ctrl) {
 331    element.on('click', function(event) {
 332      scope.$apply(function() {
 333        ctrl.removeChip(scope.$$replacedScope.$index);
 334      });
 335    });
 336
 337    // Child elements aren't available until after a $timeout tick as they are hidden by an
 338    // `ng-if`. see http://goo.gl/zIWfuw
 339    $timeout(function() {
 340      element.attr({ tabindex: -1, 'aria-hidden': true });
 341      element.find('button').attr('tabindex', '-1');
 342    });
 343  }
 344}
 345
 346
 347MdChipTransclude.$inject = ["$compile"];angular
 348    .module('material.components.chips')
 349    .directive('mdChipTransclude', MdChipTransclude);
 350
 351function MdChipTransclude ($compile) {
 352  return {
 353    restrict: 'EA',
 354    terminal: true,
 355    link: link,
 356    scope: false
 357  };
 358  function link (scope, element, attr) {
 359    var ctrl = scope.$parent.$mdChipsCtrl,
 360        newScope = ctrl.parent.$new(false, ctrl.parent);
 361    newScope.$$replacedScope = scope;
 362    newScope.$chip = scope.$chip;
 363    newScope.$index = scope.$index;
 364    newScope.$mdChipsCtrl = ctrl;
 365
 366    var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);
 367
 368    element.html(newHtml);
 369    $compile(element.contents())(newScope);
 370  }
 371}
 372
 373
 374MdChipsCtrl.$inject = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];angular
 375    .module('material.components.chips')
 376    .controller('MdChipsCtrl', MdChipsCtrl);
 377
 378/**
 379 * Controller for the MdChips component. Responsible for adding to and
 380 * removing from the list of chips, marking chips as selected, and binding to
 381 * the models of various input components.
 382 *
 383 * @param $scope
 384 * @param $attrs
 385 * @param $mdConstant
 386 * @param $log
 387 * @param $element
 388 * @param $timeout
 389 * @param $mdUtil
 390 * @constructor
 391 */
 392function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {
 393  /** @type {$timeout} **/
 394  this.$timeout = $timeout;
 395
 396  /** @type {Object} */
 397  this.$mdConstant = $mdConstant;
 398
 399  /** @type {angular.$scope} */
 400  this.$scope = $scope;
 401
 402  /** @type {angular.$scope} */
 403  this.parent = $scope.$parent;
 404
 405  /** @type {$log} */
 406  this.$log = $log;
 407
 408  /** @type {$element} */
 409  this.$element = $element;
 410
 411  /** @type {angular.NgModelController} */
 412  this.ngModelCtrl = null;
 413
 414  /** @type {angular.NgModelController} */
 415  this.userInputNgModelCtrl = null;
 416
 417  /** @type {MdAutocompleteCtrl} */
 418  this.autocompleteCtrl = null;
 419
 420  /** @type {Element} */
 421  this.userInputElement = null;
 422
 423  /** @type {Array.<Object>} */
 424  this.items = [];
 425
 426  /** @type {number} */
 427  this.selectedChip = -1;
 428
 429  /** @type {string} */
 430  this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);
 431
 432  /** @type {string} */
 433  this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);
 434
 435  /**
 436   * Hidden hint text for how to delete a chip. Used to give context to screen readers.
 437   * @type {string}
 438   */
 439  this.deleteHint = 'Press delete to remove this chip.';
 440
 441  /**
 442   * Hidden label for the delete button. Used to give context to screen readers.
 443   * @type {string}
 444   */
 445  this.deleteButtonLabel = 'Remove';
 446
 447  /**
 448   * Model used by the input element.
 449   * @type {string}
 450   */
 451  this.chipBuffer = '';
 452
 453  /**
 454   * Whether to use the transformChip expression to transform the chip buffer
 455   * before appending it to the list.
 456   * @type {boolean}
 457   */
 458  this.useTransformChip = false;
 459
 460  /**
 461   * Whether to use the onAdd expression to notify of chip additions.
 462   * @type {boolean}
 463   */
 464  this.useOnAdd = false;
 465
 466  /**
 467   * Whether to use the onRemove expression to notify of chip removals.
 468   * @type {boolean}
 469   */
 470  this.useOnRemove = false;
 471
 472  /**
 473   * Whether to use the onSelect expression to notify the component's user
 474   * after selecting a chip from the list.
 475   * @type {boolean}
 476   */
 477}
 478
 479/**
 480 * Handles the keydown event on the input element: by default <enter> appends
 481 * the buffer to the chip list, while backspace removes the last chip in the
 482 * list if the current buffer is empty.
 483 * @param event
 484 */
 485MdChipsCtrl.prototype.inputKeydown = function(event) {
 486  var chipBuffer = this.getChipBuffer();
 487
 488  // If we have an autocomplete, and it handled the event, we have nothing to do
 489  if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {
 490    return;
 491  }
 492
 493  if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {
 494    // Only select and focus the previous chip, if the current caret position of the
 495    // input element is at the beginning.
 496    if (this.getCursorPosition(event.target) !== 0) {
 497      return;
 498    }
 499
 500    event.preventDefault();
 501    event.stopPropagation();
 502
 503    if (this.items.length) {
 504      this.selectAndFocusChipSafe(this.items.length - 1);
 505    }
 506
 507    return;
 508  }
 509
 510  // By default <enter> appends the buffer to the chip list.
 511  if (!this.separatorKeys || this.separatorKeys.length < 1) {
 512    this.separatorKeys = [this.$mdConstant.KEY_CODE.ENTER];
 513  }
 514
 515  // Support additional separator key codes in an array of `md-separator-keys`.
 516  if (this.separatorKeys.indexOf(event.keyCode) !== -1) {
 517    if ((this.autocompleteCtrl && this.requireMatch) || !chipBuffer) return;
 518    event.preventDefault();
 519
 520    // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
 521    if (this.hasMaxChipsReached()) return;
 522
 523    this.appendChip(chipBuffer.trim());
 524    this.resetChipBuffer();
 525  }
 526};
 527
 528/**
 529 * Returns the cursor position of the specified input element.
 530 * @param element HTMLInputElement
 531 * @returns {Number} Cursor Position of the input.
 532 */
 533MdChipsCtrl.prototype.getCursorPosition = function(element) {
 534  /*
 535   * Figure out whether the current input for the chips buffer is valid for using
 536   * the selectionStart / end property to retrieve the cursor position.
 537   * Some browsers do not allow the use of those attributes, on different input types.
 538   */
 539  try {
 540    if (element.selectionStart === element.selectionEnd) {
 541      return element.selectionStart;
 542    }
 543  } catch (e) {
 544    if (!element.value) {
 545      return 0;
 546    }
 547  }
 548};
 549
 550
 551/**
 552 * Updates the content of the chip at given index
 553 * @param chipIndex
 554 * @param chipContents
 555 */
 556MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
 557  if(chipIndex >= 0 && chipIndex < this.items.length) {
 558    this.items[chipIndex] = chipContents;
 559    this.ngModelCtrl.$setDirty();
 560  }
 561};
 562
 563
 564/**
 565 * Returns true if a chip is currently being edited. False otherwise.
 566 * @return {boolean}
 567 */
 568MdChipsCtrl.prototype.isEditingChip = function() {
 569  return !!this.$element[0].getElementsByClassName('_md-chip-editing').length;
 570};
 571
 572
 573MdChipsCtrl.prototype.isRemovable = function() {
 574  // Return false if we have static chips
 575  if (!this.ngModelCtrl) {
 576    return false;
 577  }
 578
 579  return this.readonly ? this.removable :
 580         angular.isDefined(this.removable) ? this.removable : true;
 581};
 582
 583/**
 584 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
 585 * keys switch which chips is active
 586 * @param event
 587 */
 588MdChipsCtrl.prototype.chipKeydown = function (event) {
 589  if (this.getChipBuffer()) return;
 590  if (this.isEditingChip()) return;
 591  
 592  switch (event.keyCode) {
 593    case this.$mdConstant.KEY_CODE.BACKSPACE:
 594    case this.$mdConstant.KEY_CODE.DELETE:
 595      if (this.selectedChip < 0) return;
 596      event.preventDefault();
 597      // Cancel the delete action only after the event cancel. Otherwise the page will go back.
 598      if (!this.isRemovable()) return;
 599      this.removeAndSelectAdjacentChip(this.selectedChip);
 600      break;
 601    case this.$mdConstant.KEY_CODE.LEFT_ARROW:
 602      event.preventDefault();
 603      if (this.selectedChip < 0) this.selectedChip = this.items.length;
 604      if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
 605      break;
 606    case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
 607      event.preventDefault();
 608      this.selectAndFocusChipSafe(this.selectedChip + 1);
 609      break;
 610    case this.$mdConstant.KEY_CODE.ESCAPE:
 611    case this.$mdConstant.KEY_CODE.TAB:
 612      if (this.selectedChip < 0) return;
 613      event.preventDefault();
 614      this.onFocus();
 615      break;
 616  }
 617};
 618
 619/**
 620 * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
 621 * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
 622 * always.
 623 */
 624MdChipsCtrl.prototype.getPlaceholder = function() {
 625  // Allow `secondary-placeholder` to be blank.
 626  var useSecondary = (this.items && this.items.length &&
 627      (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
 628  return useSecondary ? this.secondaryPlaceholder : this.placeholder;
 629};
 630
 631/**
 632 * Removes chip at {@code index} and selects the adjacent chip.
 633 * @param index
 634 */
 635MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
 636  var selIndex = this.getAdjacentChipIndex(index);
 637  this.removeChip(index);
 638  this.$timeout(angular.bind(this, function () {
 639      this.selectAndFocusChipSafe(selIndex);
 640  }));
 641};
 642
 643/**
 644 * Sets the selected chip index to -1.
 645 */
 646MdChipsCtrl.prototype.resetSelectedChip = function() {
 647  this.selectedChip = -1;
 648};
 649
 650/**
 651 * Gets the index of an adjacent chip to select after deletion. Adjacency is
 652 * determined as the next chip in the list, unless the target chip is the
 653 * last in the list, then it is the chip immediately preceding the target. If
 654 * there is only one item in the list, -1 is returned (select none).
 655 * The number returned is the index to select AFTER the target has been
 656 * removed.
 657 * If the current chip is not selected, then -1 is returned to select none.
 658 */
 659MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
 660  var len = this.items.length - 1;
 661  return (len == 0) ? -1 :
 662      (index == len) ? index -1 : index;
 663};
 664
 665/**
 666 * Append the contents of the buffer to the chip list. This method will first
 667 * call out to the md-transform-chip method, if provided.
 668 *
 669 * @param newChip
 670 */
 671MdChipsCtrl.prototype.appendChip = function(newChip) {
 672  if (this.useTransformChip && this.transformChip) {
 673    var transformedChip = this.transformChip({'$chip': newChip});
 674
 675    // Check to make sure the chip is defined before assigning it, otherwise, we'll just assume
 676    // they want the string version.
 677    if (angular.isDefined(transformedChip)) {
 678      newChip = transformedChip;
 679    }
 680  }
 681
 682  // If items contains an identical object to newChip, do not append
 683  if (angular.isObject(newChip)){
 684    var identical = this.items.some(function(item){
 685      return angular.equals(newChip, item);
 686    });
 687    if (identical) return;
 688  }
 689
 690  // Check for a null (but not undefined), or existing chip and cancel appending
 691  if (newChip == null || this.items.indexOf(newChip) + 1) return;
 692
 693  // Append the new chip onto our list
 694  var index = this.items.push(newChip);
 695
 696  // Update model validation
 697  this.ngModelCtrl.$setDirty();
 698  this.validateModel();
 699
 700  // If they provide the md-on-add attribute, notify them of the chip addition
 701  if (this.useOnAdd && this.onAdd) {
 702    this.onAdd({ '$chip': newChip, '$index': index });
 703  }
 704};
 705
 706/**
 707 * Sets whether to use the md-transform-chip expression. This expression is
 708 * bound to scope and controller in {@code MdChipsDirective} as
 709 * {@code transformChip}. Due to the nature of directive scope bindings, the
 710 * controller cannot know on its own/from the scope whether an expression was
 711 * actually provided.
 712 */
 713MdChipsCtrl.prototype.useTransformChipExpression = function() {
 714  this.useTransformChip = true;
 715};
 716
 717/**
 718 * Sets whether to use the md-on-add expression. This expression is
 719 * bound to scope and controller in {@code MdChipsDirective} as
 720 * {@code onAdd}. Due to the nature of directive scope bindings, the
 721 * controller cannot know on its own/from the scope whether an expression was
 722 * actually provided.
 723 */
 724MdChipsCtrl.prototype.useOnAddExpression = function() {
 725  this.useOnAdd = true;
 726};
 727
 728/**
 729 * Sets whether to use the md-on-remove expression. This expression is
 730 * bound to scope and controller in {@code MdChipsDirective} as
 731 * {@code onRemove}. Due to the nature of directive scope bindings, the
 732 * controller cannot know on its own/from the scope whether an expression was
 733 * actually provided.
 734 */
 735MdChipsCtrl.prototype.useOnRemoveExpression = function() {
 736  this.useOnRemove = true;
 737};
 738
 739/*
 740 * Sets whether to use the md-on-select expression. This expression is
 741 * bound to scope and controller in {@code MdChipsDirective} as
 742 * {@code onSelect}. Due to the nature of directive scope bindings, the
 743 * controller cannot know on its own/from the scope whether an expression was
 744 * actually provided.
 745 */
 746MdChipsCtrl.prototype.useOnSelectExpression = function() {
 747  this.useOnSelect = true;
 748};
 749
 750/**
 751 * Gets the input buffer. The input buffer can be the model bound to the
 752 * default input item {@code this.chipBuffer}, the {@code selectedItem}
 753 * model of an {@code md-autocomplete}, or, through some magic, the model
 754 * bound to any inpput or text area element found within a
 755 * {@code md-input-container} element.
 756 * @return {Object|string}
 757 */
 758MdChipsCtrl.prototype.getChipBuffer = function() {
 759  return !this.userInputElement ? this.chipBuffer :
 760      this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
 761          this.userInputElement[0].value;
 762};
 763
 764/**
 765 * Resets the input buffer for either the internal input or user provided input element.
 766 */
 767MdChipsCtrl.prototype.resetChipBuffer = function() {
 768  if (this.userInputElement) {
 769    if (this.userInputNgModelCtrl) {
 770      this.userInputNgModelCtrl.$setViewValue('');
 771      this.userInputNgModelCtrl.$render();
 772    } else {
 773      this.userInputElement[0].value = '';
 774    }
 775  } else {
 776    this.chipBuffer = '';
 777  }
 778};
 779
 780MdChipsCtrl.prototype.hasMaxChipsReached = function() {
 781  if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
 782
 783  return this.maxChips > 0 && this.items.length >= this.maxChips;
 784};
 785
 786/**
 787 * Updates the validity properties for the ngModel.
 788 */
 789MdChipsCtrl.prototype.validateModel = function() {
 790  this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());
 791};
 792
 793/**
 794 * Removes the chip at the given index.
 795 * @param index
 796 */
 797MdChipsCtrl.prototype.removeChip = function(index) {
 798  var removed = this.items.splice(index, 1);
 799
 800  // Update model validation
 801  this.ngModelCtrl.$setDirty();
 802  this.validateModel();
 803
 804  if (removed && removed.length && this.useOnRemove && this.onRemove) {
 805    this.onRemove({ '$chip': removed[0], '$index': index });
 806  }
 807};
 808
 809MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
 810  this.removeChip(index);
 811
 812  if (this.autocompleteCtrl) {
 813    // Always hide the autocomplete dropdown before focusing the autocomplete input.
 814    // Wait for the input to move horizontally, because the chip was removed.
 815    // This can lead to an incorrect dropdown position.
 816    this.autocompleteCtrl.hidden = true;
 817    this.$mdUtil.nextTick(this.onFocus.bind(this));
 818  } else {
 819    this.onFocus();
 820  }
 821
 822};
 823/**
 824 * Selects the chip at `index`,
 825 * @param index
 826 */
 827MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
 828  if (!this.items.length) {
 829    this.selectChip(-1);
 830    this.onFocus();
 831    return;
 832  }
 833  if (index === this.items.length) return this.onFocus();
 834  index = Math.max(index, 0);
 835  index = Math.min(index, this.items.length - 1);
 836  this.selectChip(index);
 837  this.focusChip(index);
 838};
 839
 840/**
 841 * Marks the chip at the given index as selected.
 842 * @param index
 843 */
 844MdChipsCtrl.prototype.selectChip = function(index) {
 845  if (index >= -1 && index <= this.items.length) {
 846    this.selectedChip = index;
 847
 848    // Fire the onSelect if provided
 849    if (this.useOnSelect && this.onSelect) {
 850      this.onSelect({'$chip': this.items[this.selectedChip] });
 851    }
 852  } else {
 853    this.$log.warn('Selected Chip index out of bounds; ignoring.');
 854  }
 855};
 856
 857/**
 858 * Selects the chip at `index` and gives it focus.
 859 * @param index
 860 */
 861MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
 862  this.selectChip(index);
 863  if (index != -1) {
 864    this.focusChip(index);
 865  }
 866};
 867
 868/**
 869 * Call `focus()` on the chip at `index`
 870 */
 871MdChipsCtrl.prototype.focusChip = function(index) {
 872  this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content').focus();
 873};
 874
 875/**
 876 * Configures the required interactions with the ngModel Controller.
 877 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
 878 * @param ngModelCtrl
 879 */
 880MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
 881  this.ngModelCtrl = ngModelCtrl;
 882
 883  var self = this;
 884  ngModelCtrl.$render = function() {
 885    // model is updated. do something.
 886    self.items = self.ngModelCtrl.$viewValue;
 887  };
 888};
 889
 890MdChipsCtrl.prototype.onFocus = function () {
 891  var input = this.$element[0].querySelector('input');
 892  input && input.focus();
 893  this.resetSelectedChip();
 894};
 895
 896MdChipsCtrl.prototype.onInputFocus = function () {
 897  this.inputHasFocus = true;
 898  this.resetSelectedChip();
 899};
 900
 901MdChipsCtrl.prototype.onInputBlur = function () {
 902  this.inputHasFocus = false;
 903
 904  var chipBuffer = this.getChipBuffer().trim();
 905
 906  // Update the custom chip validators.
 907  this.validateModel();
 908
 909  var isModelValid = this.ngModelCtrl.$valid;
 910
 911  if (this.userInputNgModelCtrl) {
 912    isModelValid &= this.userInputNgModelCtrl.$valid;
 913  }
 914
 915  // Only append the chip and reset the chip buffer if the chips and input ngModel is valid.
 916  if (this.addOnBlur && chipBuffer && isModelValid) {
 917    this.appendChip(chipBuffer);
 918    this.resetChipBuffer();
 919  }
 920};
 921
 922/**
 923 * Configure event bindings on a user-provided input element.
 924 * @param inputElement
 925 */
 926MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
 927  this.userInputElement = inputElement;
 928
 929  // Find the NgModelCtrl for the input element
 930  var ngModelCtrl = inputElement.controller('ngModel');
 931  // `.controller` will look in the parent as well.
 932  if (ngModelCtrl != this.ngModelCtrl) {
 933    this.userInputNgModelCtrl = ngModelCtrl;
 934  }
 935
 936  var scope = this.$scope;
 937  var ctrl = this;
 938
 939  // Run all of the events using evalAsync because a focus may fire a blur in the same digest loop
 940  var scopeApplyFn = function(event, fn) {
 941    scope.$evalAsync(angular.bind(ctrl, fn, event));
 942  };
 943
 944  // Bind to keydown and focus events of input
 945  inputElement
 946      .attr({ tabindex: 0 })
 947      .on('keydown', function(event) { scopeApplyFn(event, ctrl.inputKeydown) })
 948      .on('focus', function(event) { scopeApplyFn(event, ctrl.onInputFocus) })
 949      .on('blur', function(event) { scopeApplyFn(event, ctrl.onInputBlur) })
 950};
 951
 952MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
 953  if (ctrl) {
 954    this.autocompleteCtrl = ctrl;
 955
 956    ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
 957      if (item) {
 958        // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
 959        if (this.hasMaxChipsReached()) return;
 960
 961        this.appendChip(item);
 962        this.resetChipBuffer();
 963      }
 964    }));
 965
 966    this.$element.find('input')
 967        .on('focus',angular.bind(this, this.onInputFocus) )
 968        .on('blur', angular.bind(this, this.onInputBlur) );
 969  }
 970};
 971
 972MdChipsCtrl.prototype.hasFocus = function () {
 973  return this.inputHasFocus || this.selectedChip >= 0;
 974};
 975
 976  
 977  MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular
 978      .module('material.components.chips')
 979      .directive('mdChips', MdChips);
 980
 981  /**
 982   * @ngdoc directive
 983   * @name mdChips
 984   * @module material.components.chips
 985   *
 986   * @description
 987   * `<md-chips>` is an input component for building lists of strings or objects. The list items are
 988   * displayed as 'chips'. This component can make use of an `<input>` element or an 
 989   * `<md-autocomplete>` element.
 990   *
 991   * ### Custom templates
 992   * A custom template may be provided to render the content of each chip. This is achieved by
 993   * specifying an `<md-chip-template>` element containing the custom content as a child of
 994   * `<md-chips>`.
 995   *
 996   * Note: Any attributes on
 997   * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
 998   * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
 999   * the chip object and its index in the list of chips, respectively.
1000   * To override the chip delete control, include an element (ideally a button) with the attribute
1001   * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
1002   * is also placed as a sibling to the chip content (on which there are also click listeners) to
1003   * avoid a nested ng-click situation.
1004   *
1005   * <h3> Pending Features </h3>
1006   * <ul style="padding-left:20px;">
1007   *
1008   *   <ul>Style
1009   *     <li>Colours for hover, press states (ripple?).</li>
1010   *   </ul>
1011   *
1012   *   <ul>Validation
1013   *     <li>allow a validation callback</li>
1014   *     <li>hilighting style for invalid chips</li>
1015   *   </ul>
1016   *
1017   *   <ul>Item mutation
1018   *     <li>Support `
1019   *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
1020   *       click?
1021   *     </li>
1022   *   </ul>
1023   *
1024   *   <ul>Truncation and Disambiguation (?)
1025   *     <li>Truncate chip text where possible, but do not truncate entries such that two are
1026   *     indistinguishable.</li>
1027   *   </ul>
1028   *
1029   *   <ul>Drag and Drop
1030   *     <li>Drag and drop chips between related `<md-chips>` elements.
1031   *     </li>
1032   *   </ul>
1033   * </ul>
1034   *
1035   * <span style="font-size:.8em;text-align:center">
1036   *   Warning: This component is a WORK IN PROGRESS. If you use it now,
1037   *   it will probably break on you in the future.
1038   * </span>
1039   *
1040   * Sometimes developers want to limit the amount of possible chips.<br/>
1041   * You can specify the maximum amount of chips by using the following markup.
1042   *
1043   * <hljs lang="html">
1044   *   <md-chips
1045   *       ng-model="myItems"
1046   *       placeholder="Add an item"
1047   *       md-max-chips="5">
1048   *   </md-chips>
1049   * </hljs>
1050   *
1051   * In some cases, you have an autocomplete inside of the `md-chips`.<br/>
1052   * When the maximum amount of chips has been reached, you can also disable the autocomplete selection.<br/>
1053   * Here is an example markup.
1054   *
1055   * <hljs lang="html">
1056   *   <md-chips ng-model="myItems" md-max-chips="5">
1057   *     <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>
1058   *   </md-chips>
1059   * </hljs>
1060   *
1061   * @param {string=|object=} ng-model A model to bind the list of items to
1062   * @param {string=} placeholder Placeholder text that will be forwarded to the input.
1063   * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
1064   *    displayed when there is at least one item in the list
1065   * @param {boolean=} md-removable Enables or disables the deletion of chips through the
1066   *    removal icon or the Delete/Backspace key. Defaults to true.
1067   * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
1068   *    the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
1069   *    marked as readonly.<br/><br/>
1070   *    When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.
1071   * @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can 
1072   *    go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
1073   *    supported for chips with basic template.
1074   * @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
1075   *    <br/><br/>The validation property `md-max-chips` can be used when the max chips
1076   *    amount is reached.
1077   * @param {boolean=} md-add-on-blur When set to true, remaining text inside of the input will
1078   *    be converted into a new chip on blur.
1079   * @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called
1080   *    expects one of the following return values:
1081   *    - an object representing the `$chip` input string
1082   *    - `undefined` to simply add the `$chip` input string, or
1083   *    - `null` to prevent the chip from being appended
1084   * @param {expression=} md-on-add An expression which will be called when a chip has been
1085   *    added.
1086   * @param {expression=} md-on-remove An expression which will be called when a chip has been
1087   *    removed.
1088   * @param {expression=} md-on-select An expression which will be called when a chip is selected.
1089   * @param {boolean} md-require-match If true, and the chips template contains an autocomplete,
1090   *    only allow selection of pre-defined chips (i.e. you cannot add new ones).
1091   * @param {string=} delete-hint A string read by screen readers instructing users that pressing
1092   *    the delete key will remove the chip.
1093   * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
1094   *    screen readers.
1095   * @param {expression=} md-separator-keys An array of key codes used to separate chips.
1096   *
1097   * @usage
1098   * <hljs lang="html">
1099   *   <md-chips
1100   *       ng-model="myItems"
1101   *       placeholder="Add an item"
1102   *       readonly="isReadOnly">
1103   *   </md-chips>
1104   * </hljs>
1105   *
1106   * <h3>Validation</h3>
1107   * When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based
1108   * on our custom validators.
1109   * <hljs lang="html">
1110   *   <form name="userForm">
1111   *     <md-chips
1112   *       name="fruits"
1113   *       ng-model="myItems"
1114   *       placeholder="Add an item"
1115   *       md-max-chips="5">
1116   *     </md-chips>
1117   *     <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">
1118   *       <div ng-message="md-max-chips">You reached the maximum amount of chips</div>
1119   *    </div>
1120   *   </form>
1121   * </hljs>
1122   *
1123   */
1124
1125
1126  var MD_CHIPS_TEMPLATE = '\
1127      <md-chips-wrap\
1128          ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
1129          ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \
1130                      \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\
1131                      \'md-removable\': $mdChipsCtrl.isRemovable() }"\
1132          class="md-chips">\
1133        <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
1134            index="{{$index}}"\
1135            ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}">\
1136          <div class="md-chip-content"\
1137              tabindex="-1"\
1138              aria-hidden="true"\
1139              ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\
1140              ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
1141              md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
1142          <div ng-if="$mdChipsCtrl.isRemovable()"\
1143               class="md-chip-remove-container"\
1144               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
1145        </md-chip>\
1146        <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\
1147          <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
1148        </div>\
1149      </md-chips-wrap>';
1150
1151  var CHIP_INPUT_TEMPLATE = '\
1152        <input\
1153            class="md-input"\
1154            tabindex="0"\
1155            placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
1156            aria-label="{{$mdChipsCtrl.getPlaceholder()}}"\
1157            ng-model="$mdChipsCtrl.chipBuffer"\
1158            ng-focus="$mdChipsCtrl.onInputFocus()"\
1159            ng-blur="$mdChipsCtrl.onInputBlur()"\
1160            ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
1161
1162  var CHIP_DEFAULT_TEMPLATE = '\
1163      <span>{{$chip}}</span>';
1164
1165  var CHIP_REMOVE_TEMPLATE = '\
1166      <button\
1167          class="md-chip-remove"\
1168          ng-if="$mdChipsCtrl.isRemovable()"\
1169          ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
1170          type="button"\
1171          aria-hidden="true"\
1172          tabindex="-1">\
1173        <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\
1174        <span class="md-visually-hidden">\
1175          {{$mdChipsCtrl.deleteButtonLabel}}\
1176        </span>\
1177      </button>';
1178
1179  /**
1180   * MDChips Directive Definition
1181   */
1182  function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout, $$mdSvgRegistry) {
1183    // Run our templates through $mdUtil.processTemplate() to allow custom start/end symbols
1184    var templates = getTemplates();
1185
1186    return {
1187      template: function(element, attrs) {
1188        // Clone the element into an attribute. By prepending the attribute
1189        // name with '$', Angular won't write it into the DOM. The cloned
1190        // element propagates to the link function via the attrs argument,
1191        // where various contained-elements can be consumed.
1192        attrs['$mdUserTemplate'] = element.clone();
1193        return templates.chips;
1194      },
1195      require: ['mdChips'],
1196      restrict: 'E',
1197      controller: 'MdChipsCtrl',
1198      controllerAs: '$mdChipsCtrl',
1199      bindToController: true,
1200      compile: compile,
1201      scope: {
1202        readonly: '=readonly',
1203        removable: '=mdRemovable',
1204        placeholder: '@',
1205        secondaryPlaceholder: '@',
1206        maxChips: '@mdMaxChips',
1207        transformChip: '&mdTransformChip',
1208        onAppend: '&mdOnAppend',
1209        onAdd: '&mdOnAdd',
1210        onRemove: '&mdOnRemove',
1211        onSelect: '&mdOnSelect',
1212        deleteHint: '@',
1213        deleteButtonLabel: '@',
1214        separatorKeys: '=?mdSeparatorKeys',
1215        requireMatch: '=?mdRequireMatch'
1216      }
1217    };
1218
1219    /**
1220     * Builds the final template for `md-chips` and returns the postLink function.
1221     *
1222     * Building the template involves 3 key components:
1223     * static chips
1224     * chip template
1225     * input control
1226     *
1227     * If no `ng-model` is provided, only the static chip work needs to be done.
1228     *
1229     * If no user-passed `md-chip-template` exists, the default template is used. This resulting
1230     * template is appended to the chip content element.
1231     *
1232     * The remove button may be overridden by passing an element with an md-chip-remove attribute.
1233     *
1234     * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
1235     * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
1236     * If no user input is provided, a default one is appended to the input container node in the
1237     * template.
1238     *
1239     * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
1240     * transclusion in the `postLink` function.
1241     *
1242     *
1243     * @param element
1244     * @param attr
1245     * @returns {Function}
1246     */
1247    function compile(element, attr) {
1248      // Grab the user template from attr and reset the attribute to null.
1249      var userTemplate = attr['$mdUserTemplate'];
1250      attr['$mdUserTemplate'] = null;
1251
1252      var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
1253
1254      var chipRemoveSelector = $mdUtil
1255        .prefixer()
1256        .buildList('md-chip-remove')
1257        .map(function(attr) {
1258          return 'md-chips>*[' + attr + ']';
1259        })
1260        .join(',');
1261
1262      // Set the chip remove, chip contents and chip input templates. The link function will put
1263      // them on the scope for transclusion later.
1264      var chipRemoveTemplate   = getTemplateByQuery(chipRemoveSelector) || templates.remove,
1265          chipContentsTemplate = chipTemplate || templates.default,
1266          chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
1267              || getTemplateByQuery('md-chips>input')
1268              || templates.input,
1269          staticChips = userTemplate.find('md-chip');
1270
1271      // Warn of malformed template. See #2545
1272      if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
1273        $log.warn('invalid placement of md-chip-remove within md-chip-template.');
1274      }
1275
1276      function getTemplateByQuery (query) {
1277        if (!attr.ngModel) return;
1278        var element = userTemplate[0].querySelector(query);
1279        return element && element.outerHTML;
1280      }
1281
1282      /**
1283       * Configures controller and transcludes.
1284       */
1285      return function postLink(scope, element, attrs, controllers) {
1286        $mdUtil.initOptionalProperties(scope, attr);
1287
1288        $mdTheming(element);
1289        var mdChipsCtrl = controllers[0];
1290        if(chipTemplate) {
1291          // Chip editing functionality assumes we are using the default chip template.
1292          mdChipsCtrl.enableChipEdit = false;
1293        }
1294
1295        mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
1296        mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
1297        mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
1298
1299        mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;
1300
1301        element
1302            .attr({ 'aria-hidden': true, tabindex: -1 })
1303            .on('focus', function () { mdChipsCtrl.onFocus(); });
1304
1305        if (attr.ngModel) {
1306          mdChipsCtrl.configureNgModel(element.controller('ngModel'));
1307
1308          // If an `md-transform-chip` attribute was set, tell the controller to use the expression
1309          // before appending chips.
1310          if (attrs.mdTransformChip) mdChipsCtrl.useTransformChipExpression();
1311
1312          // If an `md-on-append` attribute was set, tell the controller to use the expression
1313          // when appending chips.
1314          //
1315          // DEPRECATED: Will remove in official 1.0 release
1316          if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();
1317
1318          // If an `md-on-add` attribute was set, tell the controller to use the expression
1319          // when adding chips.
1320          if (attrs.mdOnAdd) mdChipsCtrl.useOnAddExpression();
1321
1322          // If an `md-on-remove` attribute was set, tell the controller to use the expression
1323          // when removing chips.
1324          if (attrs.mdOnRemove) mdChipsCtrl.useOnRemoveExpression();
1325
1326          // If an `md-on-select` attribute was set, tell the controller to use the expression
1327          // when selecting chips.
1328          if (attrs.mdOnSelect) mdChipsCtrl.useOnSelectExpression();
1329
1330          // The md-autocomplete and input elements won't be compiled until after this directive
1331          // is complete (due to their nested nature). Wait a tick before looking for them to
1332          // configure the controller.
1333          if (chipInputTemplate != templates.input) {
1334            // The autocomplete will not appear until the readonly attribute is not true (i.e.
1335            // false or undefined), so we have to watch the readonly and then on the next tick
1336            // after the chip transclusion has run, we can configure the autocomplete and user
1337            // input.
1338            scope.$watch('$mdChipsCtrl.readonly', function(readonly) {
1339              if (!readonly) {
1340
1341                $mdUtil.nextTick(function(){
1342
1343                  if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {
1344                    var autocompleteEl = element.find('md-autocomplete');
1345                    mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));
1346                  }
1347
1348                  mdChipsCtrl.configureUserInput(element.find('input'));
1349                });
1350              }
1351            });
1352          }
1353
1354          // At the next tick, if we find an input, make sure it has the md-input class
1355          $mdUtil.nextTick(function() {
1356            var input = element.find('input');
1357
1358            input && input.toggleClass('md-input', true);
1359          });
1360        }
1361
1362        // Compile with the parent's scope and prepend any static chips to the wrapper.
1363        if (staticChips.length > 0) {
1364          var compiledStaticChips = $compile(staticChips.clone())(scope.$parent);
1365          $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
1366        }
1367      };
1368    }
1369
1370    function getTemplates() {
1371      return {
1372        chips: $mdUtil.processTemplate(MD_CHIPS_TEMPLATE),
1373        input: $mdUtil.processTemplate(CHIP_INPUT_TEMPLATE),
1374        default: $mdUtil.processTemplate(CHIP_DEFAULT_TEMPLATE),
1375        remove: $mdUtil.processTemplate(CHIP_REMOVE_TEMPLATE)
1376      };
1377    }
1378  }
1379
1380angular
1381    .module('material.components.chips')
1382    .controller('MdContactChipsCtrl', MdContactChipsCtrl);
1383
1384
1385
1386/**
1387 * Controller for the MdContactChips component
1388 * @constructor
1389 */
1390function MdContactChipsCtrl () {
1391  /** @type {Object} */
1392  this.selectedItem = null;
1393
1394  /** @type {string} */
1395  this.searchText = '';
1396}
1397
1398
1399MdContactChipsCtrl.prototype.queryContact = function(searchText) {
1400  var results = this.contactQuery({'$query': searchText});
1401  return this.filterSelected ?
1402      results.filter(angular.bind(this, this.filterSelectedContacts)) : results;
1403};
1404
1405
1406MdContactChipsCtrl.prototype.itemName = function(item) {
1407  return item[this.contactName];
1408};
1409
1410
1411MdContactChipsCtrl.prototype.filterSelectedContacts = function(contact) {
1412  return this.contacts.indexOf(contact) == -1;
1413};
1414
1415
1416MdContactChips.$inject = ["$mdTheming", "$mdUtil"];angular
1417  .module('material.components.chips')
1418  .directive('mdContactChips', MdContactChips);
1419
1420/**
1421 * @ngdoc directive
1422 * @name mdContactChips
1423 * @module material.components.chips
1424 *
1425 * @description
1426 * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
1427 * `md-autocomplete` element. The component allows the caller to supply a query expression which
1428 * returns  a list of possible contacts. The user can select one of these and add it to the list of
1429 * chips.
1430 *
1431 * You may also use the `md-highlight-text` directive along with its parameters to control the
1432 * appearance of the matched text inside of the contacts' autocomplete popup.
1433 *
1434 * @param {string=|object=} ng-model A model to bind the list of items to
1435 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
1436 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
1437 *    displayed when there is at least on item in the list
1438 * @param {expression} md-contacts An expression expected to return contacts matching the search
1439 *    test, `$query`. If this expression involves a promise, a loading bar is displayed while
1440 *    waiting for it to resolve.
1441 * @param {string} md-contact-name The field name of the contact object representing the
1442 *    contact's name.
1443 * @param {string} md-contact-email The field name of the contact object representing the
1444 *    contact's email address.
1445 * @param {string} md-contact-image The field name of the contact object representing the
1446 *    contact's image.
1447 *
1448 *
1449 * @param {expression=} filter-selected Whether to filter selected contacts from the list of
1450 *    suggestions shown in the autocomplete. This attribute has been removed but may come back.
1451 *
1452 *
1453 *
1454 * @usage
1455 * <hljs lang="html">
1456 *   <md-contact-chips
1457 *       ng-model="ctrl.contacts"
1458 *       md-contacts="ctrl.querySearch($query)"
1459 *       md-contact-name="name"
1460 *       md-contact-image="image"
1461 *       md-contact-email="email"
1462 *       placeholder="To">
1463 *   </md-contact-chips>
1464 * </hljs>
1465 *
1466 */
1467
1468
1469var MD_CONTACT_CHIPS_TEMPLATE = '\
1470      <md-chips class="md-contact-chips"\
1471          ng-model="$mdContactChipsCtrl.contacts"\
1472          md-require-match="$mdContactChipsCtrl.requireMatch"\
1473          md-autocomplete-snap>\
1474          <md-autocomplete\
1475              md-menu-class="md-contact-chips-suggestions"\
1476              md-selected-item="$mdContactChipsCtrl.selectedItem"\
1477              md-search-text="$mdContactChipsCtrl.searchText"\
1478              md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
1479              md-item-text="$mdContactChipsCtrl.itemName(item)"\
1480              md-no-cache="true"\
1481              md-autoselect\
1482              placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
1483                  $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
1484            <div class="md-contact-suggestion">\
1485              <img \
1486                  ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
1487                  alt="{{item[$mdContactChipsCtrl.contactName]}}"\
1488                  ng-if="item[$mdContactChipsCtrl.contactImage]" />\
1489              <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText"\
1490                    md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}">\
1491                {{item[$mdContactChipsCtrl.contactName]}}\
1492              </span>\
1493              <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
1494            </div>\
1495          </md-autocomplete>\
1496          <md-chip-template>\
1497            <div class="md-contact-avatar">\
1498              <img \
1499                  ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
1500                  alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\
1501                  ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\
1502            </div>\
1503            <div class="md-contact-name">\
1504              {{$chip[$mdContactChipsCtrl.contactName]}}\
1505            </div>\
1506          </md-chip-template>\
1507      </md-chips>';
1508
1509
1510/**
1511 * MDContactChips Directive Definition
1512 *
1513 * @param $mdTheming
1514 * @returns {*}
1515 * ngInject
1516 */
1517function MdContactChips($mdTheming, $mdUtil) {
1518  return {
1519    template: function(element, attrs) {
1520      return MD_CONTACT_CHIPS_TEMPLATE;
1521    },
1522    restrict: 'E',
1523    controller: 'MdContactChipsCtrl',
1524    controllerAs: '$mdContactChipsCtrl',
1525    bindToController: true,
1526    compile: compile,
1527    scope: {
1528      contactQuery: '&mdContacts',
1529      placeholder: '@',
1530      secondaryPlaceholder: '@',
1531      contactName: '@mdContactName',
1532      contactImage: '@mdContactImage',
1533      contactEmail: '@mdContactEmail',
1534      contacts: '=ngModel',
1535      requireMatch: '=?mdRequireMatch',
1536      highlightFlags: '@?mdHighlightFlags'
1537    }
1538  };
1539
1540  function compile(element, attr) {
1541    return function postLink(scope, element, attrs, controllers) {
1542
1543      $mdUtil.initOptionalProperties(scope, attr);
1544      $mdTheming(element);
1545
1546      element.attr('tabindex', '-1');
1547    };
1548  }
1549}
1550
1551})(window, window.angular);