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");