In Focus

Tab Control Using AngularJS

In this article, we will discuss how to create a tab control with the help of AngularJS.

During the development of the web page, it is often required to represent multiple contents within a tab control. But, like other control, AngularJS have own tab directive. But we can create a simple tab directive with the help of AngularJS.
 
For creating a tab directive, we first need to create a stylesheet file. For this, we first add a style file called style.css and add the following code :- 
  1. <style>  
  2.          .leftTab {width: 200px;float: left;padding: 0 7px;}  
  3.          .leftTab li {float: left;width: 100%;}  
  4.          .leftTab li a {height: 30px;font-size: 12px;display: block;text-align: left;padding: 3px 2px;letter-spacing: 1px;}  
  5.          .leftTab li a span {line-height: 25px;}  
  6.          .leftTab li a .iconArrow {font-size: 9px;height: 10px;margin: 9px 9px 0 0;width: 10px;}  
  7.          .leftTab li .tabSelect {font-weight: 700;}  
  8.     </style>  
Now, first create the angular app as below :-
  1. var CustomCtrlApp = angular.module('CustomCtrlApp', []);  
Now,Tab control required two different directives. One for heading related purpose and another represent the contains of that particular tab. So first we create two different html file named Tab.html and Tabset.html as in the following:
 
Tabset.html 
  1. <div>  
  2.     <div id="leftTab" class="leftTab" ng-style="BodyStyle">  
  3.         <ul ng-transclude></ul>  
  4.     </div>  
  5.     <div class="tab-content multiPage" ng-style="BodyStyle">  
  6.         <div class="tab-pane" ng-repeat="fvtab in tabs" ng-class="{active: fvtab.active}" fv-tab-content-transclude="fvtab">  
  7.         </div>  
  8.     </div>  
  9. </div>  
Tab.html
  1. <li ng-class="{active: active, disabled: disabled}" ng-show="visible">  
  2.     <a ng-click="select()" fv-tab-heading-transclude ng-class="{'tabSelect' : active}">  
  3.         <i class="iconArrow left fv-right"></i><span class="left">{{heading}}</span>  
  4.     </a>  
  5. </li>  
Now, add a JavaScript file named Tab.js and write down the following code here:
  1. CustomCtrlApp.service('TabService', ['$rootScope'function ($rootScope) {  
  2.     var tabsetScope = null;  
  3.     var hideIndex = null;  
  4.   
  5.     var hide = function (index) {  
  6.         var selectedTab = tabsetScope.tabs[index];  
  7.         hideIndex = null;  
  8.         tabsetScope.hideTab(selectedTab);  
  9.     }  
  10.   
  11.     this.setTabsetScope = function (rootScope) {  
  12.         tabsetScope = rootScope;  
  13.         if (hideIndex != #ff0000) {  
  14.             hide(hideIndex);  
  15.         }  
  16.     }  
  17.   
  18.     this.selectTab = function (index) {  
  19.         var selectedTab = tabsetScope.tabs[index];  
  20.         tabsetScope.select(selectedTab);  
  21.         hideIndex = null;  
  22.     }  
  23.   
  24.     this.hideTab = function (index) {  
  25.         if (tabsetScope == undefined) {  
  26.             hideIndex = index;  
  27.         }  
  28.         else {  
  29.             hide(index);  
  30.             hideIndex = null;  
  31.         }  
  32.     }  
  33.       
  34.     this.showTab = function (index) {  
  35.         hideIndex = null;  
  36.         var selectedTab = tabsetScope.tabs[index];  
  37.         tabsetScope.showTab(selectedTab);  
  38.     }  
  39. }]);  
  40.   
  41. CustomCtrlApp.directive("tabset", [function ($httpservice) {  
  42.     this;  
  43.     return {  
  44.         restrict: 'EA',  
  45.         transclude: true,  
  46.         replace: true,  
  47.         scope: {  
  48.             type: '@'  
  49.         },  
  50.         templateUrl: "../HTMLTemplate/Tabset.html",  
  51.         controller: function ($scope, $element, $attrs, TabService) {  
  52.             var ctrl = this, tabs = ctrl.tabs = $scope.tabs = [];  
  53.             $scope.BodyStyle = getElementMinHeight();  
  54.   
  55.             ctrl.select = function (selectedTab) {  
  56.                 angular.forEach(tabs, function (tab) {  
  57.                     if (tab.active && tab !== selectedTab) {  
  58.                         tab.active = false;  
  59.                         tab.visible = true;  
  60.                         tab.onDeselect();  
  61.                     }  
  62.                 });  
  63.                 selectedTab.active = true;  
  64.                 selectedTab.onSelect();  
  65.                 TabService.setTabsetScope(ctrl);  
  66.             };  
  67.   
  68.             ctrl.hideTab = function (selectedTab) {  
  69.                 if (selectedTab != undefined) {  
  70.                     selectedTab.visible = false;  
  71.                     TabService.setTabsetScope(ctrl);  
  72.                 }  
  73.             };  
  74.   
  75.             ctrl.showTab = function (selectedTab) {  
  76.                 selectedTab.visible = true;  
  77.                 TabService.setTabsetScope(ctrl);  
  78.             };  
  79.   
  80.             ctrl.addTab = function addTab(tab) {  
  81.                 tabs.push(tab);  
  82.                 // we can't run the select function on the first tab  
  83.                 // since that would select it twice  
  84.                 if (tabs.length === 1 && tab.active !== false) {  
  85.                     tab.active = true;  
  86.                 } else if (tab.active) {  
  87.                     ctrl.select(tab);  
  88.                 }  
  89.                 else {  
  90.                     tab.active = false;  
  91.                 }  
  92.             };  
  93.   
  94.             ctrl.removeTab = function removeTab(tab) {  
  95.                 var index = tabs.indexOf(tab);  
  96.                 //Select a new tab if the tab to be removed is selected and not destroyed  
  97.                 if (tab.active && tabs.length > 1 && !destroyed) {  
  98.                     //If this is the last tab, select the previous tab. else, the next tab.  
  99.                     var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;  
  100.                     ctrl.select(tabs[newActiveIndex]);  
  101.                 }  
  102.                 tabs.splice(index, 1);  
  103.             };  
  104.   
  105.             var destroyed;  
  106.             $scope.$on('$destroy'function () {  
  107.                 destroyed = true;  
  108.             });  
  109.         }  
  110.     }  
  111. }]);  
  112.   
  113. CustomCtrlApp.directive('TabContentTransclude'function () {  
  114.     return {  
  115.         restrict: 'A',  
  116.         require: '^tabset',  
  117.         link: function (scope, elm, attrs) {  
  118.             var tab = scope.$eval(attrs.TabContentTransclude);  
  119.   
  120.             //Now our tab is ready to be transcluded: both the tab heading area  
  121.             //and the tab content area are loaded.  Transclude 'em both.  
  122.             tab.$transcludeFn(tab.$parent, function (contents) {  
  123.                 angular.forEach(contents, function (node) {  
  124.                     if (isTabHeading(node)) {  
  125.                         //Let tabHeadingTransclude know.  
  126.                         tab.headingElement = node;  
  127.                     } else {  
  128.                         elm.append(node);  
  129.                     }  
  130.                 });  
  131.             });  
  132.         }  
  133.     };  
  134.     function isTabHeading(node) {  
  135.         return node.tagName && (  
  136.           node.hasAttribute('tab-heading') ||  
  137.           node.hasAttribute('data-tab-heading') ||  
  138.           node.tagName.toLowerCase() === 'tab-heading' ||  
  139.           node.tagName.toLowerCase() === 'data-tab-heading'  
  140.         );  
  141.     }  
  142. });  
  143.   
  144. CustomCtrlApp.directive("tab", [function ($httpservice) {  
  145.     this;  
  146.     return {  
  147.         require: '^tabset',  
  148.         restrict: 'EA',  
  149.         replace: true,  
  150.         templateUrl: "../HTMLTemplate/Tab.html",  
  151.         transclude: true,  
  152.         scope: {  
  153.             active: '=?',  
  154.             heading: '@',  
  155.             onSelect: '&select'//This callback is called in contentHeadingTransclude  
  156.             //once it inserts the tab's content into the dom  
  157.             onDeselect: '&deselect'  
  158.         },  
  159.         controller: function () {  
  160.             //Empty controller so other directives can require being 'under' a tab  
  161.         },  
  162.         compile: function (elm, attrs, transclude) {  
  163.             return function postLink(scope, elm, attrs, tabsetCtrl) {  
  164.                 scope.$watch('active'function (active) {  
  165.                     if (active) {  
  166.                         tabsetCtrl.select(scope);  
  167.                     }  
  168.                 });  
  169.   
  170.                 scope.disabled = false;  
  171.                 scope.visible = true;  
  172.                 if (attrs.disable) {  
  173.                     scope.$parent.$watch($parse(attrs.disable), function (value) {  
  174.                         scope.disabled = !!value;  
  175.                     });  
  176.                 }  
  177.   
  178.                 // Deprecation support of "disabled" parameter  
  179.                 // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677  
  180.                 // This code is duplicated from the lines above to make it easy to remove once  
  181.                 // the feature has been completely deprecated  
  182.                 if (attrs.disabled) {  
  183.                     $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');  
  184.                     scope.$parent.$watch($parse(attrs.disabled), function (value) {  
  185.                         scope.disabled = !!value;  
  186.                     });  
  187.                 }  
  188.   
  189.                 scope.select = function () {  
  190.                     if (!scope.disabled) {  
  191.                         scope.active = true;  
  192.                     }  
  193.                 };  
  194.   
  195.                 tabsetCtrl.addTab(scope);  
  196.                 scope.$on('$destroy'function () {  
  197.                     tabsetCtrl.removeTab(scope);  
  198.                 });  
  199.   
  200.                 //We need to transclude later, once the content container is ready.  
  201.                 //when this link happens, we're inside a tab heading.  
  202.                 scope.$transcludeFn = transclude;  
  203.             };  
  204.         }  
  205.     }  
  206. }]);  
  207.   
  208. CustomCtrlApp.directive('TabHeadingTransclude', [function () {  
  209.     return {  
  210.         restrict: 'A',  
  211.         require: '^tab',  
  212.         link: function (scope, elm, attrs, tabCtrl) {  
  213.             scope.$watch('headingElement'function updateHeadingElement(heading) {  
  214.                 if (heading) {  
  215.                     elm.html('');  
  216.                     elm.append(heading);  
  217.                 }  
  218.             });  
  219.         }  
  220.     };  
  221. }]);  
Actually, in the above AngularJS code we use ng-transclude to fulfill the desired objective. Actually, ng-transclude is performed as a setting which actually tell angular to capture every DOM element that is placed inside the directive in the html and use where ng-transclude is used. 
 
Now, add another html file named TabDemo.html  and write down the following code:
  1. <!DOCTYPE html>  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4.     <title>Sample of Tab Directive Demostration</title>  
  5.     <style>  
  6.          .leftTab {width: 200px;float: left;padding: 0 7px;}  
  7.          .leftTab li {float: left;width: 100%;}  
  8.          .leftTab li a {height: 30px;font-size: 12px;display: block;text-align: left;padding: 3px 2px;letter-spacing: 1px;}  
  9.          .leftTab li a span {line-height: 25px;}  
  10.          .leftTab li a .iconArrow {font-size: 9px;height: 10px;margin: 9px 9px 0 0;width: 10px;}  
  11.          .leftTab li .tabSelect {font-weight: 700;}  
  12.     </style>  
  13.     <script src="../Scripts/angular.min.js"></script>  
  14.     <script src="../DirectiveScript/Tab.js"></script>  
  15. </head>  
  16.   
  17. <body>  
  18.     <tabset>  
  19.         <tab heading="First Tab">  
  20.             This is First Tab  
  21.         </tab>  
  22.         <tab heading="Second Tab">  
  23.             This is Second Tab  
  24.         </tab>  
  25.     </tabset>  
  26. </body>  
  27. </html>