/*! * Angular Material Design * https://github.com/angular/material * @license MIT * v1.0.6 */ (function( window, angular, undefined ){ "use strict"; /** * @ngdoc module * @name material.components.list * @description * List module */ angular.module('material.components.list', [ 'material.core' ]) .controller('MdListController', MdListController) .directive('mdList', mdListDirective) .directive('mdListItem', mdListItemDirective); /** * @ngdoc directive * @name mdList * @module material.components.list * * @restrict E * * @description * The `<md-list>` directive is a list container for 1..n `<md-list-item>` tags. * * @usage * <hljs lang="html"> * <md-list> * <md-list-item class="md-2-line" ng-repeat="item in todos"> * <md-checkbox ng-model="item.done"></md-checkbox> * <div class="md-list-item-text"> * <h3>{{item.title}}</h3> * <p>{{item.description}}</p> * </div> * </md-list-item> * </md-list> * </hljs> */ function mdListDirective($mdTheming) { return { restrict: 'E', compile: function(tEl) { tEl[0].setAttribute('role', 'list'); return $mdTheming; } }; } mdListDirective.$inject = ["$mdTheming"]; /** * @ngdoc directive * @name mdListItem * @module material.components.list * * @restrict E * * @description * The `<md-list-item>` directive is a container intended for row items in a `<md-list>` container. * The `md-2-line` and `md-3-line` classes can be added to a `<md-list-item>` * to increase the height with 22px and 40px respectively. * * ## CSS * `.md-avatar` - class for image avatars * * `.md-avatar-icon` - class for icon avatars * * `.md-offset` - on content without an avatar * * @usage * <hljs lang="html"> * <md-list> * <md-list-item> * <img class="md-avatar" ng-src="path/to/img"/> * <span>Item content in list</span> * </md-list-item> * <md-list-item> * <md-icon class="md-avatar-icon" md-svg-icon="communication:phone"></md-icon> * <span>Item content in list</span> * </md-list-item> * </md-list> * </hljs> * * _**Note:** We automatically apply special styling when the inner contents are wrapped inside * of a `<md-button>` tag. This styling is automatically ignored for `class="md-secondary"` buttons * and you can include a class of `class="md-exclude"` if you need to use a non-secondary button * that is inside the list, but does not wrap the contents._ */ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { var proxiedTypes = ['md-checkbox', 'md-switch']; return { restrict: 'E', controller: 'MdListController', compile: function(tEl, tAttrs) { // Check for proxy controls (no ng-click on parent, and a control inside) var secondaryItem = tEl[0].querySelector('.md-secondary'); var hasProxiedElement; var proxyElement; tEl[0].setAttribute('role', 'listitem'); if (tAttrs.ngClick || tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) { wrapIn('button'); } else { for (var i = 0, type; type = proxiedTypes[i]; ++i) { if (proxyElement = tEl[0].querySelector(type)) { hasProxiedElement = true; break; } } if (hasProxiedElement) { wrapIn('div'); } else if (!tEl[0].querySelector('md-button:not(.md-secondary):not(.md-exclude)')) { tEl.addClass('md-no-proxy'); } } wrapSecondary(); setupToggleAria(); function setupToggleAria() { var toggleTypes = ['md-switch', 'md-checkbox']; var toggle; for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) { if (toggle = tEl.find(toggleType)[0]) { if (!toggle.hasAttribute('aria-label')) { var p = tEl.find('p')[0]; if (!p) return; toggle.setAttribute('aria-label', 'Toggle ' + p.textContent); } } } } function wrapIn(type) { var container; if (type == 'div') { container = angular.element('<div class="md-no-style md-list-item-inner">'); container.append(tEl.contents()); tEl.addClass('md-proxy-focus'); } else { container = angular.element('<md-button class="md-no-style"><div class="md-list-item-inner"></div></md-button>'); copyAttributes(tEl[0], container[0]); container.children().eq(0).append(tEl.contents()); } tEl[0].setAttribute('tabindex', '-1'); tEl.append(container); } function wrapSecondary() { if (secondaryItem && !isButton(secondaryItem) && secondaryItem.hasAttribute('ng-click')) { $mdAria.expect(secondaryItem, 'aria-label'); var buttonWrapper = angular.element('<md-button class="md-secondary-container md-icon-button">'); copyAttributes(secondaryItem, buttonWrapper[0]); secondaryItem.setAttribute('tabindex', '-1'); secondaryItem.classList.remove('md-secondary'); buttonWrapper.append(secondaryItem); secondaryItem = buttonWrapper[0]; } // Check for a secondary item and move it outside if ( secondaryItem && ( secondaryItem.hasAttribute('ng-click') || ( tAttrs.ngClick && isProxiedElement(secondaryItem) ) )) { tEl.addClass('md-with-secondary'); tEl.append(secondaryItem); } } function copyAttributes(item, wrapper) { var copiedAttrs = ['ng-if', 'ng-click', 'aria-label', 'ng-disabled', 'ui-sref', 'href', 'ng-href', 'ng-attr-ui-sref']; angular.forEach(copiedAttrs, function(attr) { if (item.hasAttribute(attr)) { wrapper.setAttribute(attr, item.getAttribute(attr)); item.removeAttribute(attr); } }); } function isProxiedElement(el) { return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1; } function isButton(el) { var nodeName = el.nodeName.toUpperCase(); return nodeName == "MD-BUTTON" || nodeName == "BUTTON"; } return postLink; function postLink($scope, $element, $attr, ctrl) { var proxies = [], firstChild = $element[0].firstElementChild, hasClick = firstChild && hasClickEvent(firstChild); computeProxies(); computeClickable(); if ($element.hasClass('md-proxy-focus') && proxies.length) { angular.forEach(proxies, function(proxy) { proxy = angular.element(proxy); $scope.mouseActive = false; proxy.on('mousedown', function() { $scope.mouseActive = true; $timeout(function(){ $scope.mouseActive = false; }, 100); }) .on('focus', function() { if ($scope.mouseActive === false) { $element.addClass('md-focused'); } proxy.on('blur', function proxyOnBlur() { $element.removeClass('md-focused'); proxy.off('blur', proxyOnBlur); }); }); }); } function hasClickEvent (element) { var attr = element.attributes; for (var i = 0; i < attr.length; i++) { if ($attr.$normalize(attr[i].name) === 'ngClick') return true; } return false; } function computeProxies() { var children = $element.children(); if (children.length && !children[0].hasAttribute('ng-click')) { angular.forEach(proxiedTypes, function(type) { angular.forEach(firstChild.querySelectorAll(type), function(child) { proxies.push(child); }); }); } } function computeClickable() { if (proxies.length == 1 || hasClick) { $element.addClass('md-clickable'); if (!hasClick) { ctrl.attachRipple($scope, angular.element($element[0].querySelector('.md-no-style'))); } } } if (!hasClick && !proxies.length) { firstChild && firstChild.addEventListener('keypress', function(e) { if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) { var keyCode = e.which || e.keyCode; if (keyCode == $mdConstant.KEY_CODE.SPACE) { if (firstChild) { firstChild.click(); e.preventDefault(); e.stopPropagation(); } } } }); } $element.off('click'); $element.off('keypress'); if (proxies.length == 1 && firstChild) { $element.children().eq(0).on('click', function(e) { var parentButton = $mdUtil.getClosest(e.target, 'BUTTON'); if (!parentButton && firstChild.contains(e.target)) { angular.forEach(proxies, function(proxy) { if (e.target !== proxy && !proxy.contains(e.target)) { angular.element(proxy).triggerHandler('click'); } }); } }); } } } }; } mdListItemDirective.$inject = ["$mdAria", "$mdConstant", "$mdUtil", "$timeout"]; /* * @private * @ngdoc controller * @name MdListController * @module material.components.list * */ function MdListController($scope, $element, $mdListInkRipple) { var ctrl = this; ctrl.attachRipple = attachRipple; function attachRipple (scope, element) { var options = {}; $mdListInkRipple.attach(scope, element, options); } } MdListController.$inject = ["$scope", "$element", "$mdListInkRipple"]; })(window, window.angular);