arnaucube
arnaucube/socketioMEANseedPublic Self-hosted repository
socketioMEANseed/www/bower_components/angular-material/modules/js/chips/chips.js
www/bower_components/angular-material/modules/js/chips/chips.jsRaw
   1/*!
   2 * Angular Material Design
   3 * https://github.com/angular/material
   4 * @license MIT
   5 * v1.1.3
   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", "$compile", "$timeout"];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, $compile, $timeout) {
 253  var deleteHintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);
 254
 255  return {
 256    restrict: 'E',
 257    require: ['^?mdChips', 'mdChip'],
 258    link: postLink,
 259    controller: 'MdChipCtrl'
 260  };
 261
 262  function postLink(scope, element, attr, ctrls) {
 263    var chipsController = ctrls.shift();
 264    var chipController = ctrls.shift();
 265    var chipContentElement = angular.element(element[0].querySelector('.md-chip-content'));
 266
 267    $mdTheming(element);
 268
 269    if (chipsController) {
 270      chipController.init(chipsController);
 271
 272      // Append our delete hint to the div.md-chip-content (which does not exist at compile time)
 273      chipContentElement.append($compile(deleteHintTemplate)(scope));
 274
 275      // When a chip is blurred, make sure to unset (or reset) the selected chip so that tabbing
 276      // through elements works properly
 277      chipContentElement.on('blur', function() {
 278        chipsController.resetSelectedChip();
 279        chipsController.$scope.$applyAsync();
 280      });
 281    }
 282
 283    // Use $timeout to ensure we run AFTER the element has been added to the DOM so we can focus it.
 284    $timeout(function() {
 285      if (!chipsController) {
 286        return;
 287      }
 288
 289      if (chipsController.shouldFocusLastChip) {
 290        chipsController.focusLastChipThenInput();
 291      }
 292    });
 293  }
 294}
 295
 296
 297MdChipRemove['$inject'] = ["$timeout"];angular
 298    .module('material.components.chips')
 299    .directive('mdChipRemove', MdChipRemove);
 300
 301/**
 302 * @ngdoc directive
 303 * @name mdChipRemove
 304 * @restrict A
 305 * @module material.components.chips
 306 *
 307 * @description
 308 * Designates an element to be used as the delete button for a chip. <br/>
 309 * This element is passed as a child of the `md-chips` element.
 310 *
 311 * The designated button will be just appended to the chip and removes the given chip on click.<br/>
 312 * By default the button is not being styled by the `md-chips` component.
 313 *
 314 * @usage
 315 * <hljs lang="html">
 316 *   <md-chips>
 317 *     <button md-chip-remove="">
 318 *       <md-icon md-svg-icon="md-close"></md-icon>
 319 *     </button>
 320 *   </md-chips>
 321 * </hljs>
 322 */
 323
 324
 325/**
 326 * MdChipRemove Directive Definition.
 327 * 
 328 * @param $timeout
 329 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
 330 * @constructor
 331 */
 332function MdChipRemove ($timeout) {
 333  return {
 334    restrict: 'A',
 335    require: '^mdChips',
 336    scope: false,
 337    link: postLink
 338  };
 339
 340  function postLink(scope, element, attr, ctrl) {
 341    element.on('click', function(event) {
 342      scope.$apply(function() {
 343        ctrl.removeChip(scope.$$replacedScope.$index);
 344      });
 345    });
 346
 347    // Child elements aren't available until after a $timeout tick as they are hidden by an
 348    // `ng-if`. see http://goo.gl/zIWfuw
 349    $timeout(function() {
 350      element.attr({ tabindex: -1, 'aria-hidden': true });
 351      element.find('button').attr('tabindex', '-1');
 352    });
 353  }
 354}
 355
 356
 357MdChipTransclude['$inject'] = ["$compile"];angular
 358    .module('material.components.chips')
 359    .directive('mdChipTransclude', MdChipTransclude);
 360
 361function MdChipTransclude ($compile) {
 362  return {
 363    restrict: 'EA',
 364    terminal: true,
 365    link: link,
 366    scope: false
 367  };
 368  function link (scope, element, attr) {
 369    var ctrl = scope.$parent.$mdChipsCtrl,
 370        newScope = ctrl.parent.$new(false, ctrl.parent);
 371    newScope.$$replacedScope = scope;
 372    newScope.$chip = scope.$chip;
 373    newScope.$index = scope.$index;
 374    newScope.$mdChipsCtrl = ctrl;
 375
 376    var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);
 377
 378    element.html(newHtml);
 379    $compile(element.contents())(newScope);
 380  }
 381}
 382
 383/**
 384 * The default chip append delay.
 385 *
 386 * @type {number}
 387 */
 388MdChipsCtrl['$inject'] = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];
 389var DEFAULT_CHIP_APPEND_DELAY = 300;
 390
 391angular
 392    .module('material.components.chips')
 393    .controller('MdChipsCtrl', MdChipsCtrl);
 394
 395/**
 396 * Controller for the MdChips component. Responsible for adding to and
 397 * removing from the list of chips, marking chips as selected, and binding to
 398 * the models of various input components.
 399 *
 400 * @param $scope
 401 * @param $attrs
 402 * @param $mdConstant
 403 * @param $log
 404 * @param $element
 405 * @param $timeout
 406 * @param $mdUtil
 407 * @constructor
 408 */
 409function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {
 410  /** @type {$timeout} **/
 411  this.$timeout = $timeout;
 412
 413  /** @type {Object} */
 414  this.$mdConstant = $mdConstant;
 415
 416  /** @type {angular.$scope} */
 417  this.$scope = $scope;
 418
 419  /** @type {angular.$scope} */
 420  this.parent = $scope.$parent;
 421
 422  /** @type {$mdUtil} */
 423  this.$mdUtil = $mdUtil;
 424
 425  /** @type {$log} */
 426  this.$log = $log;
 427
 428  /** @type {$element} */
 429  this.$element = $element;
 430
 431  /** @type {$attrs} */
 432  this.$attrs = $attrs;
 433
 434  /** @type {angular.NgModelController} */
 435  this.ngModelCtrl = null;
 436
 437  /** @type {angular.NgModelController} */
 438  this.userInputNgModelCtrl = null;
 439
 440  /** @type {MdAutocompleteCtrl} */
 441  this.autocompleteCtrl = null;
 442
 443  /** @type {Element} */
 444  this.userInputElement = null;
 445
 446  /** @type {Array.<Object>} */
 447  this.items = [];
 448
 449  /** @type {number} */
 450  this.selectedChip = -1;
 451
 452  /** @type {string} */
 453  this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);
 454
 455  /** @type {string} */
 456  this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);
 457
 458  /**
 459   * The text to be used as the aria-label for the input.
 460   * @type {string}
 461   */
 462  this.inputAriaLabel = 'Chips input.';
 463
 464  /**
 465   * Hidden hint text to describe the chips container. Used to give context to screen readers when
 466   * the chips are readonly and the input cannot be selected.
 467   *
 468   * @type {string}
 469   */
 470  this.containerHint = 'Chips container. Use arrow keys to select chips.';
 471
 472  /**
 473   * Hidden hint text for how to delete a chip. Used to give context to screen readers.
 474   * @type {string}
 475   */
 476  this.deleteHint = 'Press delete to remove this chip.';
 477
 478  /**
 479   * Hidden label for the delete button. Used to give context to screen readers.
 480   * @type {string}
 481   */
 482  this.deleteButtonLabel = 'Remove';
 483
 484  /**
 485   * Model used by the input element.
 486   * @type {string}
 487   */
 488  this.chipBuffer = '';
 489
 490  /**
 491   * Whether to use the transformChip expression to transform the chip buffer
 492   * before appending it to the list.
 493   * @type {boolean}
 494   */
 495  this.useTransformChip = false;
 496
 497  /**
 498   * Whether to use the onAdd expression to notify of chip additions.
 499   * @type {boolean}
 500   */
 501  this.useOnAdd = false;
 502
 503  /**
 504   * Whether to use the onRemove expression to notify of chip removals.
 505   * @type {boolean}
 506   */
 507  this.useOnRemove = false;
 508
 509  /**
 510   * The ID of the chips wrapper which is used to build unique IDs for the chips and the aria-owns
 511   * attribute.
 512   *
 513   * Defaults to '_md-chips-wrapper-' plus a unique number.
 514   *
 515   * @type {string}
 516   */
 517  this.wrapperId = '';
 518
 519  /**
 520   * Array of unique numbers which will be auto-generated any time the items change, and is used to
 521   * create unique IDs for the aria-owns attribute.
 522   *
 523   * @type {Array}
 524   */
 525  this.contentIds = [];
 526
 527  /**
 528   * The index of the chip that should have it's tabindex property set to 0 so it is selectable
 529   * via the keyboard.
 530   *
 531   * @type {int}
 532   */
 533  this.ariaTabIndex = null;
 534
 535  /**
 536   * After appending a chip, the chip will be focused for this number of milliseconds before the
 537   * input is refocused.
 538   *
 539   * **Note:** This is **required** for compatibility with certain screen readers in order for
 540   * them to properly allow keyboard access.
 541   *
 542   * @type {number}
 543   */
 544  this.chipAppendDelay = DEFAULT_CHIP_APPEND_DELAY;
 545
 546  this.init();
 547}
 548
 549/**
 550 * Initializes variables and sets up watchers
 551 */
 552MdChipsCtrl.prototype.init = function() {
 553  var ctrl = this;
 554
 555  // Set the wrapper ID
 556  ctrl.wrapperId = '_md-chips-wrapper-' + ctrl.$mdUtil.nextUid();
 557
 558  // Setup a watcher which manages the role and aria-owns attributes
 559  ctrl.$scope.$watchCollection('$mdChipsCtrl.items', function() {
 560    // Make sure our input and wrapper have the correct ARIA attributes
 561    ctrl.setupInputAria();
 562    ctrl.setupWrapperAria();
 563  });
 564
 565  ctrl.$attrs.$observe('mdChipAppendDelay', function(newValue) {
 566    ctrl.chipAppendDelay = parseInt(newValue) || DEFAULT_CHIP_APPEND_DELAY;
 567  });
 568};
 569
 570/**
 571 * If we have an input, ensure it has the appropriate ARIA attributes.
 572 */
 573MdChipsCtrl.prototype.setupInputAria = function() {
 574  var input = this.$element.find('input');
 575
 576  // If we have no input, just return
 577  if (!input) {
 578    return;
 579  }
 580
 581  input.attr('role', 'textbox');
 582  input.attr('aria-multiline', true);
 583};
 584
 585/**
 586 * Ensure our wrapper has the appropriate ARIA attributes.
 587 */
 588MdChipsCtrl.prototype.setupWrapperAria = function() {
 589  var ctrl = this,
 590      wrapper = this.$element.find('md-chips-wrap');
 591
 592  if (this.items && this.items.length) {
 593    // Dynamically add the listbox role on every change because it must be removed when there are
 594    // no items.
 595    wrapper.attr('role', 'listbox');
 596
 597    // Generate some random (but unique) IDs for each chip
 598    this.contentIds = this.items.map(function() {
 599      return ctrl.wrapperId + '-chip-' + ctrl.$mdUtil.nextUid();
 600    });
 601
 602    // Use the contentIDs above to generate the aria-owns attribute
 603    wrapper.attr('aria-owns', this.contentIds.join(' '));
 604  } else {
 605    // If we have no items, then the role and aria-owns attributes MUST be removed
 606    wrapper.removeAttr('role');
 607    wrapper.removeAttr('aria-owns');
 608  }
 609};
 610
 611/**
 612 * Handles the keydown event on the input element: by default <enter> appends
 613 * the buffer to the chip list, while backspace removes the last chip in the
 614 * list if the current buffer is empty.
 615 * @param event
 616 */
 617MdChipsCtrl.prototype.inputKeydown = function(event) {
 618  var chipBuffer = this.getChipBuffer();
 619
 620  // If we have an autocomplete, and it handled the event, we have nothing to do
 621  if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {
 622    return;
 623  }
 624
 625  if (event.keyCode === this.$mdConstant.KEY_CODE.BACKSPACE) {
 626    // Only select and focus the previous chip, if the current caret position of the
 627    // input element is at the beginning.
 628    if (this.getCursorPosition(event.target) !== 0) {
 629      return;
 630    }
 631
 632    event.preventDefault();
 633    event.stopPropagation();
 634
 635    if (this.items.length) {
 636      this.selectAndFocusChipSafe(this.items.length - 1);
 637    }
 638
 639    return;
 640  }
 641
 642  // By default <enter> appends the buffer to the chip list.
 643  if (!this.separatorKeys || this.separatorKeys.length < 1) {
 644    this.separatorKeys = [this.$mdConstant.KEY_CODE.ENTER];
 645  }
 646
 647  // Support additional separator key codes in an array of `md-separator-keys`.
 648  if (this.separatorKeys.indexOf(event.keyCode) !== -1) {
 649    if ((this.autocompleteCtrl && this.requireMatch) || !chipBuffer) return;
 650    event.preventDefault();
 651
 652    // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
 653    if (this.hasMaxChipsReached()) return;
 654
 655    this.appendChip(chipBuffer.trim());
 656    this.resetChipBuffer();
 657
 658    return false;
 659  }
 660};
 661
 662/**
 663 * Returns the cursor position of the specified input element.
 664 * @param element HTMLInputElement
 665 * @returns {Number} Cursor Position of the input.
 666 */
 667MdChipsCtrl.prototype.getCursorPosition = function(element) {
 668  /*
 669   * Figure out whether the current input for the chips buffer is valid for using
 670   * the selectionStart / end property to retrieve the cursor position.
 671   * Some browsers do not allow the use of those attributes, on different input types.
 672   */
 673  try {
 674    if (element.selectionStart === element.selectionEnd) {
 675      return element.selectionStart;
 676    }
 677  } catch (e) {
 678    if (!element.value) {
 679      return 0;
 680    }
 681  }
 682};
 683
 684
 685/**
 686 * Updates the content of the chip at given index
 687 * @param chipIndex
 688 * @param chipContents
 689 */
 690MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
 691  if(chipIndex >= 0 && chipIndex < this.items.length) {
 692    this.items[chipIndex] = chipContents;
 693    this.ngModelCtrl.$setDirty();
 694  }
 695};
 696
 697
 698/**
 699 * Returns true if a chip is currently being edited. False otherwise.
 700 * @return {boolean}
 701 */
 702MdChipsCtrl.prototype.isEditingChip = function() {
 703  return !!this.$element[0].querySelector('._md-chip-editing');
 704};
 705
 706
 707MdChipsCtrl.prototype.isRemovable = function() {
 708  // Return false if we have static chips
 709  if (!this.ngModelCtrl) {
 710    return false;
 711  }
 712
 713  return this.readonly ? this.removable :
 714         angular.isDefined(this.removable) ? this.removable : true;
 715};
 716
 717/**
 718 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
 719 * keys switch which chips is active
 720 * @param event
 721 */
 722MdChipsCtrl.prototype.chipKeydown = function (event) {
 723  if (this.getChipBuffer()) return;
 724  if (this.isEditingChip()) return;
 725
 726  switch (event.keyCode) {
 727    case this.$mdConstant.KEY_CODE.BACKSPACE:
 728    case this.$mdConstant.KEY_CODE.DELETE:
 729      if (this.selectedChip < 0) return;
 730      event.preventDefault();
 731      // Cancel the delete action only after the event cancel. Otherwise the page will go back.
 732      if (!this.isRemovable()) return;
 733      this.removeAndSelectAdjacentChip(this.selectedChip);
 734      break;
 735    case this.$mdConstant.KEY_CODE.LEFT_ARROW:
 736      event.preventDefault();
 737      // By default, allow selection of -1 which will focus the input; if we're readonly, don't go
 738      // below 0
 739      if (this.selectedChip < 0 || (this.readonly && this.selectedChip == 0)) {
 740        this.selectedChip = this.items.length;
 741      }
 742      if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
 743      break;
 744    case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
 745      event.preventDefault();
 746      this.selectAndFocusChipSafe(this.selectedChip + 1);
 747      break;
 748    case this.$mdConstant.KEY_CODE.ESCAPE:
 749    case this.$mdConstant.KEY_CODE.TAB:
 750      if (this.selectedChip < 0) return;
 751      event.preventDefault();
 752      this.onFocus();
 753      break;
 754  }
 755};
 756
 757/**
 758 * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
 759 * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
 760 * always.
 761 */
 762MdChipsCtrl.prototype.getPlaceholder = function() {
 763  // Allow `secondary-placeholder` to be blank.
 764  var useSecondary = (this.items && this.items.length &&
 765      (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
 766  return useSecondary ? this.secondaryPlaceholder : this.placeholder;
 767};
 768
 769/**
 770 * Removes chip at {@code index} and selects the adjacent chip.
 771 * @param index
 772 */
 773MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
 774  var self = this;
 775  var selIndex = self.getAdjacentChipIndex(index);
 776  var wrap = this.$element[0].querySelector('md-chips-wrap');
 777  var chip = this.$element[0].querySelector('md-chip[index="' + index + '"]');
 778
 779  self.removeChip(index);
 780
 781  // The dobule-timeout is currently necessary to ensure that the DOM has finalized and the select()
 782  // will find the proper chip since the selection is index-based.
 783  //
 784  // TODO: Investigate calling from within chip $scope.$on('$destroy') to reduce/remove timeouts
 785  self.$timeout(function() {
 786    self.$timeout(function() {
 787      self.selectAndFocusChipSafe(selIndex);
 788    });
 789  });
 790};
 791
 792/**
 793 * Sets the selected chip index to -1.
 794 */
 795MdChipsCtrl.prototype.resetSelectedChip = function() {
 796  this.selectedChip = -1;
 797  this.ariaTabIndex = null;
 798};
 799
 800/**
 801 * Gets the index of an adjacent chip to select after deletion. Adjacency is
 802 * determined as the next chip in the list, unless the target chip is the
 803 * last in the list, then it is the chip immediately preceding the target. If
 804 * there is only one item in the list, -1 is returned (select none).
 805 * The number returned is the index to select AFTER the target has been
 806 * removed.
 807 * If the current chip is not selected, then -1 is returned to select none.
 808 */
 809MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
 810  var len = this.items.length - 1;
 811  return (len == 0) ? -1 :
 812      (index == len) ? index -1 : index;
 813};
 814
 815/**
 816 * Append the contents of the buffer to the chip list. This method will first
 817 * call out to the md-transform-chip method, if provided.
 818 *
 819 * @param newChip
 820 */
 821MdChipsCtrl.prototype.appendChip = function(newChip) {
 822  this.shouldFocusLastChip = true;
 823  if (this.useTransformChip && this.transformChip) {
 824    var transformedChip = this.transformChip({'$chip': newChip});
 825
 826    // Check to make sure the chip is defined before assigning it, otherwise, we'll just assume
 827    // they want the string version.
 828    if (angular.isDefined(transformedChip)) {
 829      newChip = transformedChip;
 830    }
 831  }
 832
 833  // If items contains an identical object to newChip, do not append
 834  if (angular.isObject(newChip)){
 835    var identical = this.items.some(function(item){
 836      return angular.equals(newChip, item);
 837    });
 838    if (identical) return;
 839  }
 840
 841  // Check for a null (but not undefined), or existing chip and cancel appending
 842  if (newChip == null || this.items.indexOf(newChip) + 1) return;
 843
 844  // Append the new chip onto our list
 845  var length = this.items.push(newChip);
 846  var index = length - 1;
 847
 848  // Update model validation
 849  this.ngModelCtrl.$setDirty();
 850  this.validateModel();
 851
 852  // If they provide the md-on-add attribute, notify them of the chip addition
 853  if (this.useOnAdd && this.onAdd) {
 854    this.onAdd({ '$chip': newChip, '$index': index });
 855  }
 856};
 857
 858/**
 859 * Sets whether to use the md-transform-chip expression. This expression is
 860 * bound to scope and controller in {@code MdChipsDirective} as
 861 * {@code transformChip}. Due to the nature of directive scope bindings, the
 862 * controller cannot know on its own/from the scope whether an expression was
 863 * actually provided.
 864 */
 865MdChipsCtrl.prototype.useTransformChipExpression = function() {
 866  this.useTransformChip = true;
 867};
 868
 869/**
 870 * Sets whether to use the md-on-add expression. This expression is
 871 * bound to scope and controller in {@code MdChipsDirective} as
 872 * {@code onAdd}. Due to the nature of directive scope bindings, the
 873 * controller cannot know on its own/from the scope whether an expression was
 874 * actually provided.
 875 */
 876MdChipsCtrl.prototype.useOnAddExpression = function() {
 877  this.useOnAdd = true;
 878};
 879
 880/**
 881 * Sets whether to use the md-on-remove expression. This expression is
 882 * bound to scope and controller in {@code MdChipsDirective} as
 883 * {@code onRemove}. Due to the nature of directive scope bindings, the
 884 * controller cannot know on its own/from the scope whether an expression was
 885 * actually provided.
 886 */
 887MdChipsCtrl.prototype.useOnRemoveExpression = function() {
 888  this.useOnRemove = true;
 889};
 890
 891/*
 892 * Sets whether to use the md-on-select expression. This expression is
 893 * bound to scope and controller in {@code MdChipsDirective} as
 894 * {@code onSelect}. Due to the nature of directive scope bindings, the
 895 * controller cannot know on its own/from the scope whether an expression was
 896 * actually provided.
 897 */
 898MdChipsCtrl.prototype.useOnSelectExpression = function() {
 899  this.useOnSelect = true;
 900};
 901
 902/**
 903 * Gets the input buffer. The input buffer can be the model bound to the
 904 * default input item {@code this.chipBuffer}, the {@code selectedItem}
 905 * model of an {@code md-autocomplete}, or, through some magic, the model
 906 * bound to any inpput or text area element found within a
 907 * {@code md-input-container} element.
 908 * @return {string}
 909 */
 910MdChipsCtrl.prototype.getChipBuffer = function() {
 911  var chipBuffer =  !this.userInputElement ? this.chipBuffer :
 912                     this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
 913                     this.userInputElement[0].value;
 914
 915  // Ensure that the chip buffer is always a string. For example, the input element buffer might be falsy.
 916  return angular.isString(chipBuffer) ? chipBuffer : '';
 917};
 918
 919/**
 920 * Resets the input buffer for either the internal input or user provided input element.
 921 */
 922MdChipsCtrl.prototype.resetChipBuffer = function() {
 923  if (this.userInputElement) {
 924    if (this.userInputNgModelCtrl) {
 925      this.userInputNgModelCtrl.$setViewValue('');
 926      this.userInputNgModelCtrl.$render();
 927    } else {
 928      this.userInputElement[0].value = '';
 929    }
 930  } else {
 931    this.chipBuffer = '';
 932  }
 933};
 934
 935MdChipsCtrl.prototype.hasMaxChipsReached = function() {
 936  if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
 937
 938  return this.maxChips > 0 && this.items.length >= this.maxChips;
 939};
 940
 941/**
 942 * Updates the validity properties for the ngModel.
 943 */
 944MdChipsCtrl.prototype.validateModel = function() {
 945  this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());
 946};
 947
 948/**
 949 * Removes the chip at the given index.
 950 * @param index
 951 */
 952MdChipsCtrl.prototype.removeChip = function(index) {
 953  var removed = this.items.splice(index, 1);
 954
 955  // Update model validation
 956  this.ngModelCtrl.$setDirty();
 957  this.validateModel();
 958
 959  if (removed && removed.length && this.useOnRemove && this.onRemove) {
 960    this.onRemove({ '$chip': removed[0], '$index': index });
 961  }
 962};
 963
 964MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
 965  this.removeChip(index);
 966
 967  if (this.autocompleteCtrl) {
 968    // Always hide the autocomplete dropdown before focusing the autocomplete input.
 969    // Wait for the input to move horizontally, because the chip was removed.
 970    // This can lead to an incorrect dropdown position.
 971    this.autocompleteCtrl.hidden = true;
 972    this.$mdUtil.nextTick(this.onFocus.bind(this));
 973  } else {
 974    this.onFocus();
 975  }
 976
 977};
 978/**
 979 * Selects the chip at `index`,
 980 * @param index
 981 */
 982MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
 983  // If we have no chips, or are asked to select a chip before the first, just focus the input
 984  if (!this.items.length || index === -1) {
 985    return this.focusInput();
 986  }
 987
 988  // If we are asked to select a chip greater than the number of chips...
 989  if (index >= this.items.length) {
 990    if (this.readonly) {
 991      // If we are readonly, jump back to the start (because we have no input)
 992      index = 0;
 993    } else {
 994      // If we are not readonly, we should attempt to focus the input
 995      return this.onFocus();
 996    }
 997  }
 998
 999  index = Math.max(index, 0);
1000  index = Math.min(index, this.items.length - 1);
1001
1002  this.selectChip(index);
1003  this.focusChip(index);
1004};
1005
1006MdChipsCtrl.prototype.focusLastChipThenInput = function() {
1007  var ctrl = this;
1008
1009  ctrl.shouldFocusLastChip = false;
1010
1011  ctrl.focusChip(this.items.length - 1);
1012
1013  ctrl.$timeout(function() {
1014    ctrl.focusInput();
1015  }, ctrl.chipAppendDelay);
1016};
1017
1018MdChipsCtrl.prototype.focusInput = function() {
1019  this.selectChip(-1);
1020  this.onFocus();
1021};
1022
1023/**
1024 * Marks the chip at the given index as selected.
1025 * @param index
1026 */
1027MdChipsCtrl.prototype.selectChip = function(index) {
1028  if (index >= -1 && index <= this.items.length) {
1029    this.selectedChip = index;
1030
1031    // Fire the onSelect if provided
1032    if (this.useOnSelect && this.onSelect) {
1033      this.onSelect({'$chip': this.items[index] });
1034    }
1035  } else {
1036    this.$log.warn('Selected Chip index out of bounds; ignoring.');
1037  }
1038};
1039
1040/**
1041 * Selects the chip at `index` and gives it focus.
1042 * @param index
1043 */
1044MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
1045  this.selectChip(index);
1046  if (index != -1) {
1047    this.focusChip(index);
1048  }
1049};
1050
1051/**
1052 * Call `focus()` on the chip at `index`
1053 */
1054MdChipsCtrl.prototype.focusChip = function(index) {
1055  var chipContent = this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content');
1056
1057  this.ariaTabIndex = index;
1058
1059  chipContent.focus();
1060};
1061
1062/**
1063 * Configures the required interactions with the ngModel Controller.
1064 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
1065 * @param ngModelCtrl
1066 */
1067MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
1068  this.ngModelCtrl = ngModelCtrl;
1069
1070  var self = this;
1071  ngModelCtrl.$render = function() {
1072    // model is updated. do something.
1073    self.items = self.ngModelCtrl.$viewValue;
1074  };
1075};
1076
1077MdChipsCtrl.prototype.onFocus = function () {
1078  var input = this.$element[0].querySelector('input');
1079  input && input.focus();
1080  this.resetSelectedChip();
1081};
1082
1083MdChipsCtrl.prototype.onInputFocus = function () {
1084  this.inputHasFocus = true;
1085
1086  // Make sure we have the appropriate ARIA attributes
1087  this.setupInputAria();
1088
1089  // Make sure we don't have any chips selected
1090  this.resetSelectedChip();
1091};
1092
1093MdChipsCtrl.prototype.onInputBlur = function () {
1094  this.inputHasFocus = false;
1095
1096  if (this.shouldAddOnBlur()) {
1097    this.appendChip(this.getChipBuffer().trim());
1098    this.resetChipBuffer();
1099  }
1100};
1101
1102/**
1103 * Configure event bindings on a user-provided input element.
1104 * @param inputElement
1105 */
1106MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
1107  this.userInputElement = inputElement;
1108
1109  // Find the NgModelCtrl for the input element
1110  var ngModelCtrl = inputElement.controller('ngModel');
1111  // `.controller` will look in the parent as well.
1112  if (ngModelCtrl != this.ngModelCtrl) {
1113    this.userInputNgModelCtrl = ngModelCtrl;
1114  }
1115
1116  var scope = this.$scope;
1117  var ctrl = this;
1118
1119  // Run all of the events using evalAsync because a focus may fire a blur in the same digest loop
1120  var scopeApplyFn = function(event, fn) {
1121    scope.$evalAsync(angular.bind(ctrl, fn, event));
1122  };
1123
1124  // Bind to keydown and focus events of input
1125  inputElement
1126      .attr({ tabindex: 0 })
1127      .on('keydown', function(event) { scopeApplyFn(event, ctrl.inputKeydown) })
1128      .on('focus', function(event) { scopeApplyFn(event, ctrl.onInputFocus) })
1129      .on('blur', function(event) { scopeApplyFn(event, ctrl.onInputBlur) })
1130};
1131
1132MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
1133  if (ctrl) {
1134    this.autocompleteCtrl = ctrl;
1135
1136    ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
1137      if (item) {
1138        // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
1139        if (this.hasMaxChipsReached()) return;
1140
1141        this.appendChip(item);
1142        this.resetChipBuffer();
1143      }
1144    }));
1145
1146    this.$element.find('input')
1147        .on('focus',angular.bind(this, this.onInputFocus) )
1148        .on('blur', angular.bind(this, this.onInputBlur) );
1149  }
1150};
1151
1152/**
1153 * Whether the current chip buffer should be added on input blur or not.
1154 * @returns {boolean}
1155 */
1156MdChipsCtrl.prototype.shouldAddOnBlur = function() {
1157
1158  // Update the custom ngModel validators from the chips component.
1159  this.validateModel();
1160
1161  var chipBuffer = this.getChipBuffer().trim();
1162  var isModelValid = this.ngModelCtrl.$valid;
1163  var isAutocompleteShowing = this.autocompleteCtrl && !this.autocompleteCtrl.hidden;
1164
1165  if (this.userInputNgModelCtrl) {
1166    isModelValid = isModelValid && this.userInputNgModelCtrl.$valid;
1167  }
1168
1169  return this.addOnBlur && !this.requireMatch && chipBuffer && isModelValid && !isAutocompleteShowing;
1170};
1171
1172MdChipsCtrl.prototype.hasFocus = function () {
1173  return this.inputHasFocus || this.selectedChip >= 0;
1174};
1175
1176MdChipsCtrl.prototype.contentIdFor = function(index) {
1177  return this.contentIds[index];
1178};
1179
1180  
1181  MdChips['$inject'] = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular
1182      .module('material.components.chips')
1183      .directive('mdChips', MdChips);
1184
1185  /**
1186   * @ngdoc directive
1187   * @name mdChips
1188   * @module material.components.chips
1189   *
1190   * @description
1191   * `<md-chips>` is an input component for building lists of strings or objects. The list items are
1192   * displayed as 'chips'. This component can make use of an `<input>` element or an 
1193   * `<md-autocomplete>` element.
1194   *
1195   * ### Custom templates
1196   * A custom template may be provided to render the content of each chip. This is achieved by
1197   * specifying an `<md-chip-template>` element containing the custom content as a child of
1198   * `<md-chips>`.
1199   *
1200   * Note: Any attributes on
1201   * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
1202   * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
1203   * the chip object and its index in the list of chips, respectively.
1204   * To override the chip delete control, include an element (ideally a button) with the attribute
1205   * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
1206   * is also placed as a sibling to the chip content (on which there are also click listeners) to
1207   * avoid a nested ng-click situation.
1208   *
1209   * <!-- Note: We no longer want to include this in the site docs; but it should remain here for
1210   * future developers and those looking at the documentation.
1211   *
1212   * <h3> Pending Features </h3>
1213   * <ul style="padding-left:20px;">
1214   *
1215   *   <ul>Style
1216   *     <li>Colours for hover, press states (ripple?).</li>
1217   *   </ul>
1218   *
1219   *   <ul>Validation
1220   *     <li>allow a validation callback</li>
1221   *     <li>hilighting style for invalid chips</li>
1222   *   </ul>
1223   *
1224   *   <ul>Item mutation
1225   *     <li>Support `
1226   *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
1227   *       click?
1228   *     </li>
1229   *   </ul>
1230   *
1231   *   <ul>Truncation and Disambiguation (?)
1232   *     <li>Truncate chip text where possible, but do not truncate entries such that two are
1233   *     indistinguishable.</li>
1234   *   </ul>
1235   *
1236   *   <ul>Drag and Drop
1237   *     <li>Drag and drop chips between related `<md-chips>` elements.
1238   *     </li>
1239   *   </ul>
1240   * </ul>
1241   *
1242   * //-->
1243   *
1244   * Sometimes developers want to limit the amount of possible chips.<br/>
1245   * You can specify the maximum amount of chips by using the following markup.
1246   *
1247   * <hljs lang="html">
1248   *   <md-chips
1249   *       ng-model="myItems"
1250   *       placeholder="Add an item"
1251   *       md-max-chips="5">
1252   *   </md-chips>
1253   * </hljs>
1254   *
1255   * In some cases, you have an autocomplete inside of the `md-chips`.<br/>
1256   * When the maximum amount of chips has been reached, you can also disable the autocomplete selection.<br/>
1257   * Here is an example markup.
1258   *
1259   * <hljs lang="html">
1260   *   <md-chips ng-model="myItems" md-max-chips="5">
1261   *     <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>
1262   *   </md-chips>
1263   * </hljs>
1264   *
1265   * ### Accessibility
1266   *
1267   * The `md-chips` component supports keyboard and screen reader users since Version 1.1.2. In
1268   * order to achieve this, we modified the chips behavior to select newly appended chips for
1269   * `300ms` before re-focusing the input and allowing the user to type.
1270   *
1271   * For most users, this delay is small enough that it will not be noticeable but allows certain
1272   * screen readers to function properly (JAWS and NVDA in particular).
1273   *
1274   * We introduced a new `md-chip-append-delay` option to allow developers to better control this
1275   * behavior.
1276   *
1277   * Please refer to the documentation of this option (below) for more information.
1278   *
1279   * @param {string=|object=} ng-model A model to which the list of items will be bound.
1280   * @param {string=} placeholder Placeholder text that will be forwarded to the input.
1281   * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
1282   *    displayed when there is at least one item in the list
1283   * @param {boolean=} md-removable Enables or disables the deletion of chips through the
1284   *    removal icon or the Delete/Backspace key. Defaults to true.
1285   * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
1286   *    the input and delete buttons. If no `ng-model` is provided, the chips will automatically be
1287   *    marked as readonly.<br/><br/>
1288   *    When `md-removable` is not defined, the `md-remove` behavior will be overwritten and disabled.
1289   * @param {string=} md-enable-chip-edit Set this to "true" to enable editing of chip contents. The user can 
1290   *    go into edit mode with pressing "space", "enter", or double clicking on the chip. Chip edit is only
1291   *    supported for chips with basic template.
1292   * @param {number=} md-max-chips The maximum number of chips allowed to add through user input.
1293   *    <br/><br/>The validation property `md-max-chips` can be used when the max chips
1294   *    amount is reached.
1295   * @param {boolean=} md-add-on-blur When set to true, remaining text inside of the input will
1296   *    be converted into a new chip on blur.
1297   * @param {expression} md-transform-chip An expression of form `myFunction($chip)` that when called
1298   *    expects one of the following return values:
1299   *    - an object representing the `$chip` input string
1300   *    - `undefined` to simply add the `$chip` input string, or
1301   *    - `null` to prevent the chip from being appended
1302   * @param {expression=} md-on-add An expression which will be called when a chip has been
1303   *    added.
1304   * @param {expression=} md-on-remove An expression which will be called when a chip has been
1305   *    removed.
1306   * @param {expression=} md-on-select An expression which will be called when a chip is selected.
1307   * @param {boolean} md-require-match If true, and the chips template contains an autocomplete,
1308   *    only allow selection of pre-defined chips (i.e. you cannot add new ones).
1309   * @param {string=} input-aria-label A string read by screen readers to identify the input.
1310   * @param {string=} container-hint A string read by screen readers informing users of how to
1311   *    navigate the chips. Used in readonly mode.
1312   * @param {string=} delete-hint A string read by screen readers instructing users that pressing
1313   *    the delete key will remove the chip.
1314   * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
1315   *    screen readers.
1316   * @param {expression=} md-separator-keys An array of key codes used to separate chips.
1317   * @param {string=} md-chip-append-delay The number of milliseconds that the component will select
1318   *    a newly appended chip before allowing a user to type into the input. This is **necessary**
1319   *    for keyboard accessibility for screen readers. It defaults to 300ms and any number less than
1320   *    300 can cause issues with screen readers (particularly JAWS and sometimes NVDA).
1321   *
1322   *    _Available since Version 1.1.2._
1323   *
1324   *    **Note:** You can safely set this to `0` in one of the following two instances:
1325   *
1326   *    1. You are targeting an iOS or Safari-only application (where users would use VoiceOver) or
1327   *    only ChromeVox users.
1328   *
1329   *    2. If you have utilized the `md-separator-keys` to disable the `enter` keystroke in
1330   *    favor of another one (such as `,` or `;`).
1331   *
1332   * @usage
1333   * <hljs lang="html">
1334   *   <md-chips
1335   *       ng-model="myItems"
1336   *       placeholder="Add an item"
1337   *       readonly="isReadOnly">
1338   *   </md-chips>
1339   * </hljs>
1340   *
1341   * <h3>Validation</h3>
1342   * When using [ngMessages](https://docs.angularjs.org/api/ngMessages), you can show errors based
1343   * on our custom validators.
1344   * <hljs lang="html">
1345   *   <form name="userForm">
1346   *     <md-chips
1347   *       name="fruits"
1348   *       ng-model="myItems"
1349   *       placeholder="Add an item"
1350   *       md-max-chips="5">
1351   *     </md-chips>
1352   *     <div ng-messages="userForm.fruits.$error" ng-if="userForm.$dirty">
1353   *       <div ng-message="md-max-chips">You reached the maximum amount of chips</div>
1354   *    </div>
1355   *   </form>
1356   * </hljs>
1357   *
1358   */
1359
1360  var MD_CHIPS_TEMPLATE = '\
1361      <md-chips-wrap\
1362          id="{{$mdChipsCtrl.wrapperId}}"\
1363          tabindex="{{$mdChipsCtrl.readonly ? 0 : -1}}"\
1364          ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
1365          ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus(), \
1366                      \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly,\
1367                      \'md-removable\': $mdChipsCtrl.isRemovable() }"\
1368          aria-setsize="{{$mdChipsCtrl.items.length}}"\
1369          class="md-chips">\
1370        <span ng-if="$mdChipsCtrl.readonly" class="md-visually-hidden">\
1371          {{$mdChipsCtrl.containerHint}}\
1372        </span>\
1373        <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
1374            index="{{$index}}"\
1375            ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index, \'md-readonly\': !$mdChipsCtrl.ngModelCtrl || $mdChipsCtrl.readonly}">\
1376          <div class="md-chip-content"\
1377              tabindex="{{$mdChipsCtrl.ariaTabIndex == $index ? 0 : -1}}"\
1378              id="{{$mdChipsCtrl.contentIdFor($index)}}"\
1379              role="option"\
1380              aria-selected="{{$mdChipsCtrl.selectedChip == $index}}" \
1381              aria-posinset="{{$index}}"\
1382              ng-click="!$mdChipsCtrl.readonly && $mdChipsCtrl.focusChip($index)"\
1383              ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
1384              md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
1385          <div ng-if="$mdChipsCtrl.isRemovable()"\
1386               class="md-chip-remove-container"\
1387               tabindex="-1"\
1388               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
1389        </md-chip>\
1390        <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\
1391          <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
1392        </div>\
1393      </md-chips-wrap>';
1394
1395  var CHIP_INPUT_TEMPLATE = '\
1396        <input\
1397            class="md-input"\
1398            tabindex="0"\
1399            aria-label="{{$mdChipsCtrl.inputAriaLabel}}" \
1400            placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
1401            ng-model="$mdChipsCtrl.chipBuffer"\
1402            ng-focus="$mdChipsCtrl.onInputFocus()"\
1403            ng-blur="$mdChipsCtrl.onInputBlur()"\
1404            ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
1405
1406  var CHIP_DEFAULT_TEMPLATE = '\
1407      <span>{{$chip}}</span>';
1408
1409  var CHIP_REMOVE_TEMPLATE = '\
1410      <button\
1411          class="md-chip-remove"\
1412          ng-if="$mdChipsCtrl.isRemovable()"\
1413          ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
1414          type="button"\
1415          tabindex="-1">\
1416        <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\
1417        <span class="md-visually-hidden">\
1418          {{$mdChipsCtrl.deleteButtonLabel}}\
1419        </span>\
1420      </button>';
1421
1422  /**
1423   * MDChips Directive Definition
1424   */
1425  function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout, $$mdSvgRegistry) {
1426    // Run our templates through $mdUtil.processTemplate() to allow custom start/end symbols
1427    var templates = getTemplates();
1428
1429    return {
1430      template: function(element, attrs) {
1431        // Clone the element into an attribute. By prepending the attribute
1432        // name with '$', Angular won't write it into the DOM. The cloned
1433        // element propagates to the link function via the attrs argument,
1434        // where various contained-elements can be consumed.
1435        attrs['$mdUserTemplate'] = element.clone();
1436        return templates.chips;
1437      },
1438      require: ['mdChips'],
1439      restrict: 'E',
1440      controller: 'MdChipsCtrl',
1441      controllerAs: '$mdChipsCtrl',
1442      bindToController: true,
1443      compile: compile,
1444      scope: {
1445        readonly: '=readonly',
1446        removable: '=mdRemovable',
1447        placeholder: '@',
1448        secondaryPlaceholder: '@',
1449        maxChips: '@mdMaxChips',
1450        transformChip: '&mdTransformChip',
1451        onAppend: '&mdOnAppend',
1452        onAdd: '&mdOnAdd',
1453        onRemove: '&mdOnRemove',
1454        onSelect: '&mdOnSelect',
1455        inputAriaLabel: '@',
1456        containerHint: '@',
1457        deleteHint: '@',
1458        deleteButtonLabel: '@',
1459        separatorKeys: '=?mdSeparatorKeys',
1460        requireMatch: '=?mdRequireMatch',
1461        chipAppendDelayString: '@?mdChipAppendDelay'
1462      }
1463    };
1464
1465    /**
1466     * Builds the final template for `md-chips` and returns the postLink function.
1467     *
1468     * Building the template involves 3 key components:
1469     * static chips
1470     * chip template
1471     * input control
1472     *
1473     * If no `ng-model` is provided, only the static chip work needs to be done.
1474     *
1475     * If no user-passed `md-chip-template` exists, the default template is used. This resulting
1476     * template is appended to the chip content element.
1477     *
1478     * The remove button may be overridden by passing an element with an md-chip-remove attribute.
1479     *
1480     * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
1481     * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
1482     * If no user input is provided, a default one is appended to the input container node in the
1483     * template.
1484     *
1485     * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
1486     * transclusion in the `postLink` function.
1487     *
1488     *
1489     * @param element
1490     * @param attr
1491     * @returns {Function}
1492     */
1493    function compile(element, attr) {
1494      // Grab the user template from attr and reset the attribute to null.
1495      var userTemplate = attr['$mdUserTemplate'];
1496      attr['$mdUserTemplate'] = null;
1497
1498      var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
1499
1500      var chipRemoveSelector = $mdUtil
1501        .prefixer()
1502        .buildList('md-chip-remove')
1503        .map(function(attr) {
1504          return 'md-chips>*[' + attr + ']';
1505        })
1506        .join(',');
1507
1508      // Set the chip remove, chip contents and chip input templates. The link function will put
1509      // them on the scope for transclusion later.
1510      var chipRemoveTemplate   = getTemplateByQuery(chipRemoveSelector) || templates.remove,
1511          chipContentsTemplate = chipTemplate || templates.default,
1512          chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
1513              || getTemplateByQuery('md-chips>input')
1514              || templates.input,
1515          staticChips = userTemplate.find('md-chip');
1516
1517      // Warn of malformed template. See #2545
1518      if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
1519        $log.warn('invalid placement of md-chip-remove within md-chip-template.');
1520      }
1521
1522      function getTemplateByQuery (query) {
1523        if (!attr.ngModel) return;
1524        var element = userTemplate[0].querySelector(query);
1525        return element && element.outerHTML;
1526      }
1527
1528      /**
1529       * Configures controller and transcludes.
1530       */
1531      return function postLink(scope, element, attrs, controllers) {
1532        $mdUtil.initOptionalProperties(scope, attr);
1533
1534        $mdTheming(element);
1535        var mdChipsCtrl = controllers[0];
1536        if(chipTemplate) {
1537          // Chip editing functionality assumes we are using the default chip template.
1538          mdChipsCtrl.enableChipEdit = false;
1539        }
1540
1541        mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
1542        mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
1543        mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
1544
1545        mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;
1546
1547        element
1548            .attr({ tabindex: -1 })
1549            .on('focus', function () { mdChipsCtrl.onFocus(); });
1550
1551        if (attr.ngModel) {
1552          mdChipsCtrl.configureNgModel(element.controller('ngModel'));
1553
1554          // If an `md-transform-chip` attribute was set, tell the controller to use the expression
1555          // before appending chips.
1556          if (attrs.mdTransformChip) mdChipsCtrl.useTransformChipExpression();
1557
1558          // If an `md-on-append` attribute was set, tell the controller to use the expression
1559          // when appending chips.
1560          //
1561          // DEPRECATED: Will remove in official 1.0 release
1562          if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();
1563
1564          // If an `md-on-add` attribute was set, tell the controller to use the expression
1565          // when adding chips.
1566          if (attrs.mdOnAdd) mdChipsCtrl.useOnAddExpression();
1567
1568          // If an `md-on-remove` attribute was set, tell the controller to use the expression
1569          // when removing chips.
1570          if (attrs.mdOnRemove) mdChipsCtrl.useOnRemoveExpression();
1571
1572          // If an `md-on-select` attribute was set, tell the controller to use the expression
1573          // when selecting chips.
1574          if (attrs.mdOnSelect) mdChipsCtrl.useOnSelectExpression();
1575
1576          // The md-autocomplete and input elements won't be compiled until after this directive
1577          // is complete (due to their nested nature). Wait a tick before looking for them to
1578          // configure the controller.
1579          if (chipInputTemplate != templates.input) {
1580            // The autocomplete will not appear until the readonly attribute is not true (i.e.
1581            // false or undefined), so we have to watch the readonly and then on the next tick
1582            // after the chip transclusion has run, we can configure the autocomplete and user
1583            // input.
1584            scope.$watch('$mdChipsCtrl.readonly', function(readonly) {
1585              if (!readonly) {
1586
1587                $mdUtil.nextTick(function(){
1588
1589                  if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {
1590                    var autocompleteEl = element.find('md-autocomplete');
1591                    mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));
1592                  }
1593
1594                  mdChipsCtrl.configureUserInput(element.find('input'));
1595                });
1596              }
1597            });
1598          }
1599
1600          // At the next tick, if we find an input, make sure it has the md-input class
1601          $mdUtil.nextTick(function() {
1602            var input = element.find('input');
1603
1604            input && input.toggleClass('md-input', true);
1605          });
1606        }
1607
1608        // Compile with the parent's scope and prepend any static chips to the wrapper.
1609        if (staticChips.length > 0) {
1610          var compiledStaticChips = $compile(staticChips.clone())(scope.$parent);
1611          $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
1612        }
1613      };
1614    }
1615
1616    function getTemplates() {
1617      return {
1618        chips: $mdUtil.processTemplate(MD_CHIPS_TEMPLATE),
1619        input: $mdUtil.processTemplate(CHIP_INPUT_TEMPLATE),
1620        default: $mdUtil.processTemplate(CHIP_DEFAULT_TEMPLATE),
1621        remove: $mdUtil.processTemplate(CHIP_REMOVE_TEMPLATE)
1622      };
1623    }
1624  }
1625
1626angular
1627    .module('material.components.chips')
1628    .controller('MdContactChipsCtrl', MdContactChipsCtrl);
1629
1630
1631
1632/**
1633 * Controller for the MdContactChips component
1634 * @constructor
1635 */
1636function MdContactChipsCtrl () {
1637  /** @type {Object} */
1638  this.selectedItem = null;
1639
1640  /** @type {string} */
1641  this.searchText = '';
1642}
1643
1644
1645MdContactChipsCtrl.prototype.queryContact = function(searchText) {
1646  return this.contactQuery({'$query': searchText});
1647};
1648
1649
1650MdContactChipsCtrl.prototype.itemName = function(item) {
1651  return item[this.contactName];
1652};
1653
1654
1655MdContactChips['$inject'] = ["$mdTheming", "$mdUtil"];angular
1656  .module('material.components.chips')
1657  .directive('mdContactChips', MdContactChips);
1658
1659/**
1660 * @ngdoc directive
1661 * @name mdContactChips
1662 * @module material.components.chips
1663 *
1664 * @description
1665 * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
1666 * `md-autocomplete` element. The component allows the caller to supply a query expression which
1667 * returns  a list of possible contacts. The user can select one of these and add it to the list of
1668 * chips.
1669 *
1670 * You may also use the `md-highlight-text` directive along with its parameters to control the
1671 * appearance of the matched text inside of the contacts' autocomplete popup.
1672 *
1673 * @param {string=|object=} ng-model A model to bind the list of items to
1674 * @param {string=} placeholder Placeholder text that will be forwarded to the input.
1675 * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
1676 *    displayed when there is at least on item in the list
1677 * @param {expression} md-contacts An expression expected to return contacts matching the search
1678 *    test, `$query`. If this expression involves a promise, a loading bar is displayed while
1679 *    waiting for it to resolve.
1680 * @param {string} md-contact-name The field name of the contact object representing the
1681 *    contact's name.
1682 * @param {string} md-contact-email The field name of the contact object representing the
1683 *    contact's email address.
1684 * @param {string} md-contact-image The field name of the contact object representing the
1685 *    contact's image.
1686 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
1687 *    make suggestions
1688 *
1689 * @param {expression=} filter-selected Whether to filter selected contacts from the list of
1690 *    suggestions shown in the autocomplete.
1691 *
1692 *    ***Note:** This attribute has been removed but may come back.*
1693 *
1694 *
1695 *
1696 * @usage
1697 * <hljs lang="html">
1698 *   <md-contact-chips
1699 *       ng-model="ctrl.contacts"
1700 *       md-contacts="ctrl.querySearch($query)"
1701 *       md-contact-name="name"
1702 *       md-contact-image="image"
1703 *       md-contact-email="email"
1704 *       placeholder="To">
1705 *   </md-contact-chips>
1706 * </hljs>
1707 *
1708 */
1709
1710
1711var MD_CONTACT_CHIPS_TEMPLATE = '\
1712      <md-chips class="md-contact-chips"\
1713          ng-model="$mdContactChipsCtrl.contacts"\
1714          md-require-match="$mdContactChipsCtrl.requireMatch"\
1715          md-chip-append-delay="{{$mdContactChipsCtrl.chipAppendDelay}}" \
1716          md-autocomplete-snap>\
1717          <md-autocomplete\
1718              md-menu-class="md-contact-chips-suggestions"\
1719              md-selected-item="$mdContactChipsCtrl.selectedItem"\
1720              md-search-text="$mdContactChipsCtrl.searchText"\
1721              md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
1722              md-item-text="$mdContactChipsCtrl.itemName(item)"\
1723              md-no-cache="true"\
1724              md-min-length="$mdContactChipsCtrl.minLength"\
1725              md-autoselect\
1726              placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
1727                  $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
1728            <div class="md-contact-suggestion">\
1729              <img \
1730                  ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
1731                  alt="{{item[$mdContactChipsCtrl.contactName]}}"\
1732                  ng-if="item[$mdContactChipsCtrl.contactImage]" />\
1733              <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText"\
1734                    md-highlight-flags="{{$mdContactChipsCtrl.highlightFlags}}">\
1735                {{item[$mdContactChipsCtrl.contactName]}}\
1736              </span>\
1737              <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
1738            </div>\
1739          </md-autocomplete>\
1740          <md-chip-template>\
1741            <div class="md-contact-avatar">\
1742              <img \
1743                  ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
1744                  alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\
1745                  ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\
1746            </div>\
1747            <div class="md-contact-name">\
1748              {{$chip[$mdContactChipsCtrl.contactName]}}\
1749            </div>\
1750          </md-chip-template>\
1751      </md-chips>';
1752
1753
1754/**
1755 * MDContactChips Directive Definition
1756 *
1757 * @param $mdTheming
1758 * @returns {*}
1759 * ngInject
1760 */
1761function MdContactChips($mdTheming, $mdUtil) {
1762  return {
1763    template: function(element, attrs) {
1764      return MD_CONTACT_CHIPS_TEMPLATE;
1765    },
1766    restrict: 'E',
1767    controller: 'MdContactChipsCtrl',
1768    controllerAs: '$mdContactChipsCtrl',
1769    bindToController: true,
1770    compile: compile,
1771    scope: {
1772      contactQuery: '&mdContacts',
1773      placeholder: '@',
1774      secondaryPlaceholder: '@',
1775      contactName: '@mdContactName',
1776      contactImage: '@mdContactImage',
1777      contactEmail: '@mdContactEmail',
1778      contacts: '=ngModel',
1779      requireMatch: '=?mdRequireMatch',
1780      minLength: '=?mdMinLength',
1781      highlightFlags: '@?mdHighlightFlags',
1782      chipAppendDelay: '@?mdChipAppendDelay'
1783    }
1784  };
1785
1786  function compile(element, attr) {
1787    return function postLink(scope, element, attrs, controllers) {
1788      var contactChipsController = controllers;
1789
1790      $mdUtil.initOptionalProperties(scope, attr);
1791      $mdTheming(element);
1792
1793      element.attr('tabindex', '-1');
1794
1795      attrs.$observe('mdChipAppendDelay', function(newValue) {
1796        contactChipsController.chipAppendDelay = newValue;
1797      });
1798    };
1799  }
1800}
1801
1802})(window, window.angular);