Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

A way to intercept tab change #2715

Closed
DmitryEfimenko opened this issue Sep 17, 2014 · 18 comments
Closed

A way to intercept tab change #2715

DmitryEfimenko opened this issue Sep 17, 2014 · 18 comments

Comments

@DmitryEfimenko
Copy link

Hello,

I need a way to intercept a tab change. Here is a situation where such functionality would be useful:

I have two tabs. Each has a form inside. The requirement is that if form in the current tab becomes dirty, I need to show a popup message and depending on which option user chooses ((1) "save changes", (2) "discard changes", (3) "cancel") I should either (1) save and switch tab, (2) don't save and switch tab, or (3) don't save and stay on current tab.

Please let me know if that functionality already exists. If it does not, what approach do you think would be most suitable?

@wojjas
Copy link

wojjas commented Feb 17, 2015

Hi,
I have the exact same scenario and need to intercept the tab change as well. I would manage my problem if I could just cancel in the controller for the "deselect" (setting of , fires first, before "select")
Dmitry, did you find any solution to this? please help me.

@karianna
Copy link
Contributor

@DmitryEfimenko
Copy link
Author

@wojjas I could not do it using ui-bootstrap directive, so I had to go with my own, custom implementation.

@karianna Why was this closed? It's a feature request. This functionality does not exist in current version of bootstrap-ui directive and no support can help with that.

@wojjas
Copy link

wojjas commented Feb 17, 2015

@DmitryEfimenko I see, I was hoping you could share some ideas, but it's ok. My workaround will be to disable all the other tabs as soon as current tab's form becomes "dirty". And then try to guide the poor user somehow to Save/Cancel when he desperately tries to get away from "the dirty tab" :-)
I too believe that what we ask for here is a feature request, not a bug or misunderstanding of how to use.

@DmitryEfimenko
Copy link
Author

@wojjas I could share some details.

My flow of events is following:

  • I use a modified version of module unsavedChanges to track whether any input of the form inside a tab was changed or not (I exposed a service method from that library to do that) ... I guess I could make my version available online.
  • Then if changes were made I show a modal to the user asking if s/he wants to (1) save changes, (2) don't save changes, or (3) stay on current tab.

here is some relevant code:

Controller

$scope.tabs = [
    { type: 'tab1', active: true },
    { type: 'tab2' },
    { type: 'tab3' }
];

$scope.switchTabs = function(newTabIndex) {
    var formIsClean = unsavedWarningSharedService.allFormsClean(); // this method is not exposed to service api in original version
    if (formIsClean) {
        $scope.setActiveTab(newTabIndex);
    } else {
        var currentTab = misc.getByValue($scope.tabs, { active: true }).type; // returns value of "type" property of selected tab. Can be implemented using underscore.
        // display modal to user
        modal.changesMade($scope, newTabIndex, currentTab);
    }
}

$scope.setActiveTab = function(tabIndex) {
    for (var i = 0, l = $scope.tabs.length; i < l; i++) {
        $scope.tabs[i].active = false;
    }
    $scope.tabs[tabIndex].active = true;
}

$scope.saveForm = function() {
    $http.post('posturl', $scope.vm)
        .success(function(response){
            // display feedback to user
            toaster.success('Saved!');
            // reset master values of all inputs so that `allFormsClean()` method returns true
            unsavedWarningSharedService.resetMasterValues(); // this method does not exist in original version
        });
}

Approximate code to display tabs in the view

<ul class="nav nav-tabs" role="tablist">
    <li ng-repeat="tab in tabs" ng-class="{'active': tab.active}">
        <a ng-click="switchTabs($index)" role="tab" data-toggle="tab" tabindex="{{tab.active ? -1 : 0}}">{{resources[tab.type]}}</a>
    </li>
</ul>

Modal changesMade

angular.module('app.services')
    .factory('changesMade', [
        '$rootScope', '$state', '$modal', 'm.Helpers', 'unsavedWarningSharedService', 'miscService',
        function ($rootScope, $state, $modal, mHelpers, unsavedWarningSharedService, misc) {
            return function (scope, newTabIndex, currentTab) {
                $modal.open({
                    id: 'changesMade',
                    templateUrl: 'modals/changesMade',
                    controller: function ($scope, $modalInstance) {
                        $scope.vm = {};

                        var forms = unsavedWarningSharedService.allForms();
                        var form = misc.getByValue(forms, { '$name': currentTab });

                        $scope.vm.formValid = form.$valid;

                        $scope.dontSave = function() {
                            scope.setActiveTab(newTabIndex);
                            // this reverts input values to original values
                            $rootScope.$broadcast('resetResettables');
                            form.$setPristine();
                            // close all modals
                            mHelpers.closeAll();
                        }

                        $scope.save = function () {
                            scope.saveForm();
                            form.$setPristine();
                            mHelpers.closeAll();
                        }
                    }
                });
            }
        }
    ]);

modal view

<div class="modal-header">
    <button type="button" class="close" ng-click="$close()">&times;</button>
    <h3 class="modal-title" >Changes Made</h3>
</div>

<div class="modal-body">
    <p role="definition" tabindex="0">You've made changes. Would you like to save them before you continue?</p>
</div>

<div class="modal-footer">
    <button class="btn btn-default" ng-click="$close()">{{resources.cancel}}</button>
    <button class="btn btn-warning" ng-click="dontSave()">{{resources.dontSave}}</button>
    <button class="btn btn-primary" ng-click="save()" ng-disabled="!vm.formValid">Save</button>
</div>

This code is a bit simplified just to give you an idea.

@kuceram
Copy link

kuceram commented Mar 23, 2015

Hi,

I have same problem. I use angular-unsavedChanges to prevent the user from leaving unsaved values. The proper use of this plugin is to configure your custom event which is fired when you would like to validate the $dirty - ness of your form.

.config(function (unsavedWarningsConfigProvider) {
        unsavedWarningsConfigProvider.routeEvent = ['$locationChangeStart' ,'$stateChangeStart', 'customTabChangeEvent']
    });

Than in the tab definition you can fire this event:

...
<tab select="$emit('customTabChangeEvent')">
...

When clicking on the tab the angular-unsavedChanges check if any form is dirty. If it is dirty (and user click on close in provided alert) it stop propagation of event:

event.preventDefault(); // user clicks cancel, wants to stay on page

This should stop the tab to be switched. This same approach works with ui-route

@k4rm3l0
Copy link

k4rm3l0 commented Apr 28, 2015

Anyone solve this with angular-ui bootstrap?
I need to handle the click on a tab, and change the view on it ONLY if a condition is true (for example after show a modal for save changes).

@larvanitis
Copy link

My workaround while working on a proper solution...

1. Load the following script after ui-bootstrap(-tpls).js (for good measure):
angular.module('ui.bootstrap.tabs').config(['$provide', function ($provide) {
  $provide.decorator('tabDirective', ['$delegate', '$parse', function ($delegate, $parse) {
    var directive = $delegate[0];
    var compile = directive.compile;

    directive.compile = function () {
      var link = compile.apply(this, arguments);

      return function (scope, elm, attrs) {
        link.apply(this, arguments);

        // expose 'before-select' attribute as scope.beforeSelect
        scope.beforeSelect = angular.noop;
        attrs.$observe('beforeSelect', function (value) {
          if (value) {
            var parentGet = $parse(value);
            scope.beforeSelect = function(locals) {
              return parentGet(scope.$parent, locals);
            };
          } else {
            scope.beforeSelect = angular.noop;
          }
        });

        scope.select = function ($event) {
          if (!scope.disabled) {
            // abort tab switch if necessary
            scope.beforeSelect({$event: $event});
            if ($event.isDefaultPrevented()) { return; }

            scope.active = true;
          }
        };
      };
    };

    return $delegate;

  }]);
}]);

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 href ng-click=\"select($event)\" tab-heading-transclude>{{heading}}</a>\n" +
    "</li>\n" +
    "");
}]);
2. Then, on your tab element add before-select attribute:
<tab before-select="preventIfNecessary($event)">...</tab>
3. Use some logic in your controller to prevent the tab switch:
$scope.preventIfNecessary = function (event) {
  if ($scope.myForm.$dirty) {
    var answer = confirm('You have pending changes! Are you sure you want to continue?');
    !answer && event.preventDefault();
  }
};

@thebalaa
Copy link

@larvanitis thanks for the workaround! 👍

@wesleycho wesleycho modified the milestones: 0.14.3, 1.0.0 Oct 23, 2015
@wesleycho
Copy link
Contributor

Closing this issue in favor of #4836 - this will be addressed when we fix the API for the tab component.

@diegodfsd
Copy link

While I no found a better solution more integrated with the tab component I'm using route events and form state to know when I should alert the user or not.

I created a directive to do the hard job.

@Salsao
Copy link

Salsao commented Mar 30, 2016

I'm trying to intercept the tab change using the 'select' settings passing the $event (on the 'tab one') on this plunker - http://plnkr.co/edit/AKCezdOUVGM5tIUWWxJ6?p=preview -.
But the $event.preventDefault() is not working, the function is called but it didn't prevent the tab switching.
That's how supposed to do or am I doing something wrong? Thanks

@icfantv
Copy link
Contributor

icfantv commented Mar 30, 2016

@Salsao, you would be far better off asking for help via the methods outlined in our project documentation rather than commetngin on an issue from over two years ago.

@icfantv
Copy link
Contributor

icfantv commented Mar 30, 2016

@Salsao, we don't allow you to prevent the default event from happening. You may feel free to file an issue requesting such a feature. I cannot guarantee that it will be added, but at the very least we will have a discussion. It's not a huge change on our end - it's more whether or not we want to support such a feature.

When/if you decide to file the issue, please adhere to the issue template or your issue will be closed until it is followed. You might even try your hand at a possible fix via updating our tab plunker to respect the .preventDefault.

@icfantv
Copy link
Contributor

icfantv commented Mar 31, 2016

@Salsao, #5720 was entered for this feature.

icfantv added a commit to icfantv/bootstrap that referenced this issue Mar 31, 2016
* add ability for user to prevent currently selected tab's deselction by
calling `$event.preventDefault()` in tab's `deselect` callback.

Fixes angular-ui#5720
Addresses angular-ui#2715, angular-ui#4836, and angular-ui#5716.
icfantv added a commit to icfantv/bootstrap that referenced this issue Mar 31, 2016
* add ability for user to prevent currently selected tab's deselection by
calling `$event.preventDefault()` in tab's `deselect` callback.

Fixes angular-ui#5720
Addresses angular-ui#2715, angular-ui#4836, and angular-ui#5716.
icfantv added a commit that referenced this issue Mar 31, 2016
* add ability for user to prevent currently selected tab's deselection by
calling `$event.preventDefault()` in tab's `deselect` callback.

Fixes #5720
Addresses #2715, #4836, and #5716.
Closes #5723
@andreasadelino
Copy link

andreasadelino commented Feb 8, 2017

@larvanitis, your solution works for me. I just had to change the 'tabDirective' to 'uibTabDirective';
ui-bootstrap version: 0.14.3.

Thank you!

@tomershatz
Copy link

@andreas-andrade your fix helped me. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

14 participants