Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
refactor(ripple): adds support for non-fixed tabs
Browse files Browse the repository at this point in the history
Closes #825.
Closes #460.
  • Loading branch information
robertmesserle committed Dec 12, 2014
1 parent d192083 commit a2c5961
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 154 deletions.
16 changes: 7 additions & 9 deletions src/components/tabs/demoDynamicTabs/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@
height: 300px;
text-align: center;
}
.tab0 {
background-color: #E3F2FD;
.tab0, .tab1, .tab2, .tab3 {
background-color: #bbdefb;
}
.tab1 {
background-color: #E1F5FE;
}
.tab2 {
background-color: #E0F7FA;

.md-header {
background-color: #1976D2 !important;
}
.tab3 {
background-color: #E0F2F1;
md-tabs-ink-bar {
background-color: #FFFF8D !important;
}

.title {
Expand Down
46 changes: 23 additions & 23 deletions src/components/tabs/js/inkBarDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
angular.module('material.components.tabs')
.directive('mdTabsInkBar', MdTabInkDirective);

function MdTabInkDirective($mdConstant, $window, $$rAF, $timeout) {
function MdTabInkDirective($$rAF) {

var lastIndex = 0;

return {
restrict: 'E',
Expand All @@ -18,40 +20,38 @@ function MdTabInkDirective($mdConstant, $window, $$rAF, $timeout) {
};

function postLink(scope, element, attr, ctrls) {
var nobar = ctrls[0],
tabsCtrl = ctrls[1],
timeout;
if (ctrls[0]) return;

if (nobar) return;
var tabsCtrl = ctrls[1],
debouncedUpdateBar = $$rAF.debounce(updateBar);

tabsCtrl.inkBarElement = element;

scope.$watch(tabsCtrl.selected, updateBar);
scope.$on('$mdTabsChanged', updateBar);
scope.$on('$mdTabsPaginationChanged', debouncedUpdateBar);

function updateBar() {
var selected = tabsCtrl.selected();
var selected = tabsCtrl.getSelectedItem();
var hideInkBar = !selected || tabsCtrl.count() < 2;

var hideInkBar = !selected || tabsCtrl.count() < 2 ||
(scope.pagination || {}).itemsPerPage === 1;
element.css('display', hideInkBar ? 'none' : 'block');

if (!hideInkBar) {
var count = tabsCtrl.count();
var scale = 1 / count;
var left = tabsCtrl.indexOf(selected);
element.css($mdConstant.CSS.TRANSFORM, 'scaleX(' + scale + ') ' +
'translate3d(' + left * 100 + '%,0,0)');
element.addClass('md-ink-bar-grow');
if (timeout) $timeout.cancel(timeout);
timeout = $timeout(function () {
element.removeClass('md-ink-bar-grow');
}, 250, false);
if (hideInkBar) return;

if (scope.pagination && scope.pagination.tabData) {
var index = tabsCtrl.getSelectedIndex();
var data = scope.pagination.tabData.tabs[index] || { left: 0, right: 0, width: 0 };
var right = element.parent().prop('offsetWidth') - data.right;
var classNames = ['md-transition-left', 'md-transition-right', 'md-no-transition'];
var classIndex = lastIndex > index ? 0 : lastIndex < index ? 1 : 2;

element
.removeClass(classNames.join(' '))
.addClass(classNames[classIndex])
.css({ left: data.left + 'px', right: right + 'px' });

lastIndex = index;
}
}

}

}
})();
187 changes: 116 additions & 71 deletions src/components/tabs/js/paginationDirective.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
(function() {
'use strict';


angular.module('material.components.tabs')
.directive('mdTabsPagination', TabPaginationDirective);
.directive('mdTabsPagination', TabPaginationDirective);

function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {
function TabPaginationDirective($mdConstant, $window, $$rAF, $$q) {

// TODO allow configuration of TAB_MIN_WIDTH
// Must match tab min-width rule in _tabs.scss
var TAB_MIN_WIDTH = 8 * 12;
// Must match (2 * width of paginators) in scss
var PAGINATORS_WIDTH = (8 * 4) * 2;

Expand All @@ -21,6 +17,7 @@ function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {

function postLink(scope, element, attr, tabsCtrl) {

var debouncedUpdatePagination = $$rAF.debounce(updatePagination);
var tabsParent = element.children();
var state = scope.pagination = {
page: -1,
Expand All @@ -29,20 +26,15 @@ function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {
clickPrevious: function() { userChangePage(-1); }
};

updatePagination();
var debouncedUpdatePagination = $$rAF.debounce(updatePagination);

scope.$on('$mdTabsChanged', debouncedUpdatePagination);
angular.element($window).on('resize', debouncedUpdatePagination);

scope.$on('$destroy', function() {
angular.element($window).off('resize', debouncedUpdatePagination);
});

scope.$watch(tabsCtrl.selected, onSelectedTabChange);
scope.$watch(function() {
return tabsCtrl.tabToFocus;
}, onTabFocus);
scope.$watch(tabsCtrl.getSelectedItem, onSelectedTabChange);
scope.$watch(function() { return tabsCtrl.tabToFocus; }, onTabFocus);

// Make sure we don't focus an element on the next page
// before it's in view
Expand All @@ -55,9 +47,7 @@ function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {
} else {
// Go to the new page, wait for the page transition to end, then focus.
oldTab && oldTab.element.blur();
setPage(pageIndex).then(function() {
tab.element.focus();
});
setPage(pageIndex).then(function() { tab.element.focus(); });
}
}

Expand All @@ -67,73 +57,52 @@ function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {
if (state.active) {
var selectedTabPage = getPageForTab(selectedTab);
setPage(selectedTabPage);
} else {
debouncedUpdatePagination();
}
}

// Called when page is changed by a user action (click)
function userChangePage(increment) {
var newPage = state.page + increment;
var newTab;
if (!tabsCtrl.selected() || getPageForTab(tabsCtrl.selected()) !== newPage) {
var startIndex;
if (increment < 0) {
// If going backward, select the previous available tab, starting from
// the first item on the page after newPage.
startIndex = (newPage + 1) * state.itemsPerPage;
newTab = tabsCtrl.previous( tabsCtrl.itemAt(startIndex) );
} else {
// If going forward, select the next available tab, starting with the
// last item before newPage.
startIndex = (newPage * state.itemsPerPage) - 1;
newTab = tabsCtrl.next( tabsCtrl.itemAt(startIndex) );
}
}
setPage(newPage).then(function() {
newTab && newTab.element.focus();
});
newTab && tabsCtrl.select(newTab);
var sizeData = state.tabData;
var newPage = Math.max(0, Math.min(sizeData.pages.length - 1, state.page + increment));
var newTabIndex = sizeData.pages[newPage][ increment > 0 ? 'firstTabIndex' : 'lastTabIndex' ];
var newTab = tabsCtrl.itemAt(newTabIndex);

setPage(newPage).then(function() { newTab.element.focus(); });
tabsCtrl.select(newTab);
}

function updatePagination() {
var tabs = element.find('md-tab');
var tabsWidth = element.parent().prop('clientWidth') - PAGINATORS_WIDTH;

var needPagination = tabsWidth && TAB_MIN_WIDTH * tabsCtrl.count() > tabsWidth;
var paginationToggled = needPagination !== state.active;
disablePagination();

// If the md-tabs element is not displayed, then do nothing.
if ( tabsWidth <= 0 ) {
needPagination = false;
paginationToggled = true;
}
var sizeData = state.tabData = calculateTabData();
var needPagination = state.active = sizeData.pages.length > 1;

state.active = needPagination;
if (needPagination) { enablePagination(); }

if (needPagination) {
scope.$evalAsync(function () { scope.$broadcast('$mdTabsPaginationChanged'); });

state.pagesCount = Math.ceil((TAB_MIN_WIDTH * tabsCtrl.count()) / tabsWidth);
state.itemsPerPage = Math.max(1, Math.floor(tabsCtrl.count() / state.pagesCount));
state.tabWidth = tabsWidth / state.itemsPerPage;
function enablePagination() {
tabsParent.css('width', '9999px');
state.pagesCount = sizeData.pages.length;

tabsParent.css('width', state.tabWidth * tabsCtrl.count() + 'px');
tabs.css('width', state.tabWidth + 'px');
//-- apply filler margins
angular.forEach(sizeData.tabs, function (tab) {
angular.element(tab.element).css('margin-left', tab.filler + 'px');
});

var selectedTabPage = getPageForTab(tabsCtrl.selected());
var selectedTabPage = getPageForTab(tabsCtrl.getSelectedItem());
setPage(selectedTabPage);
}

} else {

if (paginationToggled) {
$timeout(function() {
tabsParent.css('width', '');
tabs.css('width', '');
slideTabButtons(0);
state.page = -1;
});
}

function disablePagination() {
slideTabButtons(0);
tabsParent.css('width', '');
tabs.css('width', '');
tabs.css('margin-left', '');
state.page = -1;
state.active = false;
}
}

Expand All @@ -160,11 +129,89 @@ function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {
}
}

function stretchTabs() {
switch (scope.stretchTabs) {
case 'no': return false;
case 'yes': return true;
default: return window.innerWidth <= 600;
}
}

function calculateTabData() {
var clientWidth = element.parent().prop('offsetWidth');
var tabsWidth = clientWidth - PAGINATORS_WIDTH - 1;
var tabs = element[0].querySelectorAll('md-tab');
var totalWidth = 0;
var max = 0;
var tabData = [];
var pages = [];
var currentPage;

angular.element(tabs).css('max-width', '');
angular.forEach(tabs, function (tab, index) {
var tabWidth = Math.min(tabsWidth, tab.offsetWidth);
var data = {
element: tab,
left: totalWidth,
width: tabWidth,
right: totalWidth + tabWidth,
filler: 0
};
//-- store 1-based page number
data.page = pages.length === 1 && index === tabs.length - 1
? Math.ceil(data.right / clientWidth)
: Math.ceil(data.right / tabsWidth);
if (data.page > pages.length) {
data.filler = (tabsWidth * (data.page - 1)) - data.left;
data.right += data.filler;
data.left += data.filler;
currentPage = {
left: data.left,
firstTabIndex: index,
lastTabIndex: index,
tabs: [ tab ]
};
pages.push(currentPage);
} else {
currentPage.lastTabIndex = index;
currentPage.tabs.push(tab);
}
totalWidth = data.right;
max = Math.max(max, tabWidth);
tabData.push(data);
});
angular.element(tabs).css('max-width', tabsWidth + 'px');

if (pages.length === 1 && stretchTabs()) {
var tabWidth = Math.ceil(clientWidth / tabData.length);
if (tabWidth >= max) {
angular.element(tabs).css('width', tabWidth + 'px');
angular.forEach(tabData, function (tab, index) {
tab.width = tabWidth;
tab.left = tabWidth * index;
tab.right = tab.left + tab.width;
tab.filler = 0;
});
totalWidth = tabsWidth;
}
}

return {
width: totalWidth,
max: max,
tabs: tabData,
pages: pages,
tabElements: tabs
};
}

function getPageForTab(tab) {
var tabIndex = tabsCtrl.indexOf(tab);
if (tabIndex === -1) return 0;

return Math.floor(tabIndex / state.itemsPerPage);
var sizeData = state.tabData;

return sizeData ? sizeData.tabs[tabIndex].page - 1 : -1;
}

function setPage(page) {
Expand All @@ -176,15 +223,13 @@ function TabPaginationDirective($mdConstant, $window, $$rAF, $$q, $timeout) {
if (page > lastPage) page = lastPage;

state.hasPrev = page > 0;
state.hasNext = ((page + 1) * state.itemsPerPage) < tabsCtrl.count();
state.hasNext = page < state.tabData.pages.length - 1;

state.page = page;

$timeout(function() {
scope.$broadcast('$mdTabsPaginationChanged');
});
scope.$broadcast('$mdTabsPaginationChanged');

return slideTabButtons(-page * state.itemsPerPage * state.tabWidth);
return slideTabButtons(-state.tabData.pages[page].left);
}
}

Expand Down
Loading

0 comments on commit a2c5961

Please sign in to comment.