arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/angular-material/modules/js/list/list.js
www/lib/angular-material/modules/js/list/list.jsRaw
  1/*!
  2 * Angular Material Design
  3 * https://github.com/angular/material
  4 * @license MIT
  5 * v1.1.1
  6 */
  7(function( window, angular, undefined ){
  8"use strict";
  9
 10/**
 11 * @ngdoc module
 12 * @name material.components.list
 13 * @description
 14 * List module
 15 */
 16MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"];
 17mdListDirective.$inject = ["$mdTheming"];
 18mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"];
 19angular.module('material.components.list', [
 20  'material.core'
 21])
 22  .controller('MdListController', MdListController)
 23  .directive('mdList', mdListDirective)
 24  .directive('mdListItem', mdListItemDirective);
 25
 26/**
 27 * @ngdoc directive
 28 * @name mdList
 29 * @module material.components.list
 30 *
 31 * @restrict E
 32 *
 33 * @description
 34 * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags.
 35 *
 36 * @usage
 37 * <hljs lang="html">
 38 * <md-list>
 39 *   <md-list-item class="md-2-line" ng-repeat="item in todos">
 40 *     <md-checkbox ng-model="item.done"></md-checkbox>
 41 *     <div class="md-list-item-text">
 42 *       <h3>{{item.title}}</h3>
 43 *       <p>{{item.description}}</p>
 44 *     </div>
 45 *   </md-list-item>
 46 * </md-list>
 47 * </hljs>
 48 */
 49
 50function mdListDirective($mdTheming) {
 51  return {
 52    restrict: 'E',
 53    compile: function(tEl) {
 54      tEl[0].setAttribute('role', 'list');
 55      return $mdTheming;
 56    }
 57  };
 58}
 59/**
 60 * @ngdoc directive
 61 * @name mdListItem
 62 * @module material.components.list
 63 *
 64 * @restrict E
 65 *
 66 * @description
 67 * A `md-list-item` element can be used to represent some information in a row.<br/>
 68 *
 69 * @usage
 70 * ### Single Row Item
 71 * <hljs lang="html">
 72 *   <md-list-item>
 73 *     <span>Single Row Item</span>
 74 *   </md-list-item>
 75 * </hljs>
 76 *
 77 * ### Multiple Lines
 78 * By using the following markup, you will be able to have two lines inside of one `md-list-item`.
 79 *
 80 * <hljs lang="html">
 81 *   <md-list-item class="md-2-line">
 82 *     <div class="md-list-item-text" layout="column">
 83 *       <p>First Line</p>
 84 *       <p>Second Line</p>
 85 *     </div>
 86 *   </md-list-item>
 87 * </hljs>
 88 *
 89 * It is also possible to have three lines inside of one list item.
 90 *
 91 * <hljs lang="html">
 92 *   <md-list-item class="md-3-line">
 93 *     <div class="md-list-item-text" layout="column">
 94 *       <p>First Line</p>
 95 *       <p>Second Line</p>
 96 *       <p>Third Line</p>
 97 *     </div>
 98 *   </md-list-item>
 99 * </hljs>
100 *
101 * ### Secondary Items
102 * Secondary items are elements which will be aligned at the end of the `md-list-item`.
103 *
104 * <hljs lang="html">
105 *   <md-list-item>
106 *     <span>Single Row Item</span>
107 *     <md-button class="md-secondary">
108 *       Secondary Button
109 *     </md-button>
110 *   </md-list-item>
111 * </hljs>
112 *
113 * It also possible to have multiple secondary items inside of one `md-list-item`.
114 *
115 * <hljs lang="html">
116 *   <md-list-item>
117 *     <span>Single Row Item</span>
118 *     <md-button class="md-secondary">First Button</md-button>
119 *     <md-button class="md-secondary">Second Button</md-button>
120 *   </md-list-item>
121 * </hljs>
122 *
123 * ### Proxy Item
124 * Proxies are elements, which will execute their specific action on click<br/>
125 * Currently supported proxy items are
126 * - `md-checkbox` (Toggle)
127 * - `md-switch` (Toggle)
128 * - `md-menu` (Open)
129 *
130 * This means, when using a supported proxy item inside of `md-list-item`, the list item will
131 * become clickable and executes the associated action of the proxy element on click.
132 *
133 * <hljs lang="html">
134 *   <md-list-item>
135 *     <span>First Line</span>
136 *     <md-checkbox class="md-secondary"></md-checkbox>
137 *   </md-list-item>
138 * </hljs>
139 *
140 * The `md-checkbox` element will be automatically detected as a proxy element and will toggle on click.
141 *
142 * <hljs lang="html">
143 *   <md-list-item>
144 *     <span>First Line</span>
145 *     <md-switch class="md-secondary"></md-switch>
146 *   </md-list-item>
147 * </hljs>
148 *
149 * The recognized `md-switch` will toggle its state, when the user clicks on the `md-list-item`.
150 *
151 * It is also possible to have a `md-menu` inside of a `md-list-item`.
152 * <hljs lang="html">
153 *   <md-list-item>
154 *     <p>Click anywhere to fire the secondary action</p>
155 *     <md-menu class="md-secondary">
156 *       <md-button class="md-icon-button">
157 *         <md-icon md-svg-icon="communication:message"></md-icon>
158 *       </md-button>
159 *       <md-menu-content width="4">
160 *         <md-menu-item>
161 *           <md-button>
162 *             Redial
163 *           </md-button>
164 *         </md-menu-item>
165 *         <md-menu-item>
166 *           <md-button>
167 *             Check voicemail
168 *           </md-button>
169 *         </md-menu-item>
170 *         <md-menu-divider></md-menu-divider>
171 *         <md-menu-item>
172 *           <md-button>
173 *             Notifications
174 *           </md-button>
175 *         </md-menu-item>
176 *       </md-menu-content>
177 *     </md-menu>
178 *   </md-list-item>
179 * </hljs>
180 *
181 * The menu will automatically open, when the users clicks on the `md-list-item`.<br/>
182 *
183 * If the developer didn't specify any position mode on the menu, the `md-list-item` will automatically detect the
184 * position mode and applies it to the `md-menu`.
185 *
186 * ### Avatars
187 * Sometimes you may want to have some avatars inside of the `md-list-item `.<br/>
188 * You are able to create a optimized icon for the list item, by applying the `.md-avatar` class on the `<img>` element.
189 *
190 * <hljs lang="html">
191 *   <md-list-item>
192 *     <img src="my-avatar.png" class="md-avatar">
193 *     <span>Alan Turing</span>
194 * </hljs>
195 *
196 * When using `<md-icon>` for an avater, you have to use the `.md-avatar-icon` class.
197 * <hljs lang="html">
198 *   <md-list-item>
199 *     <md-icon class="md-avatar-icon" md-svg-icon="avatars:timothy"></md-icon>
200 *     <span>Timothy Kopra</span>
201 *   </md-list-item>
202 * </hljs>
203 *
204 * In cases, you have a `md-list-item`, which doesn't have any avatar,
205 * but you want to align it with the other avatar items, you have to use the `.md-offset` class.
206 *
207 * <hljs lang="html">
208 *   <md-list-item class="md-offset">
209 *     <span>Jon Doe</span>
210 *   </md-list-item>
211 * </hljs>
212 *
213 * ### DOM modification
214 * The `md-list-item` component automatically detects if the list item should be clickable.
215 *
216 * ---
217 * If the `md-list-item` is clickable, we wrap all content inside of a `<div>` and create
218 * an overlaying button, which will will execute the given actions (like `ng-href`, `ng-click`)
219 *
220 * We create an overlaying button, instead of wrapping all content inside of the button,
221 * because otherwise some elements may not be clickable inside of the button.
222 *
223 * ---
224 * When using a secondary item inside of your list item, the `md-list-item` component will automatically create
225 * a secondary container at the end of the `md-list-item`, which contains all secondary items.
226 *
227 * The secondary item container is not static, because otherwise the overflow will not work properly on the
228 * list item.
229 *
230 */
231function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
232  var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu'];
233  return {
234    restrict: 'E',
235    controller: 'MdListController',
236    compile: function(tEl, tAttrs) {
237
238      // Check for proxy controls (no ng-click on parent, and a control inside)
239      var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
240      var hasProxiedElement;
241      var proxyElement;
242      var itemContainer = tEl;
243
244      tEl[0].setAttribute('role', 'listitem');
245
246      if (tAttrs.ngClick || tAttrs.ngDblclick ||  tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) {
247        wrapIn('button');
248      } else {
249        for (var i = 0, type; type = proxiedTypes[i]; ++i) {
250          if (proxyElement = tEl[0].querySelector(type)) {
251            hasProxiedElement = true;
252            break;
253          }
254        }
255        if (hasProxiedElement) {
256          wrapIn('div');
257        } else if (!tEl[0].querySelector('md-button:not(.md-secondary):not(.md-exclude)')) {
258          tEl.addClass('md-no-proxy');
259        }
260      }
261
262      wrapSecondaryItems();
263      setupToggleAria();
264
265      if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") {
266        setupProxiedMenu();
267      }
268
269      function setupToggleAria() {
270        var toggleTypes = ['md-switch', 'md-checkbox'];
271        var toggle;
272
273        for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {
274          if (toggle = tEl.find(toggleType)[0]) {
275            if (!toggle.hasAttribute('aria-label')) {
276              var p = tEl.find('p')[0];
277              if (!p) return;
278              toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);
279            }
280          }
281        }
282      }
283
284      function setupProxiedMenu() {
285        var menuEl = angular.element(proxyElement);
286
287        var isEndAligned = menuEl.parent().hasClass('md-secondary-container') ||
288                           proxyElement.parentNode.firstElementChild !== proxyElement;
289
290        var xAxisPosition = 'left';
291
292        if (isEndAligned) {
293          // When the proxy item is aligned at the end of the list, we have to set the origin to the end.
294          xAxisPosition = 'right';
295        }
296        
297        // Set the position mode / origin of the proxied menu.
298        if (!menuEl.attr('md-position-mode')) {
299          menuEl.attr('md-position-mode', xAxisPosition + ' target');
300        }
301
302        // Apply menu open binding to menu button
303        var menuOpenButton = menuEl.children().eq(0);
304        if (!hasClickEvent(menuOpenButton[0])) {
305          menuOpenButton.attr('ng-click', '$mdOpenMenu($event)');
306        }
307
308        if (!menuOpenButton.attr('aria-label')) {
309          menuOpenButton.attr('aria-label', 'Open List Menu');
310        }
311      }
312
313      function wrapIn(type) {
314        if (type == 'div') {
315          itemContainer = angular.element('<div class="md-no-style md-list-item-inner">');
316          itemContainer.append(tEl.contents());
317          tEl.addClass('md-proxy-focus');
318        } else {
319          // Element which holds the default list-item content.
320          itemContainer = angular.element(
321            '<div class="md-button md-no-style">'+
322            '   <div class="md-list-item-inner"></div>'+
323            '</div>'
324          );
325
326          // Button which shows ripple and executes primary action.
327          var buttonWrap = angular.element(
328            '<md-button class="md-no-style"></md-button>'
329          );
330
331          buttonWrap[0].setAttribute('aria-label', tEl[0].textContent);
332
333          copyAttributes(tEl[0], buttonWrap[0]);
334
335          // We allow developers to specify the `md-no-focus` class, to disable the focus style
336          // on the button executor. Once more classes should be forwarded, we should probably make the
337          // class forward more generic.
338          if (tEl.hasClass('md-no-focus')) {
339            buttonWrap.addClass('md-no-focus');
340          }
341
342          // Append the button wrap before our list-item content, because it will overlay in relative.
343          itemContainer.prepend(buttonWrap);
344          itemContainer.children().eq(1).append(tEl.contents());
345
346          tEl.addClass('_md-button-wrap');
347        }
348
349        tEl[0].setAttribute('tabindex', '-1');
350        tEl.append(itemContainer);
351      }
352
353      function wrapSecondaryItems() {
354        var secondaryItemsWrapper = angular.element('<div class="md-secondary-container">');
355
356        angular.forEach(secondaryItems, function(secondaryItem) {
357          wrapSecondaryItem(secondaryItem, secondaryItemsWrapper);
358        });
359
360        itemContainer.append(secondaryItemsWrapper);
361      }
362
363      function wrapSecondaryItem(secondaryItem, container) {
364        // If the current secondary item is not a button, but contains a ng-click attribute,
365        // the secondary item will be automatically wrapped inside of a button.
366        if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) {
367
368          $mdAria.expect(secondaryItem, 'aria-label');
369          var buttonWrapper = angular.element('<md-button class="md-secondary md-icon-button">');
370
371          // Copy the attributes from the secondary item to the generated button.
372          // We also support some additional attributes from the secondary item,
373          // because some developers may use a ngIf, ngHide, ngShow on their item.
374          copyAttributes(secondaryItem, buttonWrapper[0], ['ng-if', 'ng-hide', 'ng-show']);
375
376          secondaryItem.setAttribute('tabindex', '-1');
377          buttonWrapper.append(secondaryItem);
378
379          secondaryItem = buttonWrapper[0];
380        }
381
382        if (secondaryItem && (!hasClickEvent(secondaryItem) || (!tAttrs.ngClick && isProxiedElement(secondaryItem)))) {
383          // In this case we remove the secondary class, so we can identify it later, when we searching for the
384          // proxy items.
385          angular.element(secondaryItem).removeClass('md-secondary');
386        }
387
388        tEl.addClass('md-with-secondary');
389        container.append(secondaryItem);
390      }
391
392      /**
393       * Copies attributes from a source element to the destination element
394       * By default the function will copy the most necessary attributes, supported
395       * by the button executor for clickable list items.
396       * @param source Element with the specified attributes
397       * @param destination Element which will retrieve the attributes
398       * @param extraAttrs Additional attributes, which will be copied over.
399       */
400      function copyAttributes(source, destination, extraAttrs) {
401        var copiedAttrs = $mdUtil.prefixer([
402          'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref',
403          'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'
404        ]);
405
406        if (extraAttrs) {
407          copiedAttrs = copiedAttrs.concat($mdUtil.prefixer(extraAttrs));
408        }
409
410        angular.forEach(copiedAttrs, function(attr) {
411          if (source.hasAttribute(attr)) {
412            destination.setAttribute(attr, source.getAttribute(attr));
413            source.removeAttribute(attr);
414          }
415        });
416      }
417
418      function isProxiedElement(el) {
419        return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;
420      }
421
422      function isButton(el) {
423        var nodeName = el.nodeName.toUpperCase();
424
425        return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
426      }
427
428      function hasClickEvent (element) {
429        var attr = element.attributes;
430        for (var i = 0; i < attr.length; i++) {
431          if (tAttrs.$normalize(attr[i].name) === 'ngClick') return true;
432        }
433        return false;
434      }
435
436      return postLink;
437
438      function postLink($scope, $element, $attr, ctrl) {
439        $element.addClass('_md');     // private md component indicator for styling
440
441        var proxies       = [],
442            firstElement  = $element[0].firstElementChild,
443            isButtonWrap  = $element.hasClass('_md-button-wrap'),
444            clickChild    = isButtonWrap ? firstElement.firstElementChild : firstElement,
445            hasClick      = clickChild && hasClickEvent(clickChild);
446
447        computeProxies();
448        computeClickable();
449
450        if ($element.hasClass('md-proxy-focus') && proxies.length) {
451          angular.forEach(proxies, function(proxy) {
452            proxy = angular.element(proxy);
453
454            $scope.mouseActive = false;
455            proxy.on('mousedown', function() {
456              $scope.mouseActive = true;
457              $timeout(function(){
458                $scope.mouseActive = false;
459              }, 100);
460            })
461            .on('focus', function() {
462              if ($scope.mouseActive === false) { $element.addClass('md-focused'); }
463              proxy.on('blur', function proxyOnBlur() {
464                $element.removeClass('md-focused');
465                proxy.off('blur', proxyOnBlur);
466              });
467            });
468          });
469        }
470
471
472        function computeProxies() {
473          if (firstElement && firstElement.children && !hasClick) {
474
475            angular.forEach(proxiedTypes, function(type) {
476
477              // All elements which are not capable for being used a proxy have the .md-secondary class
478              // applied. These items had been sorted out in the secondary wrap function.
479              angular.forEach(firstElement.querySelectorAll(type + ':not(.md-secondary)'), function(child) {
480                proxies.push(child);
481              });
482            });
483
484          }
485        }
486
487        function computeClickable() {
488          if (proxies.length == 1 || hasClick) {
489            $element.addClass('md-clickable');
490
491            if (!hasClick) {
492              ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style')));
493            }
494          }
495        }
496
497        function isEventFromControl(event) {
498          var forbiddenControls = ['md-slider'];
499
500          // If there is no path property in the event, then we can assume that the event was not bubbled.
501          if (!event.path) {
502            return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1;
503          }
504
505          // We iterate the event path up and check for a possible component.
506          // Our maximum index to search, is the list item root.
507          var maxPath = event.path.indexOf($element.children()[0]);
508
509          for (var i = 0; i < maxPath; i++) {
510            if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) {
511              return true;
512            }
513          }
514        }
515
516        var clickChildKeypressListener = function(e) {
517          if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
518            var keyCode = e.which || e.keyCode;
519            if (keyCode == $mdConstant.KEY_CODE.SPACE) {
520              if (clickChild) {
521                clickChild.click();
522                e.preventDefault();
523                e.stopPropagation();
524              }
525            }
526          }
527        };
528
529        if (!hasClick && !proxies.length) {
530          clickChild && clickChild.addEventListener('keypress', clickChildKeypressListener);
531        }
532
533        $element.off('click');
534        $element.off('keypress');
535
536        if (proxies.length == 1 && clickChild) {
537          $element.children().eq(0).on('click', function(e) {
538            // When the event is coming from an control and it should not trigger the proxied element
539            // then we are skipping.
540            if (isEventFromControl(e)) return;
541
542            var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
543            if (!parentButton && clickChild.contains(e.target)) {
544              angular.forEach(proxies, function(proxy) {
545                if (e.target !== proxy && !proxy.contains(e.target)) {
546                  if (proxy.nodeName === 'MD-MENU') {
547                    proxy = proxy.children[0];
548                  }
549                  angular.element(proxy).triggerHandler('click');
550                }
551              });
552            }
553          });
554        }
555
556        $scope.$on('$destroy', function () {
557          clickChild && clickChild.removeEventListener('keypress', clickChildKeypressListener);
558        });
559      }
560    }
561  };
562}
563
564/*
565 * @private
566 * @ngdoc controller
567 * @name MdListController
568 * @module material.components.list
569 *
570 */
571function MdListController($scope, $element, $mdListInkRipple) {
572  var ctrl = this;
573  ctrl.attachRipple = attachRipple;
574
575  function attachRipple (scope, element) {
576    var options = {};
577    $mdListInkRipple.attach(scope, element, options);
578  }
579}
580
581})(window, window.angular);