(function() { 'use strict'; angular.module('material.components.tabs') .directive('mdTab', MdTabDirective); /** * @ngdoc directive * @name mdTab * @module material.components.tabs * * @restrict E * * @description * `` is the nested directive used [within ``] to specify each tab with a **label** and optional *view content*. * * If the `label` attribute is not specified, then an optional `` tag can be used to specify more * complex tab header markup. If neither the **label** nor the **md-tab-label** are specified, then the nested * markup of the `` is used as the tab header markup. * * If a tab **label** has been identified, then any **non-**`` markup * will be considered tab content and will be transcluded to the internal `
` container. * * This container is used by the TabsController to show/hide the active tab's content view. This synchronization is * automatically managed by the internal TabsController whenever the tab selection changes. Selection changes can * be initiated via data binding changes, programmatic invocation, or user gestures. * * @param {string=} label Optional attribute to specify a simple string as the tab label * @param {boolean=} md-active When evaluteing to true, selects the tab. * @param {boolean=} disabled If present, disabled tab selection. * @param {expression=} md-on-deselect Expression to be evaluated after the tab has been de-selected. * @param {expression=} md-on-select Expression to be evaluated after the tab has been selected. * * * @usage * * * *

My Tab content

*
* * * *

My Tab content

*
*

* Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, * totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae * dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, * sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. *

*
*
* */ function MdTabDirective($mdInkRipple, $compile, $mdUtil, $mdConstant, $timeout) { return { restrict: 'E', require: ['mdTab', '^mdTabs'], controller: '$mdTab', scope: { onSelect: '&mdOnSelect', onDeselect: '&mdOnDeselect', label: '@' }, compile: compile }; function compile(element, attr) { var tabLabel = element.find('md-tab-label'); if (tabLabel.length) { // If a tab label element is found, remove it for later re-use. tabLabel.remove(); } else if (angular.isDefined(attr.label)) { // Otherwise, try to use attr.label as the label tabLabel = angular.element('').html(attr.label); } else { // If nothing is found, use the tab's content as the label tabLabel = angular.element('') .append(element.contents().remove()); } // Everything that's left as a child is the tab's content. var tabContent = element.contents().remove(); return function postLink(scope, element, attr, ctrls) { var tabItemCtrl = ctrls[0]; // Controller for THIS tabItemCtrl var tabsCtrl = ctrls[1]; // Controller for ALL tabs scope.$watch( function () { return attr.label; }, function () { $timeout(function () { tabsCtrl.scope.$broadcast('$mdTabsChanged'); }, 0, false); } ); transcludeTabContent(); configureAria(); var detachRippleFn = $mdInkRipple.attachTabBehavior(scope, element, { colorElement: tabsCtrl.inkBarElement }); tabsCtrl.add(tabItemCtrl); scope.$on('$destroy', function() { detachRippleFn(); tabsCtrl.remove(tabItemCtrl); }); element.on('$destroy', function () { //-- wait for item to be removed from the dom $timeout(function () { tabsCtrl.scope.$broadcast('$mdTabsChanged'); }, 0, false); }); if (!angular.isDefined(attr.ngClick)) { element.on('click', defaultClickListener); } element.on('keydown', keydownListener); scope.onSwipe = onSwipe; if (angular.isNumber(scope.$parent.$index)) { watchNgRepeatIndex(); } if (angular.isDefined(attr.mdActive)) { watchActiveAttribute(); } watchDisabled(); function transcludeTabContent() { // Clone the label we found earlier, and $compile and append it var label = tabLabel.clone(); element.append(label); $compile(label)(scope.$parent); // Clone the content we found earlier, and mark it for later placement into // the proper content area. tabItemCtrl.content = tabContent.clone(); } //defaultClickListener isn't applied if the user provides an ngClick expression. function defaultClickListener() { scope.$apply(function() { tabsCtrl.select(tabItemCtrl); tabsCtrl.focus(tabItemCtrl); }); } function keydownListener(ev) { if (ev.keyCode == $mdConstant.KEY_CODE.SPACE || ev.keyCode == $mdConstant.KEY_CODE.ENTER ) { // Fire the click handler to do normal selection if space is pressed element.triggerHandler('click'); ev.preventDefault(); } else if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) { scope.$evalAsync(function() { tabsCtrl.focus(tabsCtrl.previous(tabItemCtrl)); }); } else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) { scope.$evalAsync(function() { tabsCtrl.focus(tabsCtrl.next(tabItemCtrl)); }); } } function onSwipe(ev) { scope.$apply(function() { if (ev.type === 'swipeleft') { tabsCtrl.select(tabsCtrl.next()); } else { tabsCtrl.select(tabsCtrl.previous()); } }); } // If tabItemCtrl is part of an ngRepeat, move the tabItemCtrl in our internal array // when its $index changes function watchNgRepeatIndex() { // The tabItemCtrl has an isolate scope, so we watch the $index on the parent. scope.$watch('$parent.$index', function $indexWatchAction(newIndex) { tabsCtrl.move(tabItemCtrl, newIndex); }); } function watchActiveAttribute() { var unwatch = scope.$parent.$watch('!!(' + attr.mdActive + ')', activeWatchAction); scope.$on('$destroy', unwatch); function activeWatchAction(isActive) { var isSelected = tabsCtrl.getSelectedItem() === tabItemCtrl; if (isActive && !isSelected) { tabsCtrl.select(tabItemCtrl); } else if (!isActive && isSelected) { tabsCtrl.deselect(tabItemCtrl); } } } function watchDisabled() { scope.$watch(tabItemCtrl.isDisabled, disabledWatchAction); function disabledWatchAction(isDisabled) { element.attr('aria-disabled', isDisabled); // Auto select `next` tab when disabled var isSelected = (tabsCtrl.getSelectedItem() === tabItemCtrl); if (isSelected && isDisabled) { tabsCtrl.select(tabsCtrl.next() || tabsCtrl.previous()); } } } function configureAria() { // Link together the content area and tabItemCtrl with an id var tabId = attr.id || ('tab_' + $mdUtil.nextUid()); element.attr({ id: tabId, role: 'tab', tabIndex: -1 //this is also set on select/deselect in tabItemCtrl }); // Only setup the contentContainer's aria attributes if tab content is provided if (tabContent.length) { var tabContentId = 'content_' + tabId; if (!element.attr('aria-controls')) { element.attr('aria-controls', tabContentId); } tabItemCtrl.contentContainer.attr({ id: tabContentId, role: 'tabpanel', 'aria-labelledby': tabId }); } } }; } } })();