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