(function () { 'use strict'; var app = angular.module('sysMgrApp') .directive('smNavTabs', ['$rootScope', '$location', '$state', '$stateParams', navTabViews]) .directive('smNavTab', ['$rootScope', '$location', '$state', '$stateParams', navTabView]); function navTabViews($rootScope, $location, $state, $stateParams) { return { restrict: 'E', transclude: true, // means it has access to the outside scope replace: true, scope: {}, controller: function ($scope) { $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; $rootScope.stateChangeAborted = false; var urlProtocolAndPath = $location.protocol() + '://' + $location.host() + ($location.port() != NaN ? ':' + $location.port() : ''); var panes = $scope.panes = []; $scope.lastSelected = 0; $scope.$parent.tabsInvalid = false; $scope.select = function (uiView) { // window.console && console.log('Selecting ' + uiView.name); // call hook before changing state panes[$scope.lastSelected].invalid = uiView.form.$invalid; // all tabs point to the same form 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) { uiView.isActive = true; } }; // called from $stateNotFound, $stateChangeError, $stateChangeAborted // and $stateChangeSuccess event listeners $rootScope.setNavTabs = function (viewName) { var i = 0; // window.console && console.log('setNavTabs method is called with viewName = ' + viewName); $scope.$parent.tabsInvalid = false; for (i = 0; i < panes.length; i++) { var pane = panes[i]; // set to 'false' all but matching name if (pane.isActive) { $scope.lastSelected = i; } pane.isActive = false; if ((pane.name == viewName)) { // recall hook if stateChangeAborted = true if ($rootScope.stateChangeAborted == true) pane.onActivating(viewName); pane.isActive = true; } if (pane.invalid) { $scope.$parent.tabsInvalid = true; } } } //onStateChangeStart $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { // reset stateChangeAborted flag $rootScope.stateChangeAborted = false; }) //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; $rootScope.setNavTabs(toState.name); }) this.addPane = function (pane) { if (panes.length === 0 && pane.isVisible) { panes.push(pane); $scope.lastSelected = 0; $scope.select(pane); } else panes.push(pane); }; }, templateUrl: 'app/templates/smNavTabs' }; }; function navTabView($rootScope, $state, $stateParams) { return { require: ['^form','^smNavTabs'], restrict: 'E', transclude: false, replace: true, scope: { name: '@', title: '@', viewSettings: '@?', onActivating: '&?', isVisible: '=?ngShow' }, 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.isVisible = scope.isVisible || true; scope.viewSettings = scope.viewSettings || defaultViewSettings(); scope.onActivating = scope.onActivating || defaultOnActivating(); scope.form = tabsCtrl[0]; scope.invalid = false; scope.isActive = false; tabsCtrl[1].addPane(scope); }, }; }; })()and the template is
<div class="tabbable"> <ul class="nav nav-pills nav-stacked"> <li ng-repeat="pane in panes" ng-class="{active:pane.isActive, 'invalid-tab':pane.invalid}" ng-show="pane.isVisible"> <a href="" ng-click="select(pane)" >{{pane.title}}</a> </li> </ul> <div class="tab-content" ng-transclude></div> </div>our forms have the following markup for the add button:
<button id="btnAdd" class="btn btn-primary" ng-click="new(currentGuest)" ng-disabled="form.$invalid || disableAction"> @Labels.add </button>the disableAction is actually used when we click on that button (in order to prevent clicking when code didn't complete yet). So, my solution is really a hack.