79ce4c27444a80fd551f40af10fc82b0a02ac188
[vnfsdk/refrepo.git] /
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v1.1.3
6  */
7 goog.provide('ngmaterial.components.chips');
8 goog.require('ngmaterial.components.autocomplete');
9 goog.require('ngmaterial.core');
10 /**
11  * @ngdoc module
12  * @name material.components.chips
13  */
14 /*
15  * @see js folder for chips implementation
16  */
17 angular.module('material.components.chips', [
18   'material.core',
19   'material.components.autocomplete'
20 ]);
21
22
23 MdChipCtrl['$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  */
38 function 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  */
84 MdChipCtrl.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  */
99 MdChipCtrl.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  */
108 MdChipCtrl.prototype.getContentElement = function() {
109   return angular.element(this.getChipContent().children()[0]);
110 };
111
112
113 /**
114  * @return {number}
115  */
116 MdChipCtrl.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  */
124 MdChipCtrl.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  */
154 MdChipCtrl.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  */
173 MdChipCtrl.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  */
191 MdChipCtrl.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  */
208 MdChipCtrl.prototype.chipMouseDown = function() {
209   if(this.getChipIndex() == this.parentController.selectedChip &&
210     this.enableChipEdit &&
211     !this.isEditting) {
212     this.goInEditMode();
213   }
214 };
215
216
217 MdChip['$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.
240 var 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  */
252 function 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
297 MdChipRemove['$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  */
332 function 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
357 MdChipTransclude['$inject'] = ["$compile"];angular
358     .module('material.components.chips')
359     .directive('mdChipTransclude', MdChipTransclude);
360
361 function 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  */
388 MdChipsCtrl['$inject'] = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];
389 var DEFAULT_CHIP_APPEND_DELAY = 300;
390
391 angular
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  */
409 function 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  */
552 MdChipsCtrl.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  */
573 MdChipsCtrl.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  */
588 MdChipsCtrl.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  */
617 MdChipsCtrl.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  */
667 MdChipsCtrl.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  */
690 MdChipsCtrl.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  */
702 MdChipsCtrl.prototype.isEditingChip = function() {
703   return !!this.$element[0].querySelector('._md-chip-editing');
704 };
705
706
707 MdChipsCtrl.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  */
722 MdChipsCtrl.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  */
762 MdChipsCtrl.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  */
773 MdChipsCtrl.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  */
795 MdChipsCtrl.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  */
809 MdChipsCtrl.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  */
821 MdChipsCtrl.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  */
865 MdChipsCtrl.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  */
876 MdChipsCtrl.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  */
887 MdChipsCtrl.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  */
898 MdChipsCtrl.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  */
910 MdChipsCtrl.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  */
922 MdChipsCtrl.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
935 MdChipsCtrl.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  */
944 MdChipsCtrl.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  */
952 MdChipsCtrl.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
964 MdChipsCtrl.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  */
982 MdChipsCtrl.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
1006 MdChipsCtrl.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
1018 MdChipsCtrl.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  */
1027 MdChipsCtrl.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  */
1044 MdChipsCtrl.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  */
1054 MdChipsCtrl.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  */
1067 MdChipsCtrl.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
1077 MdChipsCtrl.prototype.onFocus = function () {
1078   var input = this.$element[0].querySelector('input');
1079   input && input.focus();
1080   this.resetSelectedChip();
1081 };
1082
1083 MdChipsCtrl.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
1093 MdChipsCtrl.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  */
1106 MdChipsCtrl.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
1132 MdChipsCtrl.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  */
1156 MdChipsCtrl.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
1172 MdChipsCtrl.prototype.hasFocus = function () {
1173   return this.inputHasFocus || this.selectedChip >= 0;
1174 };
1175
1176 MdChipsCtrl.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
1626 angular
1627     .module('material.components.chips')
1628     .controller('MdContactChipsCtrl', MdContactChipsCtrl);
1629
1630
1631
1632 /**
1633  * Controller for the MdContactChips component
1634  * @constructor
1635  */
1636 function MdContactChipsCtrl () {
1637   /** @type {Object} */
1638   this.selectedItem = null;
1639
1640   /** @type {string} */
1641   this.searchText = '';
1642 }
1643
1644
1645 MdContactChipsCtrl.prototype.queryContact = function(searchText) {
1646   return this.contactQuery({'$query': searchText});
1647 };
1648
1649
1650 MdContactChipsCtrl.prototype.itemName = function(item) {
1651   return item[this.contactName];
1652 };
1653
1654
1655 MdContactChips['$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
1711 var 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  */
1761 function 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 ngmaterial.components.chips = angular.module("material.components.chips");