2 * Angular Material Design
3 * https://github.com/angular/material
7 goog.provide('ngmaterial.components.chips');
8 goog.require('ngmaterial.components.autocomplete');
9 goog.require('ngmaterial.core');
12 * @name material.components.chips
15 * @see js folder for chips implementation
17 angular.module('material.components.chips', [
19 'material.components.autocomplete'
23 MdChipCtrl['$inject'] = ["$scope", "$element", "$mdConstant", "$timeout", "$mdUtil"];angular
24 .module('material.components.chips')
25 .controller('MdChipCtrl', MdChipCtrl);
28 * Controller for the MdChip component. Responsible for handling keyboard
29 * events and editting the chip if needed.
38 function MdChipCtrl ($scope, $element, $mdConstant, $timeout, $mdUtil) {
47 this.$element = $element;
52 this.$mdConstant = $mdConstant;
57 this.$timeout = $timeout;
62 this.$mdUtil = $mdUtil;
67 this.isEditting = false;
72 this.parentController = undefined;
77 this.enableChipEdit = false;
82 * @param {MdChipsCtrl} controller
84 MdChipCtrl.prototype.init = function(controller) {
85 this.parentController = controller;
86 this.enableChipEdit = this.parentController.enableChipEdit;
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');
99 MdChipCtrl.prototype.getChipContent = function() {
100 var chipContents = this.$element[0].getElementsByClassName('md-chip-content');
101 return angular.element(chipContents[0]);
108 MdChipCtrl.prototype.getContentElement = function() {
109 return angular.element(this.getChipContent().children()[0]);
116 MdChipCtrl.prototype.getChipIndex = function() {
117 return parseInt(this.$element.attr('index'));
122 * Presents an input element to edit the contents of the chip.
124 MdChipCtrl.prototype.goOutOfEditMode = function() {
125 if (!this.isEditting) return;
127 this.isEditting = false;
128 this.$element.removeClass('_md-chip-editing');
129 this.getChipContent()[0].contentEditable = 'false';
130 var chipIndex = this.getChipIndex();
132 var content = this.getContentElement().text();
134 this.parentController.updateChipContents(
136 this.getContentElement().text()
139 this.$mdUtil.nextTick(function() {
140 if (this.parentController.selectedChip === chipIndex) {
141 this.parentController.focusChip(chipIndex);
145 this.parentController.removeChipAndFocusInput(chipIndex);
151 * Given an HTML element. Selects contents of it.
154 MdChipCtrl.prototype.selectNodeContents = function(node) {
155 var range, selection;
156 if (document.body.createTextRange) {
157 range = document.body.createTextRange();
158 range.moveToElementText(node);
160 } else if (window.getSelection) {
161 selection = window.getSelection();
162 range = document.createRange();
163 range.selectNodeContents(node);
164 selection.removeAllRanges();
165 selection.addRange(range);
171 * Presents an input element to edit the contents of the chip.
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();
181 this.selectNodeContents(this.getChipContent()[0]);
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.
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();
197 } else if (this.isEditting &&
198 event.keyCode === this.$mdConstant.KEY_CODE.ENTER) {
199 event.preventDefault();
200 this.goOutOfEditMode();
206 * Handles the double click event
208 MdChipCtrl.prototype.chipMouseDown = function() {
209 if(this.getChipIndex() == this.parentController.selectedChip &&
210 this.enableChipEdit &&
217 MdChip['$inject'] = ["$mdTheming", "$mdUtil", "$compile", "$timeout"];angular
218 .module('material.components.chips')
219 .directive('mdChip', MdChip);
224 * @module material.components.chips
227 * `<md-chip>` is a component used within `<md-chips>` and is responsible for rendering individual
233 * <md-chip>{{$chip}}</md-chip>
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}}\
246 * MDChip Directive Definition
252 function MdChip($mdTheming, $mdUtil, $compile, $timeout) {
253 var deleteHintTemplate = $mdUtil.processTemplate(DELETE_HINT_TEMPLATE);
257 require: ['^?mdChips', 'mdChip'],
259 controller: 'MdChipCtrl'
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'));
269 if (chipsController) {
270 chipController.init(chipsController);
272 // Append our delete hint to the div.md-chip-content (which does not exist at compile time)
273 chipContentElement.append($compile(deleteHintTemplate)(scope));
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();
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) {
289 if (chipsController.shouldFocusLastChip) {
290 chipsController.focusLastChipThenInput();
297 MdChipRemove['$inject'] = ["$timeout"];angular
298 .module('material.components.chips')
299 .directive('mdChipRemove', MdChipRemove);
305 * @module material.components.chips
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.
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.
317 * <button md-chip-remove="">
318 * <md-icon md-svg-icon="md-close"></md-icon>
326 * MdChipRemove Directive Definition.
329 * @returns {{restrict: string, require: string[], link: Function, scope: boolean}}
332 function MdChipRemove ($timeout) {
340 function postLink(scope, element, attr, ctrl) {
341 element.on('click', function(event) {
342 scope.$apply(function() {
343 ctrl.removeChip(scope.$$replacedScope.$index);
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');
357 MdChipTransclude['$inject'] = ["$compile"];angular
358 .module('material.components.chips')
359 .directive('mdChipTransclude', MdChipTransclude);
361 function MdChipTransclude ($compile) {
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;
376 var newHtml = ctrl.$scope.$eval(attr.mdChipTransclude);
378 element.html(newHtml);
379 $compile(element.contents())(newScope);
384 * The default chip append delay.
388 MdChipsCtrl['$inject'] = ["$scope", "$attrs", "$mdConstant", "$log", "$element", "$timeout", "$mdUtil"];
389 var DEFAULT_CHIP_APPEND_DELAY = 300;
392 .module('material.components.chips')
393 .controller('MdChipsCtrl', MdChipsCtrl);
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.
409 function MdChipsCtrl ($scope, $attrs, $mdConstant, $log, $element, $timeout, $mdUtil) {
410 /** @type {$timeout} **/
411 this.$timeout = $timeout;
413 /** @type {Object} */
414 this.$mdConstant = $mdConstant;
416 /** @type {angular.$scope} */
417 this.$scope = $scope;
419 /** @type {angular.$scope} */
420 this.parent = $scope.$parent;
422 /** @type {$mdUtil} */
423 this.$mdUtil = $mdUtil;
428 /** @type {$element} */
429 this.$element = $element;
431 /** @type {$attrs} */
432 this.$attrs = $attrs;
434 /** @type {angular.NgModelController} */
435 this.ngModelCtrl = null;
437 /** @type {angular.NgModelController} */
438 this.userInputNgModelCtrl = null;
440 /** @type {MdAutocompleteCtrl} */
441 this.autocompleteCtrl = null;
443 /** @type {Element} */
444 this.userInputElement = null;
446 /** @type {Array.<Object>} */
449 /** @type {number} */
450 this.selectedChip = -1;
452 /** @type {string} */
453 this.enableChipEdit = $mdUtil.parseAttributeBoolean($attrs.mdEnableChipEdit);
455 /** @type {string} */
456 this.addOnBlur = $mdUtil.parseAttributeBoolean($attrs.mdAddOnBlur);
459 * The text to be used as the aria-label for the input.
462 this.inputAriaLabel = 'Chips input.';
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.
470 this.containerHint = 'Chips container. Use arrow keys to select chips.';
473 * Hidden hint text for how to delete a chip. Used to give context to screen readers.
476 this.deleteHint = 'Press delete to remove this chip.';
479 * Hidden label for the delete button. Used to give context to screen readers.
482 this.deleteButtonLabel = 'Remove';
485 * Model used by the input element.
488 this.chipBuffer = '';
491 * Whether to use the transformChip expression to transform the chip buffer
492 * before appending it to the list.
495 this.useTransformChip = false;
498 * Whether to use the onAdd expression to notify of chip additions.
501 this.useOnAdd = false;
504 * Whether to use the onRemove expression to notify of chip removals.
507 this.useOnRemove = false;
510 * The ID of the chips wrapper which is used to build unique IDs for the chips and the aria-owns
513 * Defaults to '_md-chips-wrapper-' plus a unique number.
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.
525 this.contentIds = [];
528 * The index of the chip that should have it's tabindex property set to 0 so it is selectable
533 this.ariaTabIndex = null;
536 * After appending a chip, the chip will be focused for this number of milliseconds before the
537 * input is refocused.
539 * **Note:** This is **required** for compatibility with certain screen readers in order for
540 * them to properly allow keyboard access.
544 this.chipAppendDelay = DEFAULT_CHIP_APPEND_DELAY;
550 * Initializes variables and sets up watchers
552 MdChipsCtrl.prototype.init = function() {
555 // Set the wrapper ID
556 ctrl.wrapperId = '_md-chips-wrapper-' + ctrl.$mdUtil.nextUid();
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();
565 ctrl.$attrs.$observe('mdChipAppendDelay', function(newValue) {
566 ctrl.chipAppendDelay = parseInt(newValue) || DEFAULT_CHIP_APPEND_DELAY;
571 * If we have an input, ensure it has the appropriate ARIA attributes.
573 MdChipsCtrl.prototype.setupInputAria = function() {
574 var input = this.$element.find('input');
576 // If we have no input, just return
581 input.attr('role', 'textbox');
582 input.attr('aria-multiline', true);
586 * Ensure our wrapper has the appropriate ARIA attributes.
588 MdChipsCtrl.prototype.setupWrapperAria = function() {
590 wrapper = this.$element.find('md-chips-wrap');
592 if (this.items && this.items.length) {
593 // Dynamically add the listbox role on every change because it must be removed when there are
595 wrapper.attr('role', 'listbox');
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();
602 // Use the contentIDs above to generate the aria-owns attribute
603 wrapper.attr('aria-owns', this.contentIds.join(' '));
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');
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.
617 MdChipsCtrl.prototype.inputKeydown = function(event) {
618 var chipBuffer = this.getChipBuffer();
620 // If we have an autocomplete, and it handled the event, we have nothing to do
621 if (this.autocompleteCtrl && event.isDefaultPrevented && event.isDefaultPrevented()) {
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) {
632 event.preventDefault();
633 event.stopPropagation();
635 if (this.items.length) {
636 this.selectAndFocusChipSafe(this.items.length - 1);
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];
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();
652 // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
653 if (this.hasMaxChipsReached()) return;
655 this.appendChip(chipBuffer.trim());
656 this.resetChipBuffer();
663 * Returns the cursor position of the specified input element.
664 * @param element HTMLInputElement
665 * @returns {Number} Cursor Position of the input.
667 MdChipsCtrl.prototype.getCursorPosition = function(element) {
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.
674 if (element.selectionStart === element.selectionEnd) {
675 return element.selectionStart;
678 if (!element.value) {
686 * Updates the content of the chip at given index
688 * @param chipContents
690 MdChipsCtrl.prototype.updateChipContents = function(chipIndex, chipContents){
691 if(chipIndex >= 0 && chipIndex < this.items.length) {
692 this.items[chipIndex] = chipContents;
693 this.ngModelCtrl.$setDirty();
699 * Returns true if a chip is currently being edited. False otherwise.
702 MdChipsCtrl.prototype.isEditingChip = function() {
703 return !!this.$element[0].querySelector('._md-chip-editing');
707 MdChipsCtrl.prototype.isRemovable = function() {
708 // Return false if we have static chips
709 if (!this.ngModelCtrl) {
713 return this.readonly ? this.removable :
714 angular.isDefined(this.removable) ? this.removable : true;
718 * Handles the keydown event on the chip elements: backspace removes the selected chip, arrow
719 * keys switch which chips is active
722 MdChipsCtrl.prototype.chipKeydown = function (event) {
723 if (this.getChipBuffer()) return;
724 if (this.isEditingChip()) return;
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);
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
739 if (this.selectedChip < 0 || (this.readonly && this.selectedChip == 0)) {
740 this.selectedChip = this.items.length;
742 if (this.items.length) this.selectAndFocusChipSafe(this.selectedChip - 1);
744 case this.$mdConstant.KEY_CODE.RIGHT_ARROW:
745 event.preventDefault();
746 this.selectAndFocusChipSafe(this.selectedChip + 1);
748 case this.$mdConstant.KEY_CODE.ESCAPE:
749 case this.$mdConstant.KEY_CODE.TAB:
750 if (this.selectedChip < 0) return;
751 event.preventDefault();
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
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;
770 * Removes chip at {@code index} and selects the adjacent chip.
773 MdChipsCtrl.prototype.removeAndSelectAdjacentChip = function(index) {
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 + '"]');
779 self.removeChip(index);
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.
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);
793 * Sets the selected chip index to -1.
795 MdChipsCtrl.prototype.resetSelectedChip = function() {
796 this.selectedChip = -1;
797 this.ariaTabIndex = null;
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
807 * If the current chip is not selected, then -1 is returned to select none.
809 MdChipsCtrl.prototype.getAdjacentChipIndex = function(index) {
810 var len = this.items.length - 1;
811 return (len == 0) ? -1 :
812 (index == len) ? index -1 : index;
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.
821 MdChipsCtrl.prototype.appendChip = function(newChip) {
822 this.shouldFocusLastChip = true;
823 if (this.useTransformChip && this.transformChip) {
824 var transformedChip = this.transformChip({'$chip': newChip});
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;
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);
838 if (identical) return;
841 // Check for a null (but not undefined), or existing chip and cancel appending
842 if (newChip == null || this.items.indexOf(newChip) + 1) return;
844 // Append the new chip onto our list
845 var length = this.items.push(newChip);
846 var index = length - 1;
848 // Update model validation
849 this.ngModelCtrl.$setDirty();
850 this.validateModel();
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 });
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
865 MdChipsCtrl.prototype.useTransformChipExpression = function() {
866 this.useTransformChip = true;
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
876 MdChipsCtrl.prototype.useOnAddExpression = function() {
877 this.useOnAdd = true;
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
887 MdChipsCtrl.prototype.useOnRemoveExpression = function() {
888 this.useOnRemove = true;
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
898 MdChipsCtrl.prototype.useOnSelectExpression = function() {
899 this.useOnSelect = true;
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.
910 MdChipsCtrl.prototype.getChipBuffer = function() {
911 var chipBuffer = !this.userInputElement ? this.chipBuffer :
912 this.userInputNgModelCtrl ? this.userInputNgModelCtrl.$viewValue :
913 this.userInputElement[0].value;
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 : '';
920 * Resets the input buffer for either the internal input or user provided input element.
922 MdChipsCtrl.prototype.resetChipBuffer = function() {
923 if (this.userInputElement) {
924 if (this.userInputNgModelCtrl) {
925 this.userInputNgModelCtrl.$setViewValue('');
926 this.userInputNgModelCtrl.$render();
928 this.userInputElement[0].value = '';
931 this.chipBuffer = '';
935 MdChipsCtrl.prototype.hasMaxChipsReached = function() {
936 if (angular.isString(this.maxChips)) this.maxChips = parseInt(this.maxChips, 10) || 0;
938 return this.maxChips > 0 && this.items.length >= this.maxChips;
942 * Updates the validity properties for the ngModel.
944 MdChipsCtrl.prototype.validateModel = function() {
945 this.ngModelCtrl.$setValidity('md-max-chips', !this.hasMaxChipsReached());
949 * Removes the chip at the given index.
952 MdChipsCtrl.prototype.removeChip = function(index) {
953 var removed = this.items.splice(index, 1);
955 // Update model validation
956 this.ngModelCtrl.$setDirty();
957 this.validateModel();
959 if (removed && removed.length && this.useOnRemove && this.onRemove) {
960 this.onRemove({ '$chip': removed[0], '$index': index });
964 MdChipsCtrl.prototype.removeChipAndFocusInput = function (index) {
965 this.removeChip(index);
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));
979 * Selects the chip at `index`,
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();
988 // If we are asked to select a chip greater than the number of chips...
989 if (index >= this.items.length) {
991 // If we are readonly, jump back to the start (because we have no input)
994 // If we are not readonly, we should attempt to focus the input
995 return this.onFocus();
999 index = Math.max(index, 0);
1000 index = Math.min(index, this.items.length - 1);
1002 this.selectChip(index);
1003 this.focusChip(index);
1006 MdChipsCtrl.prototype.focusLastChipThenInput = function() {
1009 ctrl.shouldFocusLastChip = false;
1011 ctrl.focusChip(this.items.length - 1);
1013 ctrl.$timeout(function() {
1015 }, ctrl.chipAppendDelay);
1018 MdChipsCtrl.prototype.focusInput = function() {
1019 this.selectChip(-1);
1024 * Marks the chip at the given index as selected.
1027 MdChipsCtrl.prototype.selectChip = function(index) {
1028 if (index >= -1 && index <= this.items.length) {
1029 this.selectedChip = index;
1031 // Fire the onSelect if provided
1032 if (this.useOnSelect && this.onSelect) {
1033 this.onSelect({'$chip': this.items[index] });
1036 this.$log.warn('Selected Chip index out of bounds; ignoring.');
1041 * Selects the chip at `index` and gives it focus.
1044 MdChipsCtrl.prototype.selectAndFocusChip = function(index) {
1045 this.selectChip(index);
1047 this.focusChip(index);
1052 * Call `focus()` on the chip at `index`
1054 MdChipsCtrl.prototype.focusChip = function(index) {
1055 var chipContent = this.$element[0].querySelector('md-chip[index="' + index + '"] .md-chip-content');
1057 this.ariaTabIndex = index;
1059 chipContent.focus();
1063 * Configures the required interactions with the ngModel Controller.
1064 * Specifically, set {@code this.items} to the {@code NgModelCtrl#$viewVale}.
1065 * @param ngModelCtrl
1067 MdChipsCtrl.prototype.configureNgModel = function(ngModelCtrl) {
1068 this.ngModelCtrl = ngModelCtrl;
1071 ngModelCtrl.$render = function() {
1072 // model is updated. do something.
1073 self.items = self.ngModelCtrl.$viewValue;
1077 MdChipsCtrl.prototype.onFocus = function () {
1078 var input = this.$element[0].querySelector('input');
1079 input && input.focus();
1080 this.resetSelectedChip();
1083 MdChipsCtrl.prototype.onInputFocus = function () {
1084 this.inputHasFocus = true;
1086 // Make sure we have the appropriate ARIA attributes
1087 this.setupInputAria();
1089 // Make sure we don't have any chips selected
1090 this.resetSelectedChip();
1093 MdChipsCtrl.prototype.onInputBlur = function () {
1094 this.inputHasFocus = false;
1096 if (this.shouldAddOnBlur()) {
1097 this.appendChip(this.getChipBuffer().trim());
1098 this.resetChipBuffer();
1103 * Configure event bindings on a user-provided input element.
1104 * @param inputElement
1106 MdChipsCtrl.prototype.configureUserInput = function(inputElement) {
1107 this.userInputElement = inputElement;
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;
1116 var scope = this.$scope;
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));
1124 // Bind to keydown and focus events of input
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) })
1132 MdChipsCtrl.prototype.configureAutocomplete = function(ctrl) {
1134 this.autocompleteCtrl = ctrl;
1136 ctrl.registerSelectedItemWatcher(angular.bind(this, function (item) {
1138 // Only append the chip and reset the chip buffer if the max chips limit isn't reached.
1139 if (this.hasMaxChipsReached()) return;
1141 this.appendChip(item);
1142 this.resetChipBuffer();
1146 this.$element.find('input')
1147 .on('focus',angular.bind(this, this.onInputFocus) )
1148 .on('blur', angular.bind(this, this.onInputBlur) );
1153 * Whether the current chip buffer should be added on input blur or not.
1154 * @returns {boolean}
1156 MdChipsCtrl.prototype.shouldAddOnBlur = function() {
1158 // Update the custom ngModel validators from the chips component.
1159 this.validateModel();
1161 var chipBuffer = this.getChipBuffer().trim();
1162 var isModelValid = this.ngModelCtrl.$valid;
1163 var isAutocompleteShowing = this.autocompleteCtrl && !this.autocompleteCtrl.hidden;
1165 if (this.userInputNgModelCtrl) {
1166 isModelValid = isModelValid && this.userInputNgModelCtrl.$valid;
1169 return this.addOnBlur && !this.requireMatch && chipBuffer && isModelValid && !isAutocompleteShowing;
1172 MdChipsCtrl.prototype.hasFocus = function () {
1173 return this.inputHasFocus || this.selectedChip >= 0;
1176 MdChipsCtrl.prototype.contentIdFor = function(index) {
1177 return this.contentIds[index];
1181 MdChips['$inject'] = ["$mdTheming", "$mdUtil", "$compile", "$log", "$timeout", "$$mdSvgRegistry"];angular
1182 .module('material.components.chips')
1183 .directive('mdChips', MdChips);
1188 * @module material.components.chips
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.
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
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.
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.
1212 * <h3> Pending Features </h3>
1213 * <ul style="padding-left:20px;">
1216 * <li>Colours for hover, press states (ripple?).</li>
1220 * <li>allow a validation callback</li>
1221 * <li>hilighting style for invalid chips</li>
1226 * <md-chip-edit>` template, show/hide the edit element on tap/click? double tap/double
1231 * <ul>Truncation and Disambiguation (?)
1232 * <li>Truncate chip text where possible, but do not truncate entries such that two are
1233 * indistinguishable.</li>
1237 * <li>Drag and drop chips between related `<md-chips>` elements.
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.
1247 * <hljs lang="html">
1249 * ng-model="myItems"
1250 * placeholder="Add an item"
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.
1259 * <hljs lang="html">
1260 * <md-chips ng-model="myItems" md-max-chips="5">
1261 * <md-autocomplete ng-hide="myItems.length > 5" ...></md-autocomplete>
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.
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).
1274 * We introduced a new `md-chip-append-delay` option to allow developers to better control this
1277 * Please refer to the documentation of this option (below) for more information.
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
1304 * @param {expression=} md-on-remove An expression which will be called when a chip has been
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
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).
1322 * _Available since Version 1.1.2._
1324 * **Note:** You can safely set this to `0` in one of the following two instances:
1326 * 1. You are targeting an iOS or Safari-only application (where users would use VoiceOver) or
1327 * only ChromeVox users.
1329 * 2. If you have utilized the `md-separator-keys` to disable the `enter` keystroke in
1330 * favor of another one (such as `,` or `;`).
1333 * <hljs lang="html">
1335 * ng-model="myItems"
1336 * placeholder="Add an item"
1337 * readonly="isReadOnly">
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">
1348 * ng-model="myItems"
1349 * placeholder="Add an item"
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>
1360 var MD_CHIPS_TEMPLATE = '\
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}}"\
1370 <span ng-if="$mdChipsCtrl.readonly" class="md-visually-hidden">\
1371 {{$mdChipsCtrl.containerHint}}\
1373 <md-chip ng-repeat="$chip in $mdChipsCtrl.items"\
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)}}"\
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"\
1388 md-chip-transclude="$mdChipsCtrl.chipRemoveTemplate"></div>\
1390 <div class="md-chip-input-container" ng-if="!$mdChipsCtrl.readonly && $mdChipsCtrl.ngModelCtrl">\
1391 <div md-chip-transclude="$mdChipsCtrl.chipInputTemplate"></div>\
1395 var CHIP_INPUT_TEMPLATE = '\
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)">';
1406 var CHIP_DEFAULT_TEMPLATE = '\
1407 <span>{{$chip}}</span>';
1409 var CHIP_REMOVE_TEMPLATE = '\
1411 class="md-chip-remove"\
1412 ng-if="$mdChipsCtrl.isRemovable()"\
1413 ng-click="$mdChipsCtrl.removeChipAndFocusInput($$replacedScope.$index)"\
1416 <md-icon md-svg-src="{{ $mdChipsCtrl.mdCloseIcon }}"></md-icon>\
1417 <span class="md-visually-hidden">\
1418 {{$mdChipsCtrl.deleteButtonLabel}}\
1423 * MDChips Directive Definition
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();
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;
1438 require: ['mdChips'],
1440 controller: 'MdChipsCtrl',
1441 controllerAs: '$mdChipsCtrl',
1442 bindToController: true,
1445 readonly: '=readonly',
1446 removable: '=mdRemovable',
1448 secondaryPlaceholder: '@',
1449 maxChips: '@mdMaxChips',
1450 transformChip: '&mdTransformChip',
1451 onAppend: '&mdOnAppend',
1453 onRemove: '&mdOnRemove',
1454 onSelect: '&mdOnSelect',
1455 inputAriaLabel: '@',
1458 deleteButtonLabel: '@',
1459 separatorKeys: '=?mdSeparatorKeys',
1460 requireMatch: '=?mdRequireMatch',
1461 chipAppendDelayString: '@?mdChipAppendDelay'
1466 * Builds the final template for `md-chips` and returns the postLink function.
1468 * Building the template involves 3 key components:
1473 * If no `ng-model` is provided, only the static chip work needs to be done.
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.
1478 * The remove button may be overridden by passing an element with an md-chip-remove attribute.
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
1485 * Static Chips (i.e. `md-chip` elements passed from the caller) are gathered and set aside for
1486 * transclusion in the `postLink` function.
1491 * @returns {Function}
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;
1498 var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
1500 var chipRemoveSelector = $mdUtil
1502 .buildList('md-chip-remove')
1503 .map(function(attr) {
1504 return 'md-chips>*[' + attr + ']';
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')
1515 staticChips = userTemplate.find('md-chip');
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.');
1522 function getTemplateByQuery (query) {
1523 if (!attr.ngModel) return;
1524 var element = userTemplate[0].querySelector(query);
1525 return element && element.outerHTML;
1529 * Configures controller and transcludes.
1531 return function postLink(scope, element, attrs, controllers) {
1532 $mdUtil.initOptionalProperties(scope, attr);
1534 $mdTheming(element);
1535 var mdChipsCtrl = controllers[0];
1537 // Chip editing functionality assumes we are using the default chip template.
1538 mdChipsCtrl.enableChipEdit = false;
1541 mdChipsCtrl.chipContentsTemplate = chipContentsTemplate;
1542 mdChipsCtrl.chipRemoveTemplate = chipRemoveTemplate;
1543 mdChipsCtrl.chipInputTemplate = chipInputTemplate;
1545 mdChipsCtrl.mdCloseIcon = $$mdSvgRegistry.mdClose;
1548 .attr({ tabindex: -1 })
1549 .on('focus', function () { mdChipsCtrl.onFocus(); });
1552 mdChipsCtrl.configureNgModel(element.controller('ngModel'));
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();
1558 // If an `md-on-append` attribute was set, tell the controller to use the expression
1559 // when appending chips.
1561 // DEPRECATED: Will remove in official 1.0 release
1562 if (attrs.mdOnAppend) mdChipsCtrl.useOnAppendExpression();
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();
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();
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();
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
1584 scope.$watch('$mdChipsCtrl.readonly', function(readonly) {
1587 $mdUtil.nextTick(function(){
1589 if (chipInputTemplate.indexOf('<md-autocomplete') === 0) {
1590 var autocompleteEl = element.find('md-autocomplete');
1591 mdChipsCtrl.configureAutocomplete(autocompleteEl.controller('mdAutocomplete'));
1594 mdChipsCtrl.configureUserInput(element.find('input'));
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');
1604 input && input.toggleClass('md-input', true);
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); });
1616 function getTemplates() {
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)
1627 .module('material.components.chips')
1628 .controller('MdContactChipsCtrl', MdContactChipsCtrl);
1633 * Controller for the MdContactChips component
1636 function MdContactChipsCtrl () {
1637 /** @type {Object} */
1638 this.selectedItem = null;
1640 /** @type {string} */
1641 this.searchText = '';
1645 MdContactChipsCtrl.prototype.queryContact = function(searchText) {
1646 return this.contactQuery({'$query': searchText});
1650 MdContactChipsCtrl.prototype.itemName = function(item) {
1651 return item[this.contactName];
1655 MdContactChips['$inject'] = ["$mdTheming", "$mdUtil"];angular
1656 .module('material.components.chips')
1657 .directive('mdContactChips', MdContactChips);
1661 * @name mdContactChips
1662 * @module material.components.chips
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
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.
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
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
1686 * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will
1689 * @param {expression=} filter-selected Whether to filter selected contacts from the list of
1690 * suggestions shown in the autocomplete.
1692 * ***Note:** This attribute has been removed but may come back.*
1697 * <hljs lang="html">
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"
1705 * </md-contact-chips>
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>\
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)"\
1724 md-min-length="$mdContactChipsCtrl.minLength"\
1726 placeholder="{{$mdContactChipsCtrl.contacts.length == 0 ?\
1727 $mdContactChipsCtrl.placeholder : $mdContactChipsCtrl.secondaryPlaceholder}}">\
1728 <div class="md-contact-suggestion">\
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]}}\
1737 <span class="md-contact-email" >{{item[$mdContactChipsCtrl.contactEmail]}}</span>\
1741 <div class="md-contact-avatar">\
1743 ng-src="{{$chip[$mdContactChipsCtrl.contactImage]}}"\
1744 alt="{{$chip[$mdContactChipsCtrl.contactName]}}"\
1745 ng-if="$chip[$mdContactChipsCtrl.contactImage]" />\
1747 <div class="md-contact-name">\
1748 {{$chip[$mdContactChipsCtrl.contactName]}}\
1750 </md-chip-template>\
1755 * MDContactChips Directive Definition
1761 function MdContactChips($mdTheming, $mdUtil) {
1763 template: function(element, attrs) {
1764 return MD_CONTACT_CHIPS_TEMPLATE;
1767 controller: 'MdContactChipsCtrl',
1768 controllerAs: '$mdContactChipsCtrl',
1769 bindToController: true,
1772 contactQuery: '&mdContacts',
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'
1786 function compile(element, attr) {
1787 return function postLink(scope, element, attrs, controllers) {
1788 var contactChipsController = controllers;
1790 $mdUtil.initOptionalProperties(scope, attr);
1791 $mdTheming(element);
1793 element.attr('tabindex', '-1');
1795 attrs.$observe('mdChipAppendDelay', function(newValue) {
1796 contactChipsController.chipAppendDelay = newValue;
1802 ngmaterial.components.chips = angular.module("material.components.chips");