Files
sogo/UI/WebServerResources/scss/components/tabs/js/tabItemDirective.js

245 lines
8.1 KiB
JavaScript

(function() {
'use strict';
angular.module('material.components.tabs')
.directive('mdTab', MdTabDirective);
/**
* @ngdoc directive
* @name mdTab
* @module material.components.tabs
*
* @restrict E
*
* @description
* `<md-tab>` is the nested directive used [within `<md-tabs>`] to specify each tab with a **label** and optional *view content*.
*
* If the `label` attribute is not specified, then an optional `<md-tab-label>` 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 `<md-tab>` is used as the tab header markup.
*
* If a tab **label** has been identified, then any **non-**`<md-tab-label>` markup
* will be considered tab content and will be transcluded to the internal `<div class="md-tabs-content">` 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
*
* <hljs lang="html">
* <md-tab label="" disabled="" md-on-select="" md-on-deselect="" >
* <h3>My Tab content</h3>
* </md-tab>
*
* <md-tab >
* <md-tab-label>
* <h3>My Tab content</h3>
* </md-tab-label>
* <p>
* 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.
* </p>
* </md-tab>
* </hljs>
*
*/
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('<md-tab-label>').html(attr.label);
} else {
// If nothing is found, use the tab's content as the label
tabLabel = angular.element('<md-tab-label>')
.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
});
}
}
};
}
}
})();