Created
May 13, 2014 13:47
-
-
Save Coridyn/e813ac8a2fc669a9b22f to your computer and use it in GitHub Desktop.
A cut-down version of the ui-bootstrap tab component - this fixes the transclusion of tab header content.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* angular-ui-bootstrap | |
* http://angular-ui.github.io/bootstrap/ | |
* Version: 0.10.0 - 2014-05-05 | |
* License: MIT | |
*/ | |
angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.tabs"]); | |
angular.module("ui.bootstrap.tpls", ["template/tabs/tab.html","template/tabs/tabset.html"]); | |
/** | |
* @ngdoc overview | |
* @name ui.bootstrap.tabs | |
* | |
* @description | |
* AngularJS version of the tabs directive. | |
*/ | |
angular.module('ui.bootstrap.tabs', []) | |
.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { | |
var ctrl = this, | |
tabs = ctrl.tabs = $scope.tabs = []; | |
ctrl.select = function(tab) { | |
angular.forEach(tabs, function(tab) { | |
tab.active = false; | |
}); | |
tab.active = true; | |
}; | |
ctrl.addTab = function addTab(tab) { | |
tabs.push(tab); | |
if (tabs.length === 1 || tab.active) { | |
ctrl.select(tab); | |
} | |
}; | |
ctrl.removeTab = function removeTab(tab) { | |
var index = tabs.indexOf(tab); | |
//Select a new tab if the tab to be removed is selected | |
if (tab.active && tabs.length > 1) { | |
//If this is the last tab, select the previous tab. else, the next tab. | |
var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; | |
ctrl.select(tabs[newActiveIndex]); | |
} | |
tabs.splice(index, 1); | |
}; | |
}]) | |
/** | |
* @ngdoc directive | |
* @name ui.bootstrap.tabs.directive:tabset | |
* @restrict EA | |
* | |
* @description | |
* Tabset is the outer container for the tabs directive | |
* | |
* @param {boolean=} vertical Whether or not to use vertical styling for the tabs. | |
* @param {boolean=} justified Whether or not to use justified styling for the tabs. | |
* | |
* @example | |
<example module="ui.bootstrap"> | |
<file name="index.html"> | |
<tabset> | |
<tab heading="Tab 1"><b>First</b> Content!</tab> | |
<tab heading="Tab 2"><i>Second</i> Content!</tab> | |
</tabset> | |
<hr /> | |
<tabset vertical="true"> | |
<tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab> | |
<tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab> | |
</tabset> | |
<tabset justified="true"> | |
<tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab> | |
<tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab> | |
</tabset> | |
</file> | |
</example> | |
*/ | |
.directive('tabset', function() { | |
return { | |
restrict: 'EA', | |
transclude: true, | |
replace: true, | |
scope: {}, | |
controller: 'TabsetController', | |
templateUrl: 'template/tabs/tabset.html', | |
link: function(scope, element, attrs) { | |
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; | |
scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; | |
scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs'; | |
} | |
}; | |
}) | |
/** | |
* @ngdoc directive | |
* @name ui.bootstrap.tabs.directive:tab | |
* @restrict EA | |
* | |
* @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. | |
* @param {string=} select An expression to evaluate when the tab is selected. | |
* @param {boolean=} active A binding, telling whether or not this tab is selected. | |
* @param {boolean=} disabled A binding, telling whether or not this tab is disabled. | |
* | |
* @description | |
* Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. | |
* | |
* @example | |
<example module="ui.bootstrap"> | |
<file name="index.html"> | |
<div ng-controller="TabsDemoCtrl"> | |
<button class="btn btn-small" ng-click="items[0].active = true"> | |
Select item 1, using active binding | |
</button> | |
<button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled"> | |
Enable/disable item 2, using disabled binding | |
</button> | |
<br /> | |
<tabset> | |
<tab heading="Tab 1">First Tab</tab> | |
<tab select="alertMe()"> | |
<tab-heading><i class="icon-bell"></i> Alert me!</tab-heading> | |
Second Tab, with alert callback and html heading! | |
</tab> | |
<tab ng-repeat="item in items" | |
heading="{{item.title}}" | |
disabled="item.disabled" | |
active="item.active"> | |
{{item.content}} | |
</tab> | |
</tabset> | |
</div> | |
</file> | |
<file name="script.js"> | |
function TabsDemoCtrl($scope) { | |
$scope.items = [ | |
{ title:"Dynamic Title 1", content:"Dynamic Item 0" }, | |
{ title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } | |
]; | |
$scope.alertMe = function() { | |
setTimeout(function() { | |
alert("You've selected the alert tab!"); | |
}); | |
}; | |
}; | |
</file> | |
</example> | |
*/ | |
/** | |
* @ngdoc directive | |
* @name ui.bootstrap.tabs.directive:tabHeading | |
* @restrict EA | |
* | |
* @description | |
* Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. | |
* | |
* @example | |
<example module="ui.bootstrap"> | |
<file name="index.html"> | |
<tabset> | |
<tab> | |
<tab-heading><b>HTML</b> in my titles?!</tab-heading> | |
And some content, too! | |
</tab> | |
<tab> | |
<tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading> | |
That's right. | |
</tab> | |
</tabset> | |
</file> | |
</example> | |
*/ | |
.directive('tab', ['$parse', function($parse) { | |
return { | |
require: '^tabset', | |
restrict: 'EA', | |
replace: true, | |
templateUrl: 'template/tabs/tab.html', | |
transclude: true, | |
scope: { | |
heading: '@', | |
onSelect: '&select', //This callback is called in contentHeadingTransclude | |
//once it inserts the tab's content into the dom | |
onDeselect: '&deselect' | |
}, | |
controller: function() { | |
//Empty controller so other directives can require being 'under' a tab | |
}, | |
compile: function(elm, attrs, transclude) { | |
return function postLink(scope, elm, attrs, tabsetCtrl) { | |
var getActive, setActive; | |
if (attrs.active) { | |
getActive = $parse(attrs.active); | |
setActive = getActive.assign; | |
scope.$parent.$watch(getActive, function updateActive(value, oldVal) { | |
// Avoid re-initializing scope.active as it is already initialized | |
// below. (watcher is called async during init with value === | |
// oldVal) | |
if (value !== oldVal) { | |
scope.active = !!value; | |
} | |
}); | |
scope.active = getActive(scope.$parent); | |
} else { | |
setActive = getActive = angular.noop; | |
} | |
scope.$watch('active', function(active) { | |
// Note this watcher also initializes and assigns scope.active to the | |
// attrs.active expression. | |
setActive(scope.$parent, active); | |
if (active) { | |
tabsetCtrl.select(scope); | |
scope.onSelect(); | |
} else { | |
scope.onDeselect(); | |
} | |
}); | |
scope.disabled = false; | |
if ( attrs.disabled ) { | |
scope.$parent.$watch($parse(attrs.disabled), function(value) { | |
scope.disabled = !! value; | |
}); | |
} | |
scope.select = function() { | |
if ( ! scope.disabled ) { | |
scope.active = true; | |
} | |
}; | |
tabsetCtrl.addTab(scope); | |
scope.$on('$destroy', function() { | |
tabsetCtrl.removeTab(scope); | |
}); | |
//We need to transclude later, once the content container is ready. | |
//when this link happens, we're inside a tab heading. | |
scope.$transcludeFn = transclude; | |
}; | |
} | |
}; | |
}]) | |
/* Original behaviour:*/ | |
//.directive('tabHeadingTransclude', [function() { | |
// return { | |
// restrict: 'A', | |
// require: '^tab', | |
// link: function(scope, elm, attrs, tabCtrl) { | |
// scope.$watch('headingElement', function updateHeadingElement(heading) { | |
// if (heading) { | |
// elm.html(''); | |
// elm.append(heading); | |
// } | |
// }); | |
// } | |
// }; | |
//}]) | |
.directive('tabHeadingTransclude', ['$compile', function($compile) { | |
return { | |
restrict: 'A', | |
require: '^tab', | |
link: function(scope, elm, attrs, tabCtrl) { | |
scope.$watch('headingElement', function updateHeadingElement(heading) { | |
if (heading) { | |
elm.html(''); | |
var headerTmpl = $compile(heading.innerHTML)(scope.$parent, function(elem, scope){ | |
elm.append(elem); | |
}); | |
} | |
}); | |
} | |
}; | |
}]) | |
.directive('tabContentTransclude', function() { | |
return { | |
restrict: 'A', | |
require: '^tabset', | |
link: function(scope, elm, attrs) { | |
var tab = scope.$eval(attrs.tabContentTransclude); | |
//Now our tab is ready to be transcluded: both the tab heading area | |
//and the tab content area are loaded. Transclude 'em both. | |
tab.$transcludeFn(tab.$parent, function(contents) { | |
angular.forEach(contents, function(node) { | |
if (isTabHeading(node)) { | |
//Let tabHeadingTransclude know. | |
tab.headingElement = node; | |
} else { | |
elm.append(node); | |
} | |
}); | |
}); | |
} | |
}; | |
function isTabHeading(node) { | |
return node.tagName && ( | |
node.hasAttribute('tab-heading') || | |
node.hasAttribute('data-tab-heading') || | |
node.tagName.toLowerCase() === 'tab-heading' || | |
node.tagName.toLowerCase() === 'data-tab-heading' | |
); | |
} | |
}) | |
; | |
angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { | |
$templateCache.put("template/tabs/tab.html", | |
"<li ng-class=\"{active: active, disabled: disabled}\">\n" + | |
" <a ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" + | |
"</li>\n" + | |
""); | |
}]); | |
angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { | |
$templateCache.put("template/tabs/tabset.html", | |
"\n" + | |
"<div class=\"tabbable\">\n" + | |
" <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" + | |
" <div class=\"tab-content\">\n" + | |
" <div class=\"tab-pane\" \n" + | |
" ng-repeat=\"tab in tabs\" \n" + | |
" ng-class=\"{active: tab.active}\"\n" + | |
" tab-content-transclude=\"tab\">\n" + | |
" </div>\n" + | |
" </div>\n" + | |
"</div>\n" + | |
""); | |
}]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment