(function () { 'use strict'; var app = angular.module('sysMgrApp') .directive('smNavTabs', ['$rootScope', '$location', '$state', '$stateParams', navTabViews]) .directive('smNavTab', ['$rootScope', '$location', '$state', '$stateParams', navTabView]) .directive('smNavTabValidate', ['$rootScope', navTabViewValid]); function navTabViews($rootScope, $location, $state, $stateParams) { return { restrict: 'E', transclude: true, //replace: true, scope: {}, controller: function ($scope) { $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; $rootScope.stateChangeAborted = false; $rootScope.currentNavTab = {}; if ($scope.panes == undefined) { window.console && console.log('$scope.panes undefined, defining now...'); $scope.panes = []; } var panes = $scope.panes; $scope.select = function (uiView) { // call hook before changing state uiView.onActivating(uiView.name); // use $rootScope to make call to change the state $rootScope.$state.go(uiView.name, uiView.viewSettings.params, uiView.viewSettings.options); // set first call from 'addPane' function // to default tab if (panes.length === 0 && uiView.isVisible != false) { uiView.isActive = true; } }; // called from $stateNotFound, $stateChangeError, $stateChangeAborted // and $stateChangeSuccess event listeners $rootScope.setNavTabs = function (viewName) { var i = 0; // window.console && console.log('Method setNavTabs called with ' + viewName); for (i = 0; i < panes.length; i++) { var pane = panes[i]; // set to 'false' all but matching name pane.isActive = false; if ((pane.name == viewName)) { // recall hook if stateChangeAborted = true if ($rootScope.stateChangeAborted == true) { pane.onActivating(viewName); } window.console && console.log('Activating ' + viewName); pane.isActive = true; $rootScope.currentNavTab = pane; } } } //onStateChangeStart $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { // reset stateChangeAborted flag $rootScope.stateChangeAborted = false; // Check if current smNavTab is Valid //if ($rootScope.currentNavTab && $rootScope.currentNavTab.isValid == false) { // event.preventDefault(); // $rootScope.stateChangeAborted = true; // $rootScope.setNavTabs(fromState.name); //}; }) //onStateNotFound $rootScope.$on('$stateNotFound', function (event, unfoundState, fromState, fromParams) { $rootScope.stateChangeAborted = true; $rootScope.setNavTabs(fromState.name); }) //onStateChangeError' $rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams) { $rootScope.stateChangeAborted = true; $rootScope.setNavTabs(fromState.name); }) //onStateChangeAborted $rootScope.$on('$stateChangeAborted', function (event, toState, toParams, fromState, fromParams) { $rootScope.stateChangeAborted = true; $rootScope.setNavTabs(fromState.name); }) //onStateChangeSuccess $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) { $rootScope.stateChangeAborted = false; if (toState!=fromState) $rootScope.setNavTabs(toState.name); }) this.addPane = function (pane) { window.console && console.log('Adding pane ' + pane.name + ' out of ' + panes.length + ' panes'); if (panes.length === 0) { $scope.select(pane); } panes.push(pane); }; }, templateUrl: 'app/templates/smNavTabs' }; }; function navTabView($rootScope, $state, $stateParams) { return { require: '^smNavTabs', restrict: 'E', transclude: false, replace: true, scope: { name: '@', title: '@', viewSettings: '@?', onActivating: '&?', isVisible: '=?' }, link: function (scope, element, attrs, tabsCtrl) { //default settings and functions var defaultViewSettings = function () { var params, options; params = {}; options = { location: true, inherit: true, relative: $state.$current, notify: true, reload: false }; return { params: params, options: options }; }; var defaultOnActivating = function () { }; scope.isValid = true; scope.isVisible = scope.isVisible || true; scope.viewSettings = scope.viewSettings || defaultViewSettings(); scope.onActivating = scope.onActivating || defaultOnActivating(); tabsCtrl.addPane(scope); }, }; }; function navTabViewValid($rootScope) { return { require: ['^form'], restrict: 'A', link: function (scope, element, attrs, ctrl) { var form = ctrl[0]; if (scope.$parent) { if (scope.$parent.invalidTabs == undefined) scope.$parent.invalidTabs = 0; } scope.$watch( 'form.$invalid', function (newValue, oldValue) { if (newValue != oldValue) { if (newValue === true) { $rootScope.currentNavTab.isValid = false; scope.$parent.invalidTabs++; // window.console && console.log($rootScope.currentNavTab.name + ' tab is invalid; scope.invalidTabs =' + scope.$parent.invalidTabs); }; if (newValue === false) { $rootScope.currentNavTab.isValid = true; scope.$parent.invalidTabs--; // window.console && console.log($rootScope.currentNavTab.name + ' tab is valid; scope.invalidTabs =' + scope.$parent.invalidTabs); }; }; } ) }, }; }; })()The template for the directive:
<div class="tabbable"> <ul class="nav nav-pills nav-stacked"> <li ng-repeat="pane in panes" ng-class="{active:pane.isActive,'invalid-tab':!pane.isValid}" ng-show="pane.isVisible"> <a href="" ng-click="select(pane)" >{{pane.title}}</a> </li> </ul> <div class="tab-content" ng-transclude></div> </div>Several forms already use that smNavTabs directive. Here is one of the most complex of them:
<div class="col-lg-2 col-md-2 panel-container ng-cloak"> <sm-nav-tabs name="guestTabs" id="guestTabs"> <sm-nav-tab name="{{selections.mode}}.general" title="@Labels.general" on-activating="setShowings('general')"></sm-nav-tab> <sm-nav-tab name="edit.guestPasses" title="@Labels.passes" data-is:visible="!isNew" on-activating="setShowings('guestPasses')"></sm-nav-tab> <sm-nav-tab name="edit.invoicesList" title="@Labels.invoices" data-is:visible="!isNew" on-activating="setShowings('invoices')"></sm-nav-tab> <sm-nav-tab name="edit.guestActivity" title="@Labels.history" data-is:visible="!isNew" on-activating="setShowings('guestActivity')"></sm-nav-tab> <sm-nav-tab name="{{selections.mode}}.userDefined1" title="@Labels.userDefined 1" on-activating="setShowings('userDefined1')"></sm-nav-tab> <sm-nav-tab name="{{selections.mode}}.userDefined2" title="@Labels.userDefined 2" on-activating="setShowings('userDefined2')"></sm-nav-tab> <sm-nav-tab name="{{selections.mode}}.notifications" title="@Labels.notifications" on-activating="setShowings('notifications')"></sm-nav-tab> <sm-nav-tab name="{{selections.mode}}.notes" title="@Labels.notes" on-activating="setShowings('notes')"></sm-nav-tab> <sm-nav-tab name="{{selections.mode}}.security" title="@Labels.security" on-activating="setShowings('security')"></sm-nav-tab> <sm-nav-tab name="{{selections.mode}}.web" title="@Labels.web" on-activating="setShowings('web')"></sm-nav-tab> </sm-nav-tabs> </div> <div class="col-lg-8 col-md-8 panel-container"> <div data-ui-view data-autoscroll="false"></div> <div data-ui-view="guestPasses" ng-show="selections.showPassesView"></div> <div data-ui-view="invoices" data-autoscroll="false" ng-show="selections.showInvoicesView"></div> </div>So, if I'll take your idea (which I like), I would have to get rid of the second directive and define tabs for the forms in the controller and pass to the first directive (smNavTabs). Doesn't sound like too much work, but still a bit of work. So, if the original directives can be fixed in easier way, it would be better.