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

Commit

Permalink
refactor(tabs): reorganize, fix focus on pagination change
Browse files Browse the repository at this point in the history
  • Loading branch information
ajoslin committed Sep 9, 2014
1 parent 72beaec commit 4b8913e
Show file tree
Hide file tree
Showing 10 changed files with 1,442 additions and 1,212 deletions.
2 changes: 1 addition & 1 deletion config/build.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ module.exports = {
'src/components/slider/slider.js',
'src/components/switch/switch.js',
'src/components/tabs/tabs.js',
'src/components/tabs/util/*.js',
'src/components/tabs/js/*.js',
'src/components/toast/toast.js',
'src/components/toolbar/toolbar.js',
'src/components/whiteframe/whiteframe.js',
Expand Down
6 changes: 3 additions & 3 deletions docs/file-readers/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ module.exports = {
var jsonDir = path.dirname(filePath);

if (!json.module) {
throw new Error("paper.json has no `module` field!");
throw new Error("module.json has no `module` field!");
}

json = _.assign({
js: ['*.js', '!*.spec.js'],
js: ['*.js'],
scss: ['*.scss'],
readme: ['README.md'],
demos: {}
}, json);

//[].concat to coerce it to an array if it's not
var sources = getDocsForPatterns([].concat(json.js));
var sources = getDocsForPatterns(['!*.spec.js'].concat(json.js));
sources.forEach(function(doc) {
doc.docType = 'source';

Expand Down
14 changes: 1 addition & 13 deletions docs/processors/components-generate.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
var _ = require('lodash');
var path = require('canonical-path');

var sourceTypeByExtension = {
html: 'HTML',
css: 'CSS',
js: 'JavaScript',
'tmpl.html': 'Template'
};

module.exports = {
name: 'components-generate',
description: 'Transform the components into a renderable data structure',
Expand Down Expand Up @@ -124,16 +117,11 @@ module.exports = {
return demo;

function generateDemoFile(fromDoc) {
var extension = fromDoc.basePath.match(/.tmpl.html$/) ?
'tmpl.html' :
path.extname(fromDoc.basePath).substring(1); //remove starting '.'

return _.assign({}, fromDoc, {
template: fromDoc.basePath === 'index.html' ?
'demo/template.index.html' :
'demo/template.file',
outputPath: path.join(outputFolder, fromDoc.basePath),
viewType : sourceTypeByExtension[extension]
outputPath: path.join(outputFolder, fromDoc.basePath)
});
}
})
Expand Down
Empty file.
238 changes: 238 additions & 0 deletions src/components/tabs/js/tabDirective.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
angular.module('material.components.tabs')
.directive('materialTab', [
'$attrBind',
'$aria',
TabDirective
]);

/**
* @ngdoc directive
* @name materialTab
* @module material.components.tabs
* @order 1
*
* @restrict E
*
* @description
* `<material-tab>` is the nested directive used [within `<material-tabs>`] to specify each tab with a **label** and optional *view content*
*
* If the `label` attribute is not specified, then an optional `<material-tab-label>` tag can be used to specified more
* complex tab header markup. If neither the **label** nor the **material-tab-label** are specified, then the nested
* markup of the `<material-tab>` is used as the tab header markup.
*
* If a tab **label** has been identified, then any **non-**`<material-tab-label>` markup
* will be considered tab content and will be transcluded to the internal `<div class="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=} active Flag indicates if the tab is currently selected; normally the `<material-tabs selected="">`; attribute is used instead.
* @param {boolean=} ngDisabled Flag indicates if the tab is disabled: not selectable with no ink effects
* @param {expression=} deselected Expression to be evaluated after the tab has been de-selected.
* @param {expression=} selected Expression to be evaluated after the tab has been selected.
*
*
* @usage
*
* <hljs lang="html">
* <material-tab label="" disabled="" selected="" deselected="" >
* <h3>My Tab content</h3>
* </material-tab>
*
* <material-tab >
* <material-tab-label>
* <h3>My Tab content</h3>
* </material-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>
* </material-tab>
* </hljs>
*
*/
function TabDirective( $attrBind, $aria ) {
var noop = angular.noop;

return {
restrict: 'E',
replace: false,
require: "^materialTabs",
transclude: 'true',
scope: true,
link: linkTab,
template:
'<material-tab-label ink-ripple ' +
'ng-class="{ disabled : disabled, active : active }" >' +
'</material-tab-label>'
};

function linkTab(scope, element, attrs, tabsController, $transclude) {
var defaults = { active: false, disabled: false, deselected: noop, selected: noop };

// Since using scope=true for inherited new scope,
// then manually scan element attributes for forced local mappings...

$attrBind(scope, attrs, {
label: '@?',
active: '=?',
disabled: '=?ngDisabled',
deselected: '&onDeselect',
selected: '&onSelect'
}, defaults);

configureWatchers();
updateTabContent(scope);

// Update ARIA values for each tab element
configureAria(element, scope);

element.on('click', function onRequestSelect()
{
// Click support for entire <material-tab /> element
if ( !scope.disabled ) {
scope.$apply(function () {
tabsController.select(scope);
});
}
})
.on('keydown', function onRequestSelect(event)
{
if(event.which === Constant.KEY_CODE.LEFT_ARROW) {
tabsController.previous(scope);
}
if(event.which === Constant.KEY_CODE.RIGHT_ARROW) {
tabsController.next(scope);
}
});

tabsController.add(scope, element);

// **********************************************************
// Private Methods
// **********************************************************


/**
* Inject ARIA-specific attributes appropriate for each Tab button
*/
function configureAria( element, scope ){
var ROLE = Constant.ARIA.ROLE;

scope.ariaId = buildAriaID();
$aria.update( element, {
'id' : scope.ariaId,
'role' : ROLE.TAB,
'aria-selected' : false,
'aria-controls' : "content_" + scope.ariaId
});

/**
* Build a unique ID for each Tab that will be used for WAI-ARIA.
* Preserve existing ID if already specified.
* @returns {*|string}
*/
function buildAriaID() {
return attrs.id || ( ROLE.TAB + "_" + tabsController.$scope.$id + "_" + scope.$id );
}
}

/**
* Auto select the next tab if the current tab is active and
* has been disabled.
*
* Set tab index for the current tab (0), with all other tabs
* outside of the tab order (-1)
*
*/
function configureWatchers() {
var unwatch = scope.$watch('disabled', function (isDisabled) {
if (scope.active && isDisabled) {
tabsController.next(scope);
}
});

scope.$watch('active', function (isActive) {

$aria.update( element, {
'aria-selected' : isActive,
'tabIndex' : isActive === true ? 0 : -1
});

});

scope.$on("$destroy", function () {
unwatch();
tabsController.remove(scope);
});
}

/**
* Transpose the optional `label` attribute value or materialTabHeader or `content` body
* into the body of the materialTabButton... all other content is saved in scope.content
* and used by TabsController to inject into the `tabs-content` container.
*/
function updateTabContent(scope) {
var tab = scope;

// Check to override label attribute with the content of the <material-tab-header> node,
// If a materialTabHeader is not specified, then the node will be considered
// a <material-view> content element...
$transclude(function ( contents ) {

// Transient references...
tab.content = [ ];

angular.forEach(contents, function (node) {

if (!isNodeEmpty(node)) {
if (isNodeType(node, 'material-tab-label')) {
// Simulate use of `label` attribute

tab.label = node.childNodes;

} else {
// Transient references...
//
// Attach to scope for future transclusion into materialView(s)
// We need the bound scope for the content elements; which is NOT
// the scope of tab or material-view container...

tab.content.push(node);
}
}
});

});

// Prepare to assign the materialTabButton content
// Use the label attribute or fallback to TabHeader content

var cntr = angular.element(element[0].querySelector('material-tab-label'));

if (angular.isDefined(scope.label)) {
// The `label` attribute is the default source

cntr.append(scope.label);

delete scope.label;

} else {

// NOTE: If not specified, all markup and content is assumed
// to be used for the tab label.

angular.forEach(scope.content, function (node) {
cntr.append(node);
});

delete scope.content;
}
}

}
}
Loading

0 comments on commit 4b8913e

Please sign in to comment.