nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / angular-material / modules / closure / chips / chips.js
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v0.9.8
6  */
7 goog.provide('ng.material.components.chips');
8 goog.require('ng.material.components.autocomplete');
9 goog.require('ng.material.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 angular
23     .module('material.components.chips')
24     .directive('mdChip', MdChip);
25
26 /**
27  * @ngdoc directive
28  * @name mdChip
29  * @module material.components.chips
30  *
31  * @description
32  * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
33  * chips.
34  *
35  *
36  * @usage
37  * <hljs lang="html">
38  *   <md-chip>{{$chip}}</md-chip>
39  * </hljs>
40  *
41  */
42
43 // This hint text is hidden within a chip but used by screen readers to
44 // inform the user how they can interact with a chip.
45 var DELETE_HINT_TEMPLATE = '\
46     <span ng-if="!$mdChipsCtrl.readonly" class="md-visually-hidden">\
47       {{$mdChipsCtrl.deleteHint}}\
48     </span>';
49
50 /**
51  * MDChip Directive Definition
52  *
53  * @param $mdTheming
54  * @param $mdInkRipple
55  * ngInject
56  */
57 function MdChip($mdTheming) {
58   return {
59     restrict: 'E',
60     require: '^?mdChips',
61     compile:  compile
62   };
63
64   function compile(element, attr) {
65     element.append(DELETE_HINT_TEMPLATE);
66     return function postLink(scope, element, attr, ctrl) {
67       element.addClass('md-chip');
68       $mdTheming(element);
69
70       if (ctrl) angular.element(element[0].querySelector('.md-chip-content'))
71           .on('blur', function () {
72             ctrl.selectedChip = -1;
73           });
74     };
75   }
76 }
77 MdChip.$inject = ["$mdTheming"];
78
79 angular
80     .module('material.components.chips')
81     .directive('mdChipRemove', MdChipRemove);
82
83 /**
84  * @ngdoc directive
85  * @name mdChipRemove
86  * @module material.components.chips
87  *
88  * @description
89  * `<md-chip-remove>`
90  * Designates an element to be used as the delete button for a chip. This
91  * element is passed as a child of the `md-chips` element.
92  *
93  * @usage
94  * <hljs lang="html">
95  *   <md-chips><button md-chip-remove>DEL</button></md-chips>
96  * </hljs>
97  */
98
99
100 /**
101  * MdChipRemove Directive Definition.
102  * 
103  * @param $compile
104  * @param $timeout
105  * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
106  * @constructor
107  */
108 function MdChipRemove ($timeout) {
109   return {
110     restrict: 'A',
111     require: '^mdChips',
112     scope: false,
113     link: postLink
114   };
115
116   function postLink(scope, element, attr, ctrl) {
117     element.on('click', function(event) {
118       scope.$apply(function() {
119         ctrl.removeChip(scope.$$replacedScope.$index);
120       });
121     });
122
123     // Child elements aren't available until after a $timeout tick as they are hidden by an
124     // `ng-if`. see http://goo.gl/zIWfuw
125     $timeout(function() {
126       element.attr({ tabindex: -1, ariaHidden: true });
127       element.find('button').attr('tabindex', '-1');
128     });
129   }
130 }
131 MdChipRemove.$inject = ["$timeout"];
132
133 angular
134     .module('material.components.chips')
135     .directive('mdChipTransclude', MdChipTransclude);
136
137 function MdChipTransclude ($compile, $mdUtil) {
138   return {
139     restrict: 'EA',
140     terminal: true,
141     link: link,
142     scope: false
143   };
144   function link (scope, element, attr) {
145     var ctrl = scope.$parent.$mdChipsCtrl,
146         newScope = ctrl.parent.$new(false, ctrl.parent);
147     newScope.$$replacedScope = scope;
148     newScope.$chip = scope.$chip;
149     newScope.$mdChipsCtrl = ctrl;
150     element.html(ctrl.$scope.$eval(attr.mdChipTransclude));
151     $compile(element.contents())(newScope);
152   }
153 }
154 MdChipTransclude.$inject = ["$compile", "$mdUtil"];
155
156 angular
157     .module('material.components.chips')
158     .controller('MdChipsCtrl', MdChipsCtrl);
159
160 /**
161  * Controller for the MdChips component. Responsible for adding to and
162  * removing from the list of chips, marking chips as selected, and binding to
163  * the models of various input components.
164  *
165  * @param $scope
166  * @param $mdConstant
167  * @param $log
168  * @param $element
169  * @constructor
170  */
171 function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout) {
172   /** @type {$timeout} **/
173   this.$timeout = $timeout;
174
175   /** @type {Object} */
176   this.$mdConstant = $mdConstant;
177
178   /** @type {angular.$scope} */
179   this.$scope = $scope;
180
181   /** @type {angular.$scope} */
182   this.parent = $scope.$parent;
183
184   /** @type {$log} */
185   this.$log = $log;
186
187   /** @type {$element} */
188   this.$element = $element;
189
190   /** @type {angular.NgModelController} */
191   this.ngModelCtrl = null;
192
193   /** @type {angular.NgModelController} */
194   this.userInputNgModelCtrl = null;
195
196   /** @type {Element} */
197   this.userInputElement = null;
198
199   /** @type {Array.<Object>} */
200   this.items = [];
201
202   /** @type {number} */
203   this.selectedChip = -1;
204
205
206   /**
207    * Hidden hint text for how to delete a chip. Used to give context to screen readers.
208    * @type {string}
209    */
210   this.deleteHint = 'Press delete to remove this chip.';
211
212   /**
213    * Hidden label for the delete button. Used to give context to screen readers.
214    * @type {string}
215    */
216   this.deleteButtonLabel = 'Remove';
217
218   /**
219    * Model used by the input element.
220    * @type {string}
221    */
222   this.chipBuffer = '';
223
224   /**
225    * Whether to use the mdOnAppend expression to transform the chip buffer
226    * before appending it to the list.
227    * @type {boolean}
228    */
229   this.useMdOnAppend = false;
230 }
231 MdChipsCtrl.$inject = ["$scope", "$mdConstant", "$log", "$element", "$timeout"];
232
233 /**
234  * Handles the keydown event on the input element: <enter> appends the
235  * buffer to the chip list, while backspace removes the last chip in the list
236  * if the current buffer is empty.
237  * @param event
238  */
239 MdChipsCtrl.prototype.inputKeydown = function(event) {
240   var chipBuffer = this.getChipBuffer();
241   switch (event.keyCode) {
242     case this.$mdConstant.KEY_CODE.ENTER:
243       if (this.$scope.requireMatch || !chipBuffer) break;
244       event.preventDefault();
245       this.appendChip(chipBuffer);
246       this.resetChipBuffer();
247       break;
248     case this.$mdConstant.KEY_CODE.BACKSPACE:
249       if (chipBuffer) break;
250       event.stopPropagation();
251       if (this.items.length) this.selectAndFocusChipSafe(this.items.length - 1);
252       break;
253   }
254 };
255
256 /**
257  * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
258  * keys switch which chips is active
259  * @param event
260  */
261 MdChipsCtrl.prototype.chipKeydown = function (event) {
262   if (this.getChipBuffer()) return;
263   switch (event.keyCode) {
264     case this.$mdConstant.KEY_CODE.BACKSPACE:
265     case this.$mdConstant.KEY_CODE.DELETE:
266       if (this.selectedChip < 0) return;
267       event.preventDefault();
268       this.removeAndSelectAdjacentChip(this.selectedChip);
269       break;
270     case this.$mdConstant.KEY_CODE.LEFT_ARROW:
271       event.preventDefault();
272       if (this.selectedChip < 0) this.selectedChip = this.items.length;
273       if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
274       break;
275     case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
276       event.preventDefault();
277       this.selectAndFocusChipSafe(this.selectedChip + 1);
278       break;
279     case this.$mdConstant.KEY_CODE.ESCAPE:
280     case this.$mdConstant.KEY_CODE.TAB:
281       if (this.selectedChip < 0) return;
282       event.preventDefault();
283       this.onFocus();
284       break;
285   }
286 };
287
288 /**
289  * Get the input's placeholder - uses `placeholder` when list is empty and `secondary-placeholder`
290  * when the list is non-empty. If `secondary-placeholder` is not provided, `placeholder` is used
291  * always.
292  */
293 MdChipsCtrl.prototype.getPlaceholder = function() {
294   // Allow `secondary-placeholder` to be blank.
295   var useSecondary = (this.items.length &&
296       (this.secondaryPlaceholder == '' || this.secondaryPlaceholder));
297   return useSecondary ? this.placeholder : this.secondaryPlaceholder;
298 };
299
300 /**
301  * Removes chip at {@code index} and selects the adjacent chip.
302  * @param index
303  */
304 MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
305   var selIndex = this.getAdjacentChipIndex(index);
306   this.removeChip(index);
307   this.$timeout(angular.bind(this, function () {
308       this.selectAndFocusChipSafe(selIndex);
309   }));
310 };
311
312 /**
313  * Sets the selected chip index to -1.
314  */
315 MdChipsCtrl.prototype.resetSelectedChip = function() {
316   this.selectedChip = -1;
317 };
318
319 /**
320  * Gets the index of an adjacent chip to select after deletion. Adjacency is
321  * determined as the next chip in the list, unless the target chip is the
322  * last in the list, then it is the chip immediately preceding the target. If
323  * there is only one item in the list, -1 is returned (select none).
324  * The number returned is the index to select AFTER the target has been
325  * removed.
326  * If the current chip is not selected, then -1 is returned to select none.
327  */
328 MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
329   var len = this.items.length - 1;
330   return (len == 0) ? -1 :
331       (index == len) ? index -1 : index;
332 };
333
334 /**
335  * Append the contents of the buffer to the chip list. This method will first
336  * call out to the md-on-append method, if provided
337  * @param newChip
338  */
339 MdChipsCtrl.prototype.appendChip = function(newChip) {
340   if (this.items.indexOf(newChip) + 1) return;
341   if (this.useMdOnAppend && this.mdOnAppend) {
342     newChip = this.mdOnAppend({'$chip': newChip});
343   }
344   this.items.push(newChip);
345 };
346
347 /**
348  * Sets whether to use the md-on-append expression. This expression is
349  * bound to scope and controller in {@code MdChipsDirective} as
350  * {@code mdOnAppend}. Due to the nature of directive scope bindings, the
351  * controller cannot know on its own/from the scope whether an expression was
352  * actually provided.
353  */
354 MdChipsCtrl.prototype.useMdOnAppendExpression = function() {
355   this.useMdOnAppend = true;
356 };
357
358 /**
359  * Gets the input buffer. The input buffer can be the model bound to the
360  * default input item {@code this.chipBuffer}, the {@code selectedItem}
361  * model of an {@code md-autocomplete}, or, through some magic, the model
362  * bound to any inpput or text area element found within a
363  * {@code md-input-container} element.
364  * @return {Object|string}
365  */
366 MdChipsCtrl.prototype.getChipBuffer = function() {
367   return !this.userInputElement ? this.chipBuffer :
368       this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
369           this.userInputElement[0].value;
370 };
371
372 /**
373  * Resets the input buffer for either the internal input or user provided input element.
374  */
375 MdChipsCtrl.prototype.resetChipBuffer = function() {
376   if (this.userInputElement) {
377     if (this.userInputNgModelCtrl) {
378       this.userInputNgModelCtrl.$setViewValue('');
379       this.userInputNgModelCtrl.$render();
380     } else {
381       this.userInputElement[0].value = '';
382     }
383   } else {
384     this.chipBuffer = '';
385   }
386 };
387
388 /**
389  * Removes the chip at the given index.
390  * @param index
391  */
392 MdChipsCtrl.prototype.removeChip = function(index) {
393   this.items.splice(index, 1);
394 };
395
396 MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
397   this.removeChip(index);
398   this.onFocus();
399 };
400 /**
401  * Selects the chip at `index`,
402  * @param index
403  */
404 MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
405   if (!this.items.length) {
406     this.selectChip(-1);
407     this.onFocus();
408     return;
409   }
410   if (index === this.items.length) return this.onFocus();
411   index = Math.max(index, 0);
412   index = Math.min(index, this.items.length - 1);
413   this.selectChip(index);
414   this.focusChip(index);
415 };
416
417 /**
418  * Marks the chip at the given index as selected.
419  * @param index
420  */
421 MdChipsCtrl.prototype.selectChip = function(index) {
422   if (index >= -1 && index <= this.items.length) {
423     this.selectedChip = index;
424   } else {
425     this.$log.warn('Selected Chip index out of bounds; ignoring.');
426   }
427 };
428
429 /**
430  * Selects the chip at `index` and gives it focus.
431  * @param index
432  */
433 MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
434   this.selectChip(index);
435   if (index != -1) {
436     this.focusChip(index);
437   }
438 };
439
440 /**
441  * Call `focus()` on the chip at `index`
442  */
443 MdChipsCtrl.prototype.focusChip = function(index) {
444   this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content').focus();
445 };
446
447 /**
448  * Configures the required interactions with the ngModel Controller.
449  * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
450  * @param ngModelCtrl
451  */
452 MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
453   this.ngModelCtrl = ngModelCtrl;
454
455   var self = this;
456   ngModelCtrl.$render = function() {
457     // model is updated. do something.
458     self.items = self.ngModelCtrl.$viewValue;
459   };
460 };
461
462 MdChipsCtrl.prototype.onFocus = function () {
463   var input = this.$element[0].querySelector('input');
464   input && input.focus();
465   this.resetSelectedChip();
466 };
467
468 MdChipsCtrl.prototype.onInputFocus = function () {
469   this.inputHasFocus = true;
470   this.resetSelectedChip();
471 };
472
473 MdChipsCtrl.prototype.onInputBlur = function () {
474   this.inputHasFocus = false;
475 };
476
477 /**
478  * Configure event bindings on a user-provided input element.
479  * @param inputElement
480  */
481 MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
482   this.userInputElement = inputElement;
483
484   // Find the NgModelCtrl for the input element
485   var ngModelCtrl = inputElement.controller('ngModel');
486   // `.controller` will look in the parent as well.
487   if (ngModelCtrl != this.ngModelCtrl) {
488     this.userInputNgModelCtrl = ngModelCtrl;
489   }
490
491   // Bind to keydown and focus events of input
492   var scope = this.$scope;
493   var ctrl = this;
494   inputElement
495       .attr({ tabindex: 0 })
496       .on('keydown', function(event) { scope.$apply( angular.bind(ctrl, function() { ctrl.inputKeydown(event); })) })
497       .on('focus', angular.bind(ctrl, ctrl.onInputFocus))
498       .on('blur', angular.bind(ctrl, ctrl.onInputBlur));
499 };
500
501 MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
502
503   ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
504     if (item) {
505       this.appendChip(item);
506       this.resetChipBuffer();
507     }
508   }));
509
510   this.$element.find('input')
511       .on('focus',angular.bind(this, this.onInputFocus) )
512       .on('blur', angular.bind(this, this.onInputBlur) );
513 };
514
515 MdChipsCtrl.prototype.hasFocus = function () {
516   return this.inputHasFocus || this.selectedChip >= 0;
517 };
518
519   angular
520       .module('material.components.chips')
521       .directive('mdChips', MdChips);
522
523   /**
524    * @ngdoc directive
525    * @name mdChips
526    * @module material.components.chips
527    *
528    * @description
529    * `<md-chips>` is an input component for building lists of strings or objects. The list items are
530    * displayed as 'chips'. This component can make use of an `<input>` element or an
531    * `<md-autocomplete>` element.
532    *
533    * <strong>Custom `<md-chip-template>` template</strong>
534    * A custom template may be provided to render the content of each chip. This is achieved by
535    * specifying an `<md-chip-template>` element as a child of `<md-chips>`. Note: Any attributes on
536    * `<md-chip-template>` will be dropped as only the innerHTML is used for the chip template. The
537    * variables `$chip` and `$index` are available in the scope of `<md-chip-template>`, representing
538    * the chip object and its index in the list of chips, respectively.
539    * To override the chip delete control, include an element (ideally a button) with the attribute
540    * `md-chip-remove`. A click listener to remove the chip will be added automatically. The element
541    * is also placed as a sibling to the chip content (on which there are also click listeners) to
542    * avoid a nested ng-click situation.
543    *
544    * <h3> Pending Features </h3>
545    * <ul style="padding-left:20px;">
546    *
547    *   <ul>Style
548    *     <li>Colours for hover, press states (ripple?).</li>
549    *   </ul>
550    *
551    *   <ul>List Manipulation
552    *     <li>delete item via DEL or backspace keys when selected</li>
553    *   </ul>
554    *
555    *   <ul>Validation
556    *     <li>de-dupe values (or support duplicates, but fix the ng-repeat duplicate key issue)</li>
557    *     <li>allow a validation callback</li>
558    *     <li>hilighting style for invalid chips</li>
559    *   </ul>
560    *
561    *   <ul>Item mutation
562    *     <li>Support `
563    *       <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
564    *       click?
565    *     </li>
566    *   </ul>
567    *
568    *   <ul>Truncation and Disambiguation (?)
569    *     <li>Truncate chip text where possible, but do not truncate entries such that two are
570    *     indistinguishable.</li>
571    *   </ul>
572    *
573    *   <ul>Drag and Drop
574    *     <li>Drag and drop chips between related `<md-chips>` elements.
575    *     </li>
576    *   </ul>
577    * </ul>
578    *
579    *  <span style="font-size:.8em;text-align:center">
580    *    Warning: This component is a WORK IN PROGRESS. If you use it now,
581    *    it will probably break on you in the future.
582    *  </span>
583    *
584    * @param {string=|object=} ng-model A model to bind the list of items to
585    * @param {string=} placeholder Placeholder text that will be forwarded to the input.
586    * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
587    *    displayed when there is at least on item in the list
588    * @param {boolean=} readonly Disables list manipulation (deleting or adding list items), hiding
589    *    the input and delete buttons
590    * @param {expression} md-on-append An expression expected to convert the input string into an
591    *    object when adding a chip.
592    * @param {string=} delete-hint A string read by screen readers instructing users that pressing
593    *    the delete key will remove the chip.
594    * @param {string=} delete-button-label A label for the delete button. Also hidden and read by
595    *    screen readers.
596    *
597    * @usage
598    * <hljs lang="html">
599    *   <md-chips
600    *       ng-model="myItems"
601    *       placeholder="Add an item"
602    *       readonly="isReadOnly">
603    *   </md-chips>
604    * </hljs>
605    *
606    */
607
608
609   var MD_CHIPS_TEMPLATE = '\
610       <md-chips-wrap\
611           ng-if="!$mdChipsCtrl.readonly || $mdChipsCtrl.items.length > 0"\
612           ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
613           ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus() }"\
614           class="md-chips">\
615         <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
616             index="{{$index}}"\
617             ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index}">\
618           <div class="md-chip-content"\
619               tabindex="-1"\
620               aria-hidden="true"\
621               ng-focus="!$mdChipsCtrl.readonly && $mdChipsCtrl.selectChip($index)"\
622               md-chip-transclude="$mdChipsCtrl.chipContentsTemplate"></div>\
623           <div class="md-chip-remove-container"\
624               md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
625         </md-chip>\
626         <div ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl"\
627             class="md-chip-input-container"\
628             md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
629         </div>\
630       </md-chips-wrap>';
631
632   var CHIP_INPUT_TEMPLATE = '\
633         <input\
634             tabindex="0"\
635             placeholder="{{$mdChipsCtrl.getPlaceholder()}}"\
636             aria-label="{{$mdChipsCtrl.getPlaceholder()}}"\
637             ng-model="$mdChipsCtrl.chipBuffer"\
638             ng-focus="$mdChipsCtrl.onInputFocus()"\
639             ng-blur="$mdChipsCtrl.onInputBlur()"\
640             ng-keydown="$mdChipsCtrl.inputKeydown($event)">';
641
642   var CHIP_DEFAULT_TEMPLATE = '\
643       <span>{{$chip}}</span>';
644
645   var CHIP_REMOVE_TEMPLATE = '\
646       <button\
647           class="md-chip-remove"\
648           ng-if="!$mdChipsCtrl.readonly"\
649           ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
650           type="button"\
651           aria-hidden="true"\
652           tabindex="-1">\
653         <md-icon md-svg-icon="md-close"></md-icon>\
654         <span class="md-visually-hidden">\
655           {{$mdChipsCtrl.deleteButtonLabel}}\
656         </span>\
657       </button>';
658
659   /**
660    * MDChips Directive Definition
661    */
662   function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout) {
663     return {
664       template: function(element, attrs) {
665         // Clone the element into an attribute. By prepending the attribute
666         // name with '$', Angular won't write it into the DOM. The cloned
667         // element propagates to the link function via the attrs argument,
668         // where various contained-elements can be consumed.
669         var content = attrs['$mdUserTemplate'] = element.clone();
670         return MD_CHIPS_TEMPLATE;
671       },
672       require: ['mdChips'],
673       restrict: 'E',
674       controller: 'MdChipsCtrl',
675       controllerAs: '$mdChipsCtrl',
676       bindToController: true,
677       compile: compile,
678       scope: {
679         readonly: '=readonly',
680         placeholder: '@',
681         secondaryPlaceholder: '@',
682         mdOnAppend: '&',
683         deleteHint: '@',
684         deleteButtonLabel: '@',
685         requireMatch: '=?mdRequireMatch'
686       }
687     };
688
689     /**
690      * Builds the final template for `md-chips` and returns the postLink function.
691      *
692      * Building the template involves 3 key components:
693      * static chips
694      * chip template
695      * input control
696      *
697      * If no `ng-model` is provided, only the static chip work needs to be done.
698      *
699      * If no user-passed `md-chip-template` exists, the default template is used. This resulting
700      * template is appended to the chip content element.
701      *
702      * The remove button may be overridden by passing an element with an md-chip-remove attribute.
703      *
704      * If an `input` or `md-autocomplete` element is provided by the caller, it is set aside for
705      * transclusion later. The transclusion happens in `postLink` as the parent scope is required.
706      * If no user input is provided, a default one is appended to the input container node in the
707      * template.
708      *
709      * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
710      * transclusion in the `postLink` function.
711      *
712      *
713      * @param element
714      * @param attr
715      * @returns {Function}
716      */
717     function compile(element, attr) {
718       // Grab the user template from attr and reset the attribute to null.
719       var userTemplate = attr['$mdUserTemplate'];
720       attr['$mdUserTemplate'] = null;
721
722       // Set the chip remove, chip contents and chip input templates. The link function will put
723       // them on the scope for transclusion later.
724       var chipRemoveTemplate   = getTemplateByQuery('md-chips>*[md-chip-remove]') || CHIP_REMOVE_TEMPLATE,
725           chipContentsTemplate = getTemplateByQuery('md-chips>md-chip-template') || CHIP_DEFAULT_TEMPLATE,
726           chipInputTemplate    = getTemplateByQuery('md-chips>md-autocomplete')
727               || getTemplateByQuery('md-chips>input')
728               || CHIP_INPUT_TEMPLATE,
729           staticChips = userTemplate.find('md-chip');
730
731       // Warn of malformed template. See #2545
732       if (userTemplate[0].querySelector('md-chip-template>*[md-chip-remove]')) {
733         $log.warn('invalid placement of md-chip-remove within md-chip-template.');
734       }
735
736       function getTemplateByQuery (query) {
737         if (!attr.ngModel) return;
738         var element = userTemplate[0].querySelector(query);
739         return element && element.outerHTML;
740       }
741
742       /**
743        * Configures controller and transcludes.
744        */
745       return function postLink(scope, element, attrs, controllers) {
746
747         $mdUtil.initOptionalProperties(scope, attr);
748
749         $mdTheming(element);
750         var mdChipsCtrl = controllers[0];
751         mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
752         mdChipsCtrl.chipRemoveTemplate   = chipRemoveTemplate;
753         mdChipsCtrl.chipInputTemplate    = chipInputTemplate;
754
755         element
756             .attr({ ariaHidden: true, tabindex: -1 })
757             .on('focus', function () { mdChipsCtrl.onFocus(); });
758
759         if (attr.ngModel) {
760           mdChipsCtrl.configureNgModel(element.controller('ngModel'));
761
762           // If an `md-on-append` attribute was set, tell the controller to use the expression
763           // when appending chips.
764           if (attrs.mdOnAppend) mdChipsCtrl.useMdOnAppendExpression();
765
766           // The md-autocomplete and input elements won't be compiled until after this directive
767           // is complete (due to their nested nature). Wait a tick before looking for them to
768           // configure the controller.
769           if (chipInputTemplate != CHIP_INPUT_TEMPLATE) {
770             $timeout(function() {
771               if (chipInputTemplate.indexOf('<md-autocomplete') === 0)
772                 mdChipsCtrl
773                     .configureAutocomplete(element.find('md-autocomplete')
774                         .controller('mdAutocomplete'));
775               mdChipsCtrl.configureUserInput(element.find('input'));
776             });
777           }
778         }
779
780         // Compile with the parent's scope and prepend any static chips to the wrapper.
781         if (staticChips.length > 0) {
782           var compiledStaticChips = $compile(staticChips)(scope.$parent);
783           $timeout(function() { element.find('md-chips-wrap').prepend(compiledStaticChips); });
784         }
785       };
786     }
787   }
788   MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout"];
789
790 angular
791     .module('material.components.chips')
792     .controller('MdContactChipsCtrl', MdContactChipsCtrl);
793
794
795
796 /**
797  * Controller for the MdContactChips component
798  * @constructor
799  */
800 function MdContactChipsCtrl () {
801   /** @type {Object} */
802   this.selectedItem = null;
803
804   /** @type {string} */
805   this.searchText = '';
806 }
807
808
809 MdContactChipsCtrl.prototype.queryContact = function(searchText) {
810   var results = this.contactQuery({'$query': searchText});
811   return this.filterSelected ?
812       results.filter(angular.bind(this, this.filterSelectedContacts)) : results;
813 };
814
815
816 MdContactChipsCtrl.prototype.filterSelectedContacts = function(contact) {
817   return this.contacts.indexOf(contact) == -1;
818 };
819
820   angular
821       .module('material.components.chips')
822       .directive('mdContactChips', MdContactChips);
823
824   /**
825    * @ngdoc directive
826    * @name mdContactChips
827    * @module material.components.chips
828    *
829    * @description
830    * `<md-contact-chips>` is an input component based on `md-chips` and makes use of an
831    *    `md-autocomplete` element. The component allows the caller to supply a query expression
832    *    which returns  a list of possible contacts. The user can select one of these and add it to
833    *    the list of chips.
834    *
835    * @param {string=|object=} ng-model A model to bind the list of items to
836    * @param {string=} placeholder Placeholder text that will be forwarded to the input.
837    * @param {string=} secondary-placeholder Placeholder text that will be forwarded to the input,
838    *    displayed when there is at least on item in the list
839    * @param {expression} md-contacts An expression expected to return contacts matching the search
840    *    test, `$query`.
841    * @param {string} md-contact-name The field name of the contact object representing the
842    *    contact's name.
843    * @param {string} md-contact-email The field name of the contact object representing the
844    *    contact's email address.
845    * @param {string} md-contact-image The field name of the contact object representing the
846    *    contact's image.
847    *
848    *
849    * // The following attribute has been removed but may come back.
850    * @param {expression=} filter-selected Whether to filter selected contacts from the list of
851    *    suggestions shown in the autocomplete.
852    *
853    *
854    *
855    * @usage
856    * <hljs lang="html">
857    *   <md-contact-chips
858    *       ng-model="ctrl.contacts"
859    *       md-contacts="ctrl.querySearch($query)"
860    *       md-contact-name="name"
861    *       md-contact-image="image"
862    *       md-contact-email="email"
863    *       placeholder="To">
864    *   </md-contact-chips>
865    * </hljs>
866    *
867    */
868
869
870   var MD_CONTACT_CHIPS_TEMPLATE = '\
871       <md-chips class="md-contact-chips"\
872           ng-model="$mdContactChipsCtrl.contacts"\
873           md-require-match="$mdContactChipsCtrl.requireMatch"\
874           md-autocomplete-snap>\
875           <md-autocomplete\
876               md-menu-class="md-contact-chips-suggestions"\
877               md-selected-item="$mdContactChipsCtrl.selectedItem"\
878               md-search-text="$mdContactChipsCtrl.searchText"\
879               md-items="item in $mdContactChipsCtrl.queryContact($mdContactChipsCtrl.searchText)"\
880               md-item-text="$mdContactChipsCtrl.mdContactName"\
881               md-no-cache="true"\
882               md-autoselect\
883               placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
884                   $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
885             <div class="md-contact-suggestion">\
886               <img \
887                   ng-src="{{item[$mdContactChipsCtrl.contactImage]}}"\
888                   alt="{{item[$mdContactChipsCtrl.contactName]}}" />\
889               <span class="md-contact-name" md-highlight-text="$mdContactChipsCtrl.searchText">\
890                 {{item[$mdContactChipsCtrl.contactName]}}\
891               </span>\
892               <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
893             </div>\
894           </md-autocomplete>\
895           <md-chip-template>\
896             <div class="md-contact-avatar">\
897               <img \
898                   ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
899                   alt="{{$chip[$mdContactChipsCtrl.contactName]}}" />\
900             </div>\
901             <div class="md-contact-name">\
902               {{$chip[$mdContactChipsCtrl.contactName]}}\
903             </div>\
904           </md-chip-template>\
905       </md-chips>';
906
907
908   /**
909    * MDContactChips Directive Definition
910    *
911    * @param $mdTheming
912    * @returns {*}
913    * ngInject
914    */
915   function MdContactChips ($mdTheming, $mdUtil) {
916     return {
917       template: function(element, attrs) {
918         return MD_CONTACT_CHIPS_TEMPLATE;
919       },
920       restrict: 'E',
921       controller: 'MdContactChipsCtrl',
922       controllerAs: '$mdContactChipsCtrl',
923       bindToController: true,
924       compile: compile,
925       scope: {
926         contactQuery: '&mdContacts',
927         placeholder: '@',
928         secondaryPlaceholder: '@',
929         contactName: '@mdContactName',
930         contactImage: '@mdContactImage',
931         contactEmail: '@mdContactEmail',
932         contacts: '=ngModel',
933         requireMatch: '=?mdRequireMatch'
934       }
935     };
936
937     function compile(element, attr) {
938       return function postLink(scope, element, attrs, controllers) {
939
940         $mdUtil.initOptionalProperties(scope, attr);
941         $mdTheming(element);
942
943         element.attr('tabindex', '-1');
944       };
945     }
946   }
947   MdContactChips.$inject = ["$mdTheming", "$mdUtil"];
948
949 ng.material.components.chips = angular.module("material.components.chips");