nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / angular-material / modules / closure / autocomplete / autocomplete.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.autocomplete');
8 goog.require('ng.material.components.icon');
9 goog.require('ng.material.core');
10 /**
11  * @ngdoc module
12  * @name material.components.autocomplete
13  */
14 /*
15  * @see js folder for autocomplete implementation
16  */
17 angular.module('material.components.autocomplete', [
18   'material.core',
19   'material.components.icon'
20 ]);
21
22 angular
23     .module('material.components.autocomplete')
24     .controller('MdAutocompleteCtrl', MdAutocompleteCtrl);
25
26 var ITEM_HEIGHT = 41,
27     MAX_HEIGHT = 5.5 * ITEM_HEIGHT,
28     MENU_PADDING = 8;
29
30 function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $timeout, $mdTheming, $window, $animate, $rootElement) {
31
32   //-- private variables
33
34   var self      = this,
35       itemParts = $scope.itemsExpr.split(/ in /i),
36       itemExpr  = itemParts[1],
37       elements  = null,
38       promise   = null,
39       cache     = {},
40       noBlur    = false,
41       selectedItemWatchers = [],
42       hasFocus  = false,
43       lastCount = 0;
44
45   //-- public variables
46
47   self.scope    = $scope;
48   self.parent   = $scope.$parent;
49   self.itemName = itemParts[0];
50   self.matches  = [];
51   self.loading  = false;
52   self.hidden   = true;
53   self.index    = null;
54   self.messages = [];
55   self.id       = $mdUtil.nextUid();
56
57   //-- public methods
58
59   self.keydown  = keydown;
60   self.blur     = blur;
61   self.focus    = focus;
62   self.clear    = clearValue;
63   self.select   = select;
64   self.getCurrentDisplayValue         = getCurrentDisplayValue;
65   self.registerSelectedItemWatcher    = registerSelectedItemWatcher;
66   self.unregisterSelectedItemWatcher  = unregisterSelectedItemWatcher;
67
68   self.listEnter = function () { noBlur = true; };
69   self.listLeave = function () {
70     noBlur = false;
71     if (!hasFocus) self.hidden = true;
72   };
73   self.mouseUp   = function () { elements.input.focus(); };
74
75   return init();
76
77   //-- initialization methods
78
79   function init () {
80     configureWatchers();
81     $timeout(function () {
82       gatherElements();
83       focusElement();
84       moveDropdown();
85     });
86   }
87
88   function positionDropdown () {
89     if (!elements) return $timeout(positionDropdown, 0, false);
90     var hrect  = elements.wrap.getBoundingClientRect(),
91         vrect  = elements.snap.getBoundingClientRect(),
92         root   = elements.root.getBoundingClientRect(),
93         top    = vrect.bottom - root.top,
94         bot    = root.bottom - vrect.top,
95         left   = hrect.left - root.left,
96         width  = hrect.width,
97         styles = {
98           left:     left + 'px',
99           minWidth: width + 'px',
100           maxWidth: Math.max(hrect.right - root.left, root.right - hrect.left) - MENU_PADDING + 'px'
101         };
102     if (top > bot && root.height - hrect.bottom - MENU_PADDING < MAX_HEIGHT) {
103       styles.top = 'auto';
104       styles.bottom = bot + 'px';
105       styles.maxHeight = Math.min(MAX_HEIGHT, hrect.top - root.top - MENU_PADDING) + 'px';
106     } else {
107       styles.top = top + 'px';
108       styles.bottom = 'auto';
109       styles.maxHeight = Math.min(MAX_HEIGHT, root.bottom - hrect.bottom - MENU_PADDING) + 'px';
110     }
111     elements.$.ul.css(styles);
112     $timeout(correctHorizontalAlignment, 0, false);
113
114     function correctHorizontalAlignment () {
115       var dropdown = elements.ul.getBoundingClientRect(),
116           styles   = {};
117       if (dropdown.right > root.right - MENU_PADDING) {
118         styles.left = (hrect.right - dropdown.width) + 'px';
119       }
120       elements.$.ul.css(styles);
121     }
122   }
123
124   function moveDropdown () {
125     if (!elements.$.root.length) return;
126     $mdTheming(elements.$.ul);
127     elements.$.ul.detach();
128     elements.$.root.append(elements.$.ul);
129     if ($animate.pin) $animate.pin(elements.$.ul, $rootElement);
130   }
131
132   function focusElement () {
133     if ($scope.autofocus) elements.input.focus();
134   }
135
136   function configureWatchers () {
137     var wait = parseInt($scope.delay, 10) || 0;
138     $scope.$watch('searchText', wait
139         ? $mdUtil.debounce(handleSearchText, wait)
140         : handleSearchText);
141     registerSelectedItemWatcher(selectedItemChange);
142     $scope.$watch('selectedItem', handleSelectedItemChange);
143     $scope.$watch('$mdAutocompleteCtrl.hidden', function (hidden, oldHidden) {
144       if (!hidden && oldHidden) positionDropdown();
145     });
146     angular.element($window).on('resize', positionDropdown);
147     $scope.$on('$destroy', cleanup);
148   }
149
150   function cleanup () {
151     elements.$.ul.remove();
152   }
153
154   function gatherElements () {
155     elements = {
156       main:  $element[0],
157       ul:    $element.find('ul')[0],
158       input: $element.find('input')[0],
159       wrap:  $element.find('md-autocomplete-wrap')[0],
160       root:  document.body
161     };
162     elements.li = elements.ul.getElementsByTagName('li');
163     elements.snap = getSnapTarget();
164     elements.$ = getAngularElements(elements);
165   }
166
167   function getSnapTarget () {
168     for (var element = $element; element.length; element = element.parent()) {
169       if (angular.isDefined(element.attr('md-autocomplete-snap'))) return element[0];
170     }
171     return elements.wrap;
172   }
173
174   function getAngularElements (elements) {
175     var obj = {};
176     for (var key in elements) {
177       obj[key] = angular.element(elements[key]);
178     }
179     return obj;
180   }
181
182   //-- event/change handlers
183
184   function selectedItemChange (selectedItem, previousSelectedItem) {
185     if (selectedItem) {
186       $scope.searchText = getDisplayValue(selectedItem);
187     }
188     if ($scope.itemChange && selectedItem !== previousSelectedItem)
189       $scope.itemChange(getItemScope(selectedItem));
190   }
191
192   function handleSelectedItemChange(selectedItem, previousSelectedItem) {
193     for (var i = 0; i < selectedItemWatchers.length; ++i) {
194       selectedItemWatchers[i](selectedItem, previousSelectedItem);
195     }
196   }
197
198   /**
199    * Register a function to be called when the selected item changes.
200    * @param cb
201    */
202   function registerSelectedItemWatcher(cb) {
203     if (selectedItemWatchers.indexOf(cb) == -1) {
204       selectedItemWatchers.push(cb);
205     }
206   }
207
208   /**
209    * Unregister a function previously registered for selected item changes.
210    * @param cb
211    */
212   function unregisterSelectedItemWatcher(cb) {
213     var i = selectedItemWatchers.indexOf(cb);
214     if (i != -1) {
215       selectedItemWatchers.splice(i, 1);
216     }
217   }
218
219   function handleSearchText (searchText, previousSearchText) {
220     self.index = getDefaultIndex();
221     //-- do nothing on init
222     if (searchText === previousSearchText) return;
223     //-- clear selected item if search text no longer matches it
224     if (searchText !== getDisplayValue($scope.selectedItem)) $scope.selectedItem = null;
225     else return;
226     //-- trigger change event if available
227     if ($scope.textChange && searchText !== previousSearchText)
228       $scope.textChange(getItemScope($scope.selectedItem));
229     //-- cancel results if search text is not long enough
230     if (!isMinLengthMet()) {
231       self.loading = false;
232       self.matches = [];
233       self.hidden = shouldHide();
234       updateMessages();
235     } else {
236       handleQuery();
237     }
238   }
239
240   function blur () {
241     hasFocus = false;
242     if (!noBlur) self.hidden = true;
243   }
244
245   function focus () {
246     hasFocus = true;
247     //-- if searchText is null, let's force it to be a string
248     if (!angular.isString($scope.searchText)) $scope.searchText = '';
249     if ($scope.minLength > 0) return;
250     self.hidden = shouldHide();
251     if (!self.hidden) handleQuery();
252   }
253
254   function keydown (event) {
255     switch (event.keyCode) {
256       case $mdConstant.KEY_CODE.DOWN_ARROW:
257         if (self.loading) return;
258         event.preventDefault();
259         self.index = Math.min(self.index + 1, self.matches.length - 1);
260         updateScroll();
261         updateMessages();
262         break;
263       case $mdConstant.KEY_CODE.UP_ARROW:
264         if (self.loading) return;
265         event.preventDefault();
266         self.index = self.index < 0 ? self.matches.length - 1 : Math.max(0, self.index - 1);
267         updateScroll();
268         updateMessages();
269         break;
270       case $mdConstant.KEY_CODE.TAB:
271       case $mdConstant.KEY_CODE.ENTER:
272         if (self.hidden || self.loading || self.index < 0 || self.matches.length < 1) return;
273         event.preventDefault();
274         select(self.index);
275         break;
276       case $mdConstant.KEY_CODE.ESCAPE:
277         self.matches = [];
278         self.hidden = true;
279         self.index = getDefaultIndex();
280         break;
281       default:
282     }
283   }
284
285   //-- getters
286
287   function getMinLength () {
288     return angular.isNumber($scope.minLength) ? $scope.minLength : 1;
289   }
290
291   function getDisplayValue (item) {
292     return (item && $scope.itemText) ? $scope.itemText(getItemScope(item)) : item;
293   }
294
295   function getItemScope (item) {
296     if (!item) return;
297     var locals = {};
298     if (self.itemName) locals[self.itemName] = item;
299     return locals;
300   }
301
302   function getDefaultIndex () {
303     return $scope.autoselect ? 0 : -1;
304   }
305
306   function shouldHide () {
307     if (!isMinLengthMet()) return true;
308   }
309
310   function getCurrentDisplayValue () {
311     return getDisplayValue(self.matches[self.index]);
312   }
313
314   function isMinLengthMet () {
315     return $scope.searchText && $scope.searchText.length >= getMinLength();
316   }
317
318   //-- actions
319
320   function select (index) {
321     $scope.selectedItem = self.matches[index];
322     self.hidden = true;
323     self.index = 0;
324     self.matches = [];
325     //-- force form to update state for validation
326     $timeout(function () {
327       elements.$.input.controller('ngModel').$setViewValue(getDisplayValue($scope.selectedItem) || $scope.searchText);
328       self.hidden = true;
329     });
330   }
331
332   function clearValue () {
333     $scope.searchText = '';
334     select(-1);
335
336     // Per http://www.w3schools.com/jsref/event_oninput.asp
337     var eventObj = document.createEvent('CustomEvent');
338     eventObj.initCustomEvent('input', true, true, {value: $scope.searchText});
339     elements.input.dispatchEvent(eventObj);
340
341     elements.input.focus();
342   }
343
344   function fetchResults (searchText) {
345     var items = $scope.$parent.$eval(itemExpr),
346         term = searchText.toLowerCase();
347     if (angular.isArray(items)) {
348       handleResults(items);
349     } else {
350       self.loading = true;
351       if (items.success) items.success(handleResults);
352       if (items.then)    items.then(handleResults);
353       if (items.error)   items.error(function () { self.loading = false; });
354     }
355     function handleResults (matches) {
356       cache[term] = matches;
357       if (searchText !== $scope.searchText) return; //-- just cache the results if old request
358       self.loading = false;
359       promise = null;
360       self.matches = matches;
361       self.hidden = shouldHide();
362       updateMessages();
363       positionDropdown();
364     }
365   }
366
367   function updateMessages () {
368     self.messages = [ getCountMessage(), getCurrentDisplayValue() ];
369   }
370
371   function getCountMessage () {
372     if (lastCount === self.matches.length) return '';
373     lastCount = self.matches.length;
374     switch (self.matches.length) {
375       case 0:  return 'There are no matches available.';
376       case 1:  return 'There is 1 match available.';
377       default: return 'There are ' + self.matches.length + ' matches available.';
378     }
379   }
380
381   function updateScroll () {
382     if (!elements.li[self.index]) return;
383     var li  = elements.li[self.index],
384         top = li.offsetTop,
385         bot = top + li.offsetHeight,
386         hgt = elements.ul.clientHeight;
387     if (top < elements.ul.scrollTop) {
388       elements.ul.scrollTop = top;
389     } else if (bot > elements.ul.scrollTop + hgt) {
390       elements.ul.scrollTop = bot - hgt;
391     }
392   }
393
394   function handleQuery () {
395     var searchText = $scope.searchText,
396         term = searchText.toLowerCase();
397     //-- cancel promise if a promise is in progress
398     if (promise && promise.cancel) {
399       promise.cancel();
400       promise = null;
401     }
402     //-- if results are cached, pull in cached results
403     if (!$scope.noCache && cache[term]) {
404       self.matches = cache[term];
405       updateMessages();
406     } else {
407       fetchResults(searchText);
408     }
409     if (hasFocus) self.hidden = shouldHide();
410   }
411
412 }
413 MdAutocompleteCtrl.$inject = ["$scope", "$element", "$mdUtil", "$mdConstant", "$timeout", "$mdTheming", "$window", "$animate", "$rootElement"];
414
415 angular
416     .module('material.components.autocomplete')
417     .directive('mdAutocomplete', MdAutocomplete);
418
419 /**
420  * @ngdoc directive
421  * @name mdAutocomplete
422  * @module material.components.autocomplete
423  *
424  * @description
425  * `<md-autocomplete>` is a special input component with a drop-down of all possible matches to a custom query.
426  * This component allows you to provide real-time suggestions as the user types in the input area.
427  *
428  * To start, you will need to specify the required parameters and provide a template for your results.
429  * The content inside `md-autocomplete` will be treated as a template.
430  *
431  * In more complex cases, you may want to include other content such as a message to display when
432  * no matches were found.  You can do this by wrapping your template in `md-item-template` and adding
433  * a tag for `md-not-found`.  An example of this is shown below.
434  * ### Validation
435  *
436  * You can use `ng-messages` to include validation the same way that you would normally validate;
437  * however, if you want to replicate a standard input with a floating label, you will have to do the
438  * following:
439  *
440  * - Make sure that your template is wrapped in `md-item-template`
441  * - Add your `ng-messages` code inside of `md-autocomplete`
442  * - Add your validation properties to `md-autocomplete` (ie. `required`)
443  * - Add a `name` to `md-autocomplete` (to be used on the generated `input`)
444  *
445  * There is an example below of how this should look.
446  *
447  *
448  * @param {expression} md-items An expression in the format of `item in items` to iterate over matches for your search.
449  * @param {expression=} md-selected-item-change An expression to be run each time a new item is selected
450  * @param {expression=} md-search-text-change An expression to be run each time the search text updates
451  * @param {string=} md-search-text A model to bind the search query text to
452  * @param {object=} md-selected-item A model to bind the selected item to
453  * @param {string=} md-item-text An expression that will convert your object to a single string.
454  * @param {string=} placeholder Placeholder text that will be forwarded to the input.
455  * @param {boolean=} md-no-cache Disables the internal caching that happens in autocomplete
456  * @param {boolean=} ng-disabled Determines whether or not to disable the input field
457  * @param {number=} md-min-length Specifies the minimum length of text before autocomplete will make suggestions
458  * @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking for results
459  * @param {boolean=} md-autofocus If true, will immediately focus the input element
460  * @param {boolean=} md-autoselect If true, the first item will be selected by default
461  * @param {string=} md-menu-class This will be applied to the dropdown menu for styling
462  * @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in `md-input-container`
463  *
464  * @usage
465  * ###Basic Example
466  * <hljs lang="html">
467  *   <md-autocomplete
468  *       md-selected-item="selectedItem"
469  *       md-search-text="searchText"
470  *       md-items="item in getMatches(searchText)"
471  *       md-item-text="item.display">
472  *     <span md-highlight-text="searchText">{{item.display}}</span>
473  *   </md-autocomplete>
474  * </hljs>
475  *
476  * ###Example with "not found" message
477  * <hljs lang="html">
478  * <md-autocomplete
479  *     md-selected-item="selectedItem"
480  *     md-search-text="searchText"
481  *     md-items="item in getMatches(searchText)"
482  *     md-item-text="item.display">
483  *   <md-item-template>
484  *     <span md-highlight-text="searchText">{{item.display}}</span>
485  *   </md-item-template>
486  *   <md-not-found>
487  *     No matches found.
488  *   </md-not-found>
489  * </md-autocomplete>
490  * </hljs>
491  *
492  * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the different
493  * parts that make up our component.
494  *
495  * ### Example with validation
496  * <hljs lang="html">
497  * <form name="autocompleteForm">
498  *   <md-autocomplete
499  *       required
500  *       input-name="autocomplete"
501  *       md-selected-item="selectedItem"
502  *       md-search-text="searchText"
503  *       md-items="item in getMatches(searchText)"
504  *       md-item-text="item.display">
505  *     <md-item-template>
506  *       <span md-highlight-text="searchText">{{item.display}}</span>
507  *     </md-item-template>
508  *     <div ng-messages="autocompleteForm.autocomplete.$error">
509  *       <div ng-message="required">This field is required</div>
510  *     </div>
511  *   </md-autocomplete>
512  * </form>
513  * </hljs>
514  *
515  * In this example, our code utilizes `md-item-template` and `md-not-found` to specify the different
516  * parts that make up our component.
517  */
518
519 function MdAutocomplete ($mdTheming, $mdUtil) {
520   return {
521     controller:   'MdAutocompleteCtrl',
522     controllerAs: '$mdAutocompleteCtrl',
523     link:         link,
524     scope:        {
525       inputName:      '@mdInputName',
526       inputMinlength: '@mdInputMinlength',
527       inputMaxlength: '@mdInputMaxlength',
528       searchText:     '=?mdSearchText',
529       selectedItem:   '=?mdSelectedItem',
530       itemsExpr:      '@mdItems',
531       itemText:       '&mdItemText',
532       placeholder:    '@placeholder',
533       noCache:        '=?mdNoCache',
534       itemChange:     '&?mdSelectedItemChange',
535       textChange:     '&?mdSearchTextChange',
536       minLength:      '=?mdMinLength',
537       delay:          '=?mdDelay',
538       autofocus:      '=?mdAutofocus',
539       floatingLabel:  '@?mdFloatingLabel',
540       autoselect:     '=?mdAutoselect',
541       menuClass:      '@?mdMenuClass'
542     },
543     template: function (element, attr) {
544       var noItemsTemplate = getNoItemsTemplate(),
545           itemTemplate = getItemTemplate(),
546           leftover = element.html();
547       return '\
548         <md-autocomplete-wrap\
549             layout="row"\
550             ng-class="{ \'md-whiteframe-z1\': !floatingLabel }"\
551             role="listbox">\
552           ' + getInputElement() + '\
553           <md-progress-linear\
554               ng-if="$mdAutocompleteCtrl.loading"\
555               md-mode="indeterminate"></md-progress-linear>\
556           <ul role="presentation"\
557               class="md-autocomplete-suggestions md-whiteframe-z1 {{menuClass || \'\'}}"\
558               id="ul-{{$mdAutocompleteCtrl.id}}"\
559               ng-mouseenter="$mdAutocompleteCtrl.listEnter()"\
560               ng-mouseleave="$mdAutocompleteCtrl.listLeave()"\
561               ng-mouseup="$mdAutocompleteCtrl.mouseUp()">\
562             <li ng-repeat="(index, item) in $mdAutocompleteCtrl.matches"\
563                 ng-class="{ selected: index === $mdAutocompleteCtrl.index }"\
564                 ng-hide="$mdAutocompleteCtrl.hidden"\
565                 ng-click="$mdAutocompleteCtrl.select(index)"\
566                 md-autocomplete-list-item="$mdAutocompleteCtrl.itemName">\
567                 ' + itemTemplate + '\
568             </li>\
569             ' + noItemsTemplate + '\
570           </ul>\
571         </md-autocomplete-wrap>\
572         <aria-status\
573             class="md-visually-hidden"\
574             role="status"\
575             aria-live="assertive">\
576           <p ng-repeat="message in $mdAutocompleteCtrl.messages" ng-if="message">{{message}}</p>\
577         </aria-status>';
578
579       function getItemTemplate() {
580         var templateTag = element.find('md-item-template').remove(),
581             html = templateTag.length ? templateTag.html() : element.html();
582         if (!templateTag.length) element.empty();
583         return html;
584       }
585
586       function getNoItemsTemplate() {
587         var templateTag = element.find('md-not-found').remove(),
588             template = templateTag.length ? templateTag.html() : '';
589         return template
590             ? '<li ng-if="!$mdAutocompleteCtrl.matches.length && !$mdAutocompleteCtrl.loading\
591                          && !$mdAutocompleteCtrl.hidden"\
592                          ng-hide="$mdAutocompleteCtrl.hidden"\
593                          md-autocomplete-parent-scope>' + template + '</li>'
594             : '';
595
596       }
597
598       function getInputElement() {
599         if (attr.mdFloatingLabel) {
600           return '\
601             <md-input-container flex ng-if="floatingLabel">\
602               <label>{{floatingLabel}}</label>\
603               <input type="search"\
604                   id="fl-input-{{$mdAutocompleteCtrl.id}}"\
605                   name="{{inputName}}"\
606                   autocomplete="off"\
607                   ng-required="isRequired"\
608                   ng-minlength="inputMinlength"\
609                   ng-maxlength="inputMaxlength"\
610                   ng-disabled="isDisabled"\
611                   ng-model="$mdAutocompleteCtrl.scope.searchText"\
612                   ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
613                   ng-blur="$mdAutocompleteCtrl.blur()"\
614                   ng-focus="$mdAutocompleteCtrl.focus()"\
615                   aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
616                   aria-label="{{floatingLabel}}"\
617                   aria-autocomplete="list"\
618                   aria-haspopup="true"\
619                   aria-activedescendant=""\
620                   aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
621               <div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
622             </md-input-container>';
623         } else {
624           return '\
625             <input flex type="search"\
626                 id="input-{{$mdAutocompleteCtrl.id}}"\
627                 name="{{inputName}}"\
628                 ng-if="!floatingLabel"\
629                 autocomplete="off"\
630                 ng-required="isRequired"\
631                 ng-disabled="isDisabled"\
632                 ng-model="$mdAutocompleteCtrl.scope.searchText"\
633                 ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
634                 ng-blur="$mdAutocompleteCtrl.blur()"\
635                 ng-focus="$mdAutocompleteCtrl.focus()"\
636                 placeholder="{{placeholder}}"\
637                 aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
638                 aria-label="{{placeholder}}"\
639                 aria-autocomplete="list"\
640                 aria-haspopup="true"\
641                 aria-activedescendant=""\
642                 aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
643             <button\
644                 type="button"\
645                 tabindex="-1"\
646                 ng-if="$mdAutocompleteCtrl.scope.searchText && !isDisabled"\
647                 ng-click="$mdAutocompleteCtrl.clear()">\
648               <md-icon md-svg-icon="md-close"></md-icon>\
649               <span class="md-visually-hidden">Clear</span>\
650             </button>\
651                 ';
652         }
653       }
654     }
655   };
656
657   function link (scope, element, attr) {
658     attr.$observe('disabled', function (value) { scope.isDisabled = value; });
659     attr.$observe('required', function (value) { scope.isRequired = value !== null; });
660
661     $mdUtil.initOptionalProperties(scope, attr, {searchText:null, selectedItem:null} );
662
663     $mdTheming(element);
664   }
665 }
666 MdAutocomplete.$inject = ["$mdTheming", "$mdUtil"];
667
668 angular
669     .module('material.components.autocomplete')
670     .controller('MdHighlightCtrl', MdHighlightCtrl);
671
672 function MdHighlightCtrl ($scope, $element, $interpolate) {
673   this.init = init;
674
675   return init();
676
677   function init (term) {
678     var unsafeText = $interpolate($element.html())($scope),
679         text = angular.element('<div>').text(unsafeText).html(),
680         flags = $element.attr('md-highlight-flags') || '',
681         watcher = $scope.$watch(term, function (term) {
682           var regex = getRegExp(term, flags),
683               html = text.replace(regex, '<span class="highlight">$&</span>');
684           $element.html(html);
685         });
686     $element.on('$destroy', function () { watcher(); });
687   }
688
689   function sanitize (term) {
690     if (!term) return term;
691     return term.replace(/[\\\^\$\*\+\?\.\(\)\|\{\}\[\]]/g, '\\$&');
692   }
693
694   function getRegExp (text, flags) {
695     var str = '';
696     if (flags.indexOf('^') >= 1) str += '^';
697     str += text;
698     if (flags.indexOf('$') >= 1) str += '$';
699     return new RegExp(sanitize(str), flags.replace(/[\$\^]/g, ''));
700   }
701 }
702 MdHighlightCtrl.$inject = ["$scope", "$element", "$interpolate"];
703
704 angular
705     .module('material.components.autocomplete')
706     .directive('mdHighlightText', MdHighlight);
707
708 /**
709  * @ngdoc directive
710  * @name mdHighlightText
711  * @module material.components.autocomplete
712  *
713  * @description
714  * The `md-highlight-text` directive allows you to specify text that should be highlighted within
715  * an element.  Highlighted text will be wrapped in `<span class="highlight"></span>` which can
716  * be styled through CSS.  Please note that child elements may not be used with this directive.
717  *
718  * @param {string} md-highlight-text A model to be searched for
719  * @param {string=} md-highlight-flags A list of flags (loosely based on JavaScript RexExp flags).
720  *    #### **Supported flags**:
721  *    - `g`: Find all matches within the provided text
722  *    - `i`: Ignore case when searching for matches
723  *    - `$`: Only match if the text ends with the search term
724  *    - `^`: Only match if the text begins with the search term
725  *
726  * @usage
727  * <hljs lang="html">
728  * <input placeholder="Enter a search term..." ng-model="searchTerm" type="text" />
729  * <ul>
730  *   <li ng-repeat="result in results" md-highlight-text="searchTerm">
731  *     {{result.text}}
732  *   </li>
733  * </ul>
734  * </hljs>
735  */
736
737 function MdHighlight () {
738   return {
739     terminal: true,
740     scope: false,
741     controller: 'MdHighlightCtrl',
742     link: function (scope, element, attr, ctrl) {
743       ctrl.init(attr.mdHighlightText);
744     }
745   };
746 }
747
748 angular
749     .module('material.components.autocomplete')
750     .directive('mdAutocompleteListItem', MdAutocompleteListItem);
751
752 function MdAutocompleteListItem ($compile, $mdUtil) {
753   return {
754     terminal: true,
755     link: postLink,
756     scope: false
757   };
758   function postLink (scope, element, attr) {
759     var ctrl     = scope.$parent.$mdAutocompleteCtrl,
760         newScope = ctrl.parent.$new(false, ctrl.parent),
761         itemName = ctrl.scope.$eval(attr.mdAutocompleteListItem);
762     newScope[itemName] = scope.item;
763     $compile(element.contents())(newScope);
764     element.attr({
765       role: 'option',
766       id: 'item_' + $mdUtil.nextUid()
767     });
768   }
769 }
770 MdAutocompleteListItem.$inject = ["$compile", "$mdUtil"];
771
772 angular
773     .module('material.components.autocomplete')
774     .directive('mdAutocompleteParentScope', MdAutocompleteParentScope);
775
776 function MdAutocompleteParentScope ($compile, $mdUtil) {
777   return {
778     restrict: 'A',
779     terminal: true,
780     link: postLink,
781     scope: false
782   };
783   function postLink (scope, element, attr) {
784     var ctrl     = scope.$parent.$mdAutocompleteCtrl;
785     $compile(element.contents())(ctrl.parent);
786     if (attr.hasOwnProperty('mdAutocompleteReplace')) {
787       element.after(element.contents());
788       element.remove();
789     }
790   }
791 }
792 MdAutocompleteParentScope.$inject = ["$compile", "$mdUtil"];
793
794 ng.material.components.autocomplete = angular.module("material.components.autocomplete");