Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
feat(tab): Get tabs by their ID (#4149)
Browse files Browse the repository at this point in the history
Use unique identifiers to reference tabs instead of the object equality comparison. Change the getIndexOfTab method to getIndexOfTabByID. Update tests.

This makes it more feasible to wrap MDC Tab Bar for frameworks, which shouldn't need to reference the vanilla component.

BREAKING CHANGE: MDCTabBar#getIndexOfTab(tab: MDCTab): boolean is now MDCTabBar#getIndexOfTabByID(id: string): boolean
  • Loading branch information
patrickrodee authored Dec 10, 2018
1 parent 2f6dda2 commit 2d35220
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 29 deletions.
2 changes: 1 addition & 1 deletion packages/mdc-tab-bar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Method Signature | Description
`getTabListLength() => number` | Returns the number of child Tab components.
`getPreviousActiveTabIndex() => number` | Returns the index of the previously active Tab.
`getFocusedTabIndex() => number` | Returns the index of the focused Tab.
`getIndexOfTab(tab: MDCTab) => number` | Returns the index of the given Tab instance.
`getIndexOfTabById(id: string) => number` | Returns the index of the given Tab ID.
`notifyTabActivated(index: number) => void` | Emits the `MDCTabBar:activated` event.

### `MDCTabBarFoundation`
Expand Down
5 changes: 2 additions & 3 deletions packages/mdc-tab-bar/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

/* eslint-disable no-unused-vars */
import {MDCTabDimensions} from '@material/tab/adapter';
import {MDCTab} from '@material/tab/index';
/* eslint-enable no-unused-vars */

/**
Expand Down Expand Up @@ -134,10 +133,10 @@ class MDCTabBarAdapter {

/**
* Returns the index of the given tab
* @param {!MDCTab} tab The tab whose index to determin
* @param {string} id The ID of the tab whose index to determine
* @return {number}
*/
getIndexOfTab(tab) {}
getIndexOfTabById(id) {}

/**
* Emits the MDCTabBar:activated event
Expand Down
4 changes: 2 additions & 2 deletions packages/mdc-tab-bar/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class MDCTabBarFoundation extends MDCFoundation {
getTabDimensionsAtIndex: () => {},
getPreviousActiveTabIndex: () => {},
getFocusedTabIndex: () => {},
getIndexOfTab: () => {},
getIndexOfTabById: () => {},
getTabListLength: () => {},
notifyTabActivated: () => {},
});
Expand Down Expand Up @@ -174,7 +174,7 @@ class MDCTabBarFoundation extends MDCFoundation {
* @param {!Event} evt
*/
handleTabInteraction(evt) {
this.adapter_.setActiveTab(this.adapter_.getIndexOfTab(evt.detail.tab));
this.adapter_.setActiveTab(this.adapter_.getIndexOfTabById(evt.detail.tabId));
}

/**
Expand Down
60 changes: 44 additions & 16 deletions packages/mdc-tab-bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {MDCTabScroller} from '@material/tab-scroller/index';
import MDCTabBarAdapter from './adapter';
import MDCTabBarFoundation from './foundation';

let tabIdCounter = 0;

/**
* @extends {MDCComponent<!MDCTabBarFoundation>}
* @final
Expand All @@ -43,15 +45,9 @@ class MDCTabBar extends MDCComponent {
/** @private {!Array<!MDCTab>} */
this.tabList_;

/** @type {(function(!Element): !MDCTab)} */
this.tabFactory_;

/** @private {?MDCTabScroller} */
this.tabScroller_;

/** @type {(function(!Element): !MDCTabScroller)} */
this.tabScrollerFactory_;

/** @private {?function(?Event): undefined} */
this.handleTabInteraction_;

Expand Down Expand Up @@ -82,15 +78,8 @@ class MDCTabBar extends MDCComponent {
initialize(
tabFactory = (el) => new MDCTab(el),
tabScrollerFactory = (el) => new MDCTabScroller(el)) {
this.tabFactory_ = tabFactory;
this.tabScrollerFactory_ = tabScrollerFactory;

this.tabList_ = this.getTabElements_().map((el) => this.tabFactory_(el));

const tabScrollerElement = this.root_.querySelector(MDCTabBarFoundation.strings.TAB_SCROLLER_SELECTOR);
if (tabScrollerElement) {
this.tabScroller_ = this.tabScrollerFactory_(tabScrollerElement);
}
this.tabList_ = this.instantiateTabs_(tabFactory);
this.tabScroller_ = this.instantiateTabScroller_(tabScrollerFactory);
}

initialSyncWithDOM() {
Expand Down Expand Up @@ -147,7 +136,14 @@ class MDCTabBar extends MDCComponent {
const activeElement = document.activeElement;
return tabElements.indexOf(activeElement);
},
getIndexOfTab: (tabToFind) => this.tabList_.indexOf(tabToFind),
getIndexOfTabById: (id) => {
for (let i = 0; i < this.tabList_.length; i++) {
if (this.tabList_[i].id === id) {
return i;
}
}
return -1;
},
getTabListLength: () => this.tabList_.length,
notifyTabActivated: (index) => this.emit(MDCTabBarFoundation.strings.TAB_ACTIVATED_EVENT, {index}, true),
})
Expand All @@ -170,9 +166,41 @@ class MDCTabBar extends MDCComponent {
this.foundation_.scrollIntoView(index);
}

/**
* Returns all the tab elements in a nice clean array
* @return {!Array<!Element>}
* @private
*/
getTabElements_() {
return [].slice.call(this.root_.querySelectorAll(MDCTabBarFoundation.strings.TAB_SELECTOR));
}

/**
* Instantiates tab components on all child tab elements
* @param {(function(!Element): !MDCTab)} tabFactory
* @return {!Array<!MDCTab>}
* @private
*/
instantiateTabs_(tabFactory) {
return this.getTabElements_().map((el) => {
el.id = el.id || `mdc-tab-${++tabIdCounter}`;
return tabFactory(el);
});
}

/**
* Instantiates tab scroller component on the child tab scroller element
* @param {(function(!Element): !MDCTabScroller)} tabScrollerFactory
* @return {?MDCTabScroller}
* @private
*/
instantiateTabScroller_(tabScrollerFactory) {
const tabScrollerElement = this.root_.querySelector(MDCTabBarFoundation.strings.TAB_SCROLLER_SELECTOR);
if (tabScrollerElement) {
return tabScrollerFactory(tabScrollerElement);
}
return null;
}
}

export {MDCTabBar, MDCTabBarFoundation};
2 changes: 1 addition & 1 deletion packages/mdc-tab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Method Signature | Description

Event Name | Event Data Structure | Description
--- | --- | ---
`MDCTab:interacted` | `{"detail": {"tab": MDCTab}}` | Emitted when the Tab is interacted with, regardless of its active state. Used by parent components to know which Tab to activate.
`MDCTab:interacted` | `{"detail": {"tabId": string}}` | Emitted when the Tab is interacted with, regardless of its active state. Used by parent components to know which Tab to activate.

## Usage within Web Frameworks

Expand Down
12 changes: 11 additions & 1 deletion packages/mdc-tab/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
*/
let MDCTabDimensions;

/**
* @typedef {{
* detail: {
* tabId: string,
* },
* bubbles: boolean,
* }}
*/
let MDCTabInteractionEventType;

/**
* Adapter for MDC Tab.
*
Expand Down Expand Up @@ -112,4 +122,4 @@ class MDCTabAdapter {
focus() {}
}

export {MDCTabDimensions, MDCTabAdapter};
export {MDCTabDimensions, MDCTabInteractionEventType, MDCTabAdapter};
9 changes: 7 additions & 2 deletions packages/mdc-tab/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import MDCComponent from '@material/base/component';
/* eslint-disable no-unused-vars */
import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple/index';
import {MDCTabIndicator, MDCTabIndicatorFoundation} from '@material/tab-indicator/index';
import {MDCTabAdapter, MDCTabDimensions} from './adapter';
import {MDCTabAdapter, MDCTabDimensions, MDCTabInteractionEventType} from './adapter';
/* eslint-enable no-unused-vars */

import MDCTabFoundation from './foundation';
Expand All @@ -41,6 +41,9 @@ class MDCTab extends MDCComponent {
*/
constructor(...args) {
super(...args);

/** @type {string} */
this.id;
/** @private {?MDCRipple} */
this.ripple_;
/** @private {?MDCTabIndicator} */
Expand All @@ -63,6 +66,7 @@ class MDCTab extends MDCComponent {
initialize(
rippleFactory = (el, foundation) => new MDCRipple(el, foundation),
tabIndicatorFactory = (el) => new MDCTabIndicator(el)) {
this.id = this.root_.id;
const rippleSurface = this.root_.querySelector(MDCTabFoundation.strings.RIPPLE_SELECTOR);
const rippleAdapter = Object.assign(MDCRipple.createAdapter(/** @type {!RippleCapableSurface} */ (this)), {
addClass: (className) => rippleSurface.classList.add(className),
Expand Down Expand Up @@ -101,7 +105,8 @@ class MDCTab extends MDCComponent {
hasClass: (className) => this.root_.classList.contains(className),
activateIndicator: (previousIndicatorClientRect) => this.tabIndicator_.activate(previousIndicatorClientRect),
deactivateIndicator: () => this.tabIndicator_.deactivate(),
notifyInteracted: () => this.emit(MDCTabFoundation.strings.INTERACTED_EVENT, {tab: this}, true /* bubble */),
notifyInteracted: () => this.emit(
MDCTabFoundation.strings.INTERACTED_EVENT, {tabId: this.id}, true /* bubble */),
getOffsetLeft: () => this.root_.offsetLeft,
getOffsetWidth: () => this.root_.offsetWidth,
getContentOffsetLeft: () => this.content_.offsetLeft,
Expand Down
2 changes: 1 addition & 1 deletion test/unit/mdc-tab-bar/foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test('defaultAdapter returns a complete adapter implementation', () => {
'getOffsetWidth', 'isRTL', 'setActiveTab',
'activateTabAtIndex', 'deactivateTabAtIndex', 'focusTabAtIndex',
'getTabIndicatorClientRectAtIndex', 'getTabDimensionsAtIndex',
'getPreviousActiveTabIndex', 'getFocusedTabIndex', 'getIndexOfTab', 'getTabListLength',
'getPreviousActiveTabIndex', 'getFocusedTabIndex', 'getIndexOfTabById', 'getTabListLength',
'notifyTabActivated',
]);
});
Expand Down
6 changes: 4 additions & 2 deletions test/unit/mdc-tab-bar/mdc-tab-bar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ test('attachTo returns an MDCTabBar instance', () => {
assert.isOk(MDCTabBar.attachTo(getFixture()) instanceof MDCTabBar);
});

let fakeTabIdCounter = 0;
class FakeTab {
constructor() {
this.id = `mdc-tab-${++fakeTabIdCounter}`;
this.destroy = td.function();
this.activate = td.function();
this.deactivate = td.function();
Expand Down Expand Up @@ -206,10 +208,10 @@ test('#adapter.getPreviousActiveTabIndex returns the index of the active tab', (
assert.strictEqual(component.getDefaultFoundation().adapter_.getPreviousActiveTabIndex(), 1);
});

test('#adapter.getIndexOfTab returns the index of the given tab', () => {
test('#adapter.getIndexOfTabById returns the index of the given tab', () => {
const {component} = setupTest();
const tab = component.tabList_[2];
assert.strictEqual(component.getDefaultFoundation().adapter_.getIndexOfTab(tab), 2);
assert.strictEqual(component.getDefaultFoundation().adapter_.getIndexOfTabById(tab.id), 2);
});

test('#adapter.getTabListLength returns the length of the tab list', () => {
Expand Down

0 comments on commit 2d35220

Please sign in to comment.