2 * Angular Material Design
3 * https://github.com/angular/material
7 goog.provide('ng.material.components.chips');
8 goog.require('ng.material.components.autocomplete');
9 goog.require('ng.material.core');
12 * @name material.components.chips
15 * @see js folder for chips implementation
17 angular.module('material.components.chips', [
19 'material.components.autocomplete'
23 .module('material.components.chips')
24 .directive('mdChip', MdChip);
29 * @module material.components.chips
32 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
38 * <md-chip>{{$chip}}</md-chip>
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}}\
51 * MDChip Directive Definition
57 function MdChip($mdTheming) {
64 function compile(element, attr) {
65 element.append(DELETE_HINT_TEMPLATE);
66 return function postLink(scope, element, attr, ctrl) {
67 element.addClass('md-chip');
70 if (ctrl) angular.element(element[0].querySelector('.md-chip-content'))
71 .on('blur', function () {
72 ctrl.selectedChip = -1;
77 MdChip.$inject = ["$mdTheming"];
80 .module('material.components.chips')
81 .directive('mdChipRemove', MdChipRemove);
86 * @module material.components.chips
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.
95 * <md-chips><button md-chip-remove>DEL</button></md-chips>
101 * MdChipRemove Directive Definition.
105 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
108 function MdChipRemove ($timeout) {
116 function postLink(scope, element, attr, ctrl) {
117 element.on('click', function(event) {
118 scope.$apply(function() {
119 ctrl.removeChip(scope.$$replacedScope.$index);
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');
131 MdChipRemove.$inject = ["$timeout"];
134 .module('material.components.chips')
135 .directive('mdChipTransclude', MdChipTransclude);
137 function MdChipTransclude ($compile, $mdUtil) {
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);
154 MdChipTransclude.$inject = ["$compile", "$mdUtil"];
157 .module('material.components.chips')
158 .controller('MdChipsCtrl', MdChipsCtrl);
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.
171 function MdChipsCtrl ($scope, $mdConstant, $log, $element, $timeout) {
172 /** @type {$timeout} **/
173 this.$timeout = $timeout;
175 /** @type {Object} */
176 this.$mdConstant = $mdConstant;
178 /** @type {angular.$scope} */
179 this.$scope = $scope;
181 /** @type {angular.$scope} */
182 this.parent = $scope.$parent;
187 /** @type {$element} */
188 this.$element = $element;
190 /** @type {angular.NgModelController} */
191 this.ngModelCtrl = null;
193 /** @type {angular.NgModelController} */
194 this.userInputNgModelCtrl = null;
196 /** @type {Element} */
197 this.userInputElement = null;
199 /** @type {Array.<Object>} */
202 /** @type {number} */
203 this.selectedChip = -1;
207 * Hidden hint text for how to delete a chip. Used to give context to screen readers.
210 this.deleteHint = 'Press delete to remove this chip.';
213 * Hidden label for the delete button. Used to give context to screen readers.
216 this.deleteButtonLabel = 'Remove';
219 * Model used by the input element.
222 this.chipBuffer = '';
225 * Whether to use the mdOnAppend expression to transform the chip buffer
226 * before appending it to the list.
229 this.useMdOnAppend = false;
231 MdChipsCtrl.$inject = ["$scope", "$mdConstant", "$log", "$element", "$timeout"];
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.
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();
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);
257 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
258 * keys switch which chips is active
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);
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);
275 case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
276 event.preventDefault();
277 this.selectAndFocusChipSafe(this.selectedChip + 1);
279 case this.$mdConstant.KEY_CODE.ESCAPE:
280 case this.$mdConstant.KEY_CODE.TAB:
281 if (this.selectedChip < 0) return;
282 event.preventDefault();
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
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;
301 * Removes chip at {@code index} and selects the adjacent chip.
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);
313 * Sets the selected chip index to -1.
315 MdChipsCtrl.prototype.resetSelectedChip = function() {
316 this.selectedChip = -1;
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
326 * If the current chip is not selected, then -1 is returned to select none.
328 MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
329 var len = this.items.length - 1;
330 return (len == 0) ? -1 :
331 (index == len) ? index -1 : index;
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
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});
344 this.items.push(newChip);
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
354 MdChipsCtrl.prototype.useMdOnAppendExpression = function() {
355 this.useMdOnAppend = true;
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}
366 MdChipsCtrl.prototype.getChipBuffer = function() {
367 return !this.userInputElement ? this.chipBuffer :
368 this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
369 this.userInputElement[0].value;
373 * Resets the input buffer for either the internal input or user provided input element.
375 MdChipsCtrl.prototype.resetChipBuffer = function() {
376 if (this.userInputElement) {
377 if (this.userInputNgModelCtrl) {
378 this.userInputNgModelCtrl.$setViewValue('');
379 this.userInputNgModelCtrl.$render();
381 this.userInputElement[0].value = '';
384 this.chipBuffer = '';
389 * Removes the chip at the given index.
392 MdChipsCtrl.prototype.removeChip = function(index) {
393 this.items.splice(index, 1);
396 MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
397 this.removeChip(index);
401 * Selects the chip at `index`,
404 MdChipsCtrl.prototype.selectAndFocusChipSafe = function(index) {
405 if (!this.items.length) {
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);
418 * Marks the chip at the given index as selected.
421 MdChipsCtrl.prototype.selectChip = function(index) {
422 if (index >= -1 && index <= this.items.length) {
423 this.selectedChip = index;
425 this.$log.warn('Selected Chip index out of bounds; ignoring.');
430 * Selects the chip at `index` and gives it focus.
433 MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
434 this.selectChip(index);
436 this.focusChip(index);
441 * Call `focus()` on the chip at `index`
443 MdChipsCtrl.prototype.focusChip = function(index) {
444 this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content').focus();
448 * Configures the required interactions with the ngModel Controller.
449 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
452 MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
453 this.ngModelCtrl = ngModelCtrl;
456 ngModelCtrl.$render = function() {
457 // model is updated. do something.
458 self.items = self.ngModelCtrl.$viewValue;
462 MdChipsCtrl.prototype.onFocus = function () {
463 var input = this.$element[0].querySelector('input');
464 input && input.focus();
465 this.resetSelectedChip();
468 MdChipsCtrl.prototype.onInputFocus = function () {
469 this.inputHasFocus = true;
470 this.resetSelectedChip();
473 MdChipsCtrl.prototype.onInputBlur = function () {
474 this.inputHasFocus = false;
478 * Configure event bindings on a user-provided input element.
479 * @param inputElement
481 MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
482 this.userInputElement = inputElement;
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;
491 // Bind to keydown and focus events of input
492 var scope = this.$scope;
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));
501 MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
503 ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
505 this.appendChip(item);
506 this.resetChipBuffer();
510 this.$element.find('input')
511 .on('focus',angular.bind(this, this.onInputFocus) )
512 .on('blur', angular.bind(this, this.onInputBlur) );
515 MdChipsCtrl.prototype.hasFocus = function () {
516 return this.inputHasFocus || this.selectedChip >= 0;
520 .module('material.components.chips')
521 .directive('mdChips', MdChips);
526 * @module material.components.chips
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.
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.
544 * <h3> Pending Features </h3>
545 * <ul style="padding-left:20px;">
548 * <li>Colours for hover, press states (ripple?).</li>
551 * <ul>List Manipulation
552 * <li>delete item via DEL or backspace keys when selected</li>
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>
563 * <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
568 * <ul>Truncation and Disambiguation (?)
569 * <li>Truncate chip text where possible, but do not truncate entries such that two are
570 * indistinguishable.</li>
574 * <li>Drag and drop chips between related `<md-chips>` elements.
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.
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
601 * placeholder="Add an item"
602 * readonly="isReadOnly">
609 var MD_CHIPS_TEMPLATE = '\
611 ng-if="!$mdChipsCtrl.readonly || $mdChipsCtrl.items.length > 0"\
612 ng-keydown="$mdChipsCtrl.chipKeydown($event)"\
613 ng-class="{ \'md-focused\': $mdChipsCtrl.hasFocus() }"\
615 <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
617 ng-class="{\'md-focused\': $mdChipsCtrl.selectedChip == $index}">\
618 <div class="md-chip-content"\
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>\
626 <div ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl"\
627 class="md-chip-input-container"\
628 md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
632 var CHIP_INPUT_TEMPLATE = '\
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)">';
642 var CHIP_DEFAULT_TEMPLATE = '\
643 <span>{{$chip}}</span>';
645 var CHIP_REMOVE_TEMPLATE = '\
647 class="md-chip-remove"\
648 ng-if="!$mdChipsCtrl.readonly"\
649 ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
653 <md-icon md-svg-icon="md-close"></md-icon>\
654 <span class="md-visually-hidden">\
655 {{$mdChipsCtrl.deleteButtonLabel}}\
660 * MDChips Directive Definition
662 function MdChips ($mdTheming, $mdUtil, $compile, $log, $timeout) {
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;
672 require: ['mdChips'],
674 controller: 'MdChipsCtrl',
675 controllerAs: '$mdChipsCtrl',
676 bindToController: true,
679 readonly: '=readonly',
681 secondaryPlaceholder: '@',
684 deleteButtonLabel: '@',
685 requireMatch: '=?mdRequireMatch'
690 * Builds the final template for `md-chips` and returns the postLink function.
692 * Building the template involves 3 key components:
697 * If no `ng-model` is provided, only the static chip work needs to be done.
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.
702 * The remove button may be overridden by passing an element with an md-chip-remove attribute.
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
709 * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
710 * transclusion in the `postLink` function.
715 * @returns {Function}
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;
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');
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.');
736 function getTemplateByQuery (query) {
737 if (!attr.ngModel) return;
738 var element = userTemplate[0].querySelector(query);
739 return element && element.outerHTML;
743 * Configures controller and transcludes.
745 return function postLink(scope, element, attrs, controllers) {
747 $mdUtil.initOptionalProperties(scope, attr);
750 var mdChipsCtrl = controllers[0];
751 mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
752 mdChipsCtrl.chipRemoveTemplate = chipRemoveTemplate;
753 mdChipsCtrl.chipInputTemplate = chipInputTemplate;
756 .attr({ ariaHidden: true, tabindex: -1 })
757 .on('focus', function () { mdChipsCtrl.onFocus(); });
760 mdChipsCtrl.configureNgModel(element.controller('ngModel'));
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();
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)
773 .configureAutocomplete(element.find('md-autocomplete')
774 .controller('mdAutocomplete'));
775 mdChipsCtrl.configureUserInput(element.find('input'));
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); });
788 MdChips.$inject = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout"];
791 .module('material.components.chips')
792 .controller('MdContactChipsCtrl', MdContactChipsCtrl);
797 * Controller for the MdContactChips component
800 function MdContactChipsCtrl () {
801 /** @type {Object} */
802 this.selectedItem = null;
804 /** @type {string} */
805 this.searchText = '';
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;
816 MdContactChipsCtrl.prototype.filterSelectedContacts = function(contact) {
817 return this.contacts.indexOf(contact) == -1;
821 .module('material.components.chips')
822 .directive('mdContactChips', MdContactChips);
826 * @name mdContactChips
827 * @module material.components.chips
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
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
841 * @param {string} md-contact-name The field name of the contact object representing the
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
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.
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"
864 * </md-contact-chips>
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>\
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"\
883 placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
884 $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
885 <div class="md-contact-suggestion">\
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]}}\
892 <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
896 <div class="md-contact-avatar">\
898 ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
899 alt="{{$chip[$mdContactChipsCtrl.contactName]}}" />\
901 <div class="md-contact-name">\
902 {{$chip[$mdContactChipsCtrl.contactName]}}\
909 * MDContactChips Directive Definition
915 function MdContactChips ($mdTheming, $mdUtil) {
917 template: function(element, attrs) {
918 return MD_CONTACT_CHIPS_TEMPLATE;
921 controller: 'MdContactChipsCtrl',
922 controllerAs: '$mdContactChipsCtrl',
923 bindToController: true,
926 contactQuery: '&mdContacts',
928 secondaryPlaceholder: '@',
929 contactName: '@mdContactName',
930 contactImage: '@mdContactImage',
931 contactEmail: '@mdContactEmail',
932 contacts: '=ngModel',
933 requireMatch: '=?mdRequireMatch'
937 function compile(element, attr) {
938 return function postLink(scope, element, attrs, controllers) {
940 $mdUtil.initOptionalProperties(scope, attr);
943 element.attr('tabindex', '-1');
947 MdContactChips.$inject = ["$mdTheming", "$mdUtil"];
949 ng.material.components.chips = angular.module("material.components.chips");