8

So I'm trying to implement a custom confirm box using Angular. Ideally, I would simply like to add an attribute to enable the functionality. Example:

<button type="button" ng-click="delete(foo)">Delete</button>  ->  <button type="button" ng-click="delete(foo)" ng-confirm="Are you sure you want to delete this foo?">Delete</button>

(foo is inside an ng-repeat... foo in fooList..)

So all of the problems I am having revolve around tying the click event that would normally happen to a different button. I have a seperate directive "confirmBox" that will create my modal (not using bootstrap) and handle all of the showing/hiding/etc.

What I am currently using requires me to alter my ng-click functionality, which I really want to get away from:

Current Implementation:

<button ... ng-click="confirm('Are you sure you want to delete this foo?, 'delete', foo)">Delete</button>

var confirmModule = angular.module('confirm', []);

confirmModule.run(function($rootScope) {
    $rootScope.confirm = function(text, func, obj) {
        $rootScope.$broadcast('confirm', func, obj, text);
    };
});

confirmModule.directive('confirmBox', function($parse) {

    return {
        restrict: 'A',
        template: myModalTemplate,
        link: function(scope, element, attrs){
            element.hide();
            var noBtn = element.find("[name='no']");
            noBtn.bind("click", function() {
                element.hide();
            });
            scope.$on("confirm", function(event, func, obj, text) {
                var yesBtn = element.find("[name='yes']");
                element.show();
                yesBtn.unbind("click").bind("click", function() {
                    scope[func](obj);
                });
            });
        }
    }
});

Anyone have any ideas? I started by adding the directive for the button and then unbinding the click event so ng-click doesn't fire. Then I am left with the string 'delete(foo)' from the ng-click attribute that I can execute with $parse(attrs.ngClick)(scope), but I don't know how to tie that to the separate directives button click.

Edit: Here is a fiddle with my current attempt at implementation. The problem is the variable being passed in to the function is always undefined.

http://jsfiddle.net/UCtbj/2/

Edit2: Updated implementation, however I don't particularly like how it links the two directives together by targetting the other directives elements.

http://jsfiddle.net/UCtbj/3/

James Kleeh
  • 12,094
  • 5
  • 34
  • 61

7 Answers7

7

It seems to me that you're trying to do things the jQuery way from within the directive. However, what you want is as simple as pulling in the UI-Bootstrap directive for confirming actions. http://plnkr.co/edit/JhfAF1?p=preview

Sharondio
  • 2,605
  • 13
  • 16
  • 2
    The problem with this is that it requires you to put this dialog code in each function you want it to show. It really has nothing to do with the business logic of the application so imo, it shouldn't be there. – James Kleeh Jun 18 '13 at 04:20
  • 1
    It seems that angular UI has been refactored and the $dialog service has been removed and you can use messagebox functionality as indicated in the plunker anymore. http://stackoverflow.com/questions/19468127/where-is-dialog-messagebox-in-angular-ui – ScottGuymer Apr 24 '14 at 13:36
  • I could refactor it to use the $modal service in my confirm directive. I still don't want to use $modal in my controller - its view logic so doesn't belong there. – James Kleeh Apr 29 '14 at 03:05
  • is there a way to use ONLY the UI-BS part of the code and not having to load the rest? – MartianMartian Sep 16 '16 at 09:20
3

First simple service for modal windows:

app.service('ConfirmService', function($modal) {
  var service = {};
  service.open = function (text, onOk) {
    var modalInstance = $modal.open({
      templateUrl: 'myModalContent.html',
      controller: 'ModalConfirmCtrl',
      resolve: {
        text: function () {
          return text;
        }
      }
    });

    modalInstance.result.then(function (selectedItem) {
      onOk();
    }, function () {
    });
  };

  return service;
})

app.controller('ModalConfirmCtrl', function ($scope, $modalInstance, text) {

  $scope.text = text;

  $scope.ok = function () {
    $modalInstance.close(true);
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
});

Then simple directive that uses it:

app.directive('confirm', function(ConfirmService) {
    return {
        restrict: 'A',
        scope: {
            eventHandler: '&ngClick'
        },
        link: function(scope, element, attrs){
          element.unbind("click");
          element.bind("click", function(e) {
            ConfirmService.open(attrs.confirm, scope.eventHandler);
          });
        }
    }
});

And here u go:

<button ng-click="test(12)" confirm='Are you sure?'>Button</button>

http://plnkr.co/edit/LOZOnsVyx3JU5XoKYn74?p=preview

Petr Averyanov
  • 9,327
  • 3
  • 20
  • 38
1

To allow a button to be marked up like

<button type="button" ng-click="deleteItem(drink)" ng-confirm="Are you sure you want to delete '{{drink.name}}'">Delete</button>

You can write a directive that

  • Intercepts the click event before ngClick's click handler can run
  • Opens a dialog (using $modal and not the removed $dialog)
  • On close of the dialog (which is treated as a success) run the function specified by the ngClick attribute on the element.

Basing the code on the previous answer, you can do this as follows:

app.directive('ngConfirm', function($modal, $parse) {
  return {
    // So the link function is run before ngClick's, which has priority 0
    priority: -1,

    link: function(scope, element, attrs) {
      element.on('click', function(e) {
        // Don't run ngClick's handler
        e.stopImmediatePropagation();

        $modal.open({
          templateUrl: 'ng-confirm-template',
          controller: 'ngConfirmController',
          resolve: {
            message: function() {
              return attrs.ngConfirm;
            }
          }
        }).result.then(function() {
          // Pass original click as '$event', just like ngClick
          $parse(attrs.ngClick)(scope, {$event: e});
        });
      });
    }
  };
});

which needs a simple controller:

app.controller('ngConfirmController', function($scope, $modalInstance, message) {
  $scope.message = message;

  $scope.yes = function() {
    $modalInstance.close();
  };

  $scope.no = function() {
    $modalInstance.dismiss();
  };
});

and template for the dialog:

<script type="text/ng-template" id="ng-confirm-template">
  <div class="modal-body">
    <p>{{message}}</p>
  </div>
  <div class="modal-footer">
    <button class="btn btn-link pull-left" ng-click="no()">No</button>
    <button class="btn btn-primary pull-right" ng-click="yes()">Yes</button>
  </div>
</script>

You can see this running at http://plnkr.co/edit/Gm9lFsGb099w6kCMQoVY?p=preview

Edit: changed plunker link to example without scrollbar appearing/disappearing on display of the dialog

Community
  • 1
  • 1
Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
1

Here is a nice directive for that.That is ngBootbox. Check it out.

<button class="btn btn-lg btn-primary"
        ng-bootbox-title="A cool title!"
        ng-bootbox-custom-dialog="Some custom text"
        ng-bootbox-buttons="customDialogButtons"
        ng-bootbox-class-name="some-class">
    Custom dialog
</button>

<script>
    $scope.customDialogButtons = {
        warning: {
            label: "Warning!",
            className: "btn-warning",
            callback: function() { $scope.addAction('Warning', false); }
        },
        success: {
            label: "Success!",
            className: "btn-success",
            callback: function() { $scope.addAction('Success!', true) }
        },
        danger: {
            label: "Danger!",
            className: "btn-danger",
            callback: function() { $scope.addAction('Danger!', false) }
        },
        main: {
            label: "Click ME!",
            className: "btn-primary",
            callback: function() { $scope.addAction('Main...!', true) }
        }
    };
    </script>

Demo

ngBootbox

Sampath
  • 63,341
  • 64
  • 307
  • 441
0

I created a repo for this functionality. It wraps the ui-bootstrap modal to produce a confirmation box. It is customizable and easily integrated into any application.

Here is the link to the GitHub: https://github.com/Schlogen/angular-confirm

Example Usages:

As a directive:

 <button type="button" ng-click="delete()" confirm-if="checked" confirm="Are you sure, {{name}}?">Delete</button>

As a service:

$confirm({text: 'Are you sure you want to delete?'})
  .then(function() {
    $scope.deletedConfirm = 'Deleted';
  });
James Kleeh
  • 12,094
  • 5
  • 34
  • 61
  • This one is pretty close but sort of jumps because it alters the scroll instead of doing an overlay. – Demodave Feb 03 '15 at 14:43
0

Ok, here is the one I ended up going with

1) Create a service for the dialog

app.service('dialogModal', [
    '$modal', function($modal) {
    return function(message, title, okButton, cancelButton) {

      okButton = okButton === false ? false : (okButton || 'Yes');
      cancelButton = cancelButton === false ? false : (cancelButton || 'No');

      var modalInstance = $modal.open({        
        templateUrl: '/templates/deletePrompt.html',
        controller: ModalInstanceCtrl,
        resolve: {
          settings: function() {
            return {
              modalTitle: title,
              modalBody: message,
              okButton: okButton,
              cancelButton: cancelButton
            };
          }
        }
      });
      // return the modal instance
      return modalInstance;
    }
  }
]);

2) Create a controller and pass the model instance in it

var ModalInstanceCtrl = function ($scope, $modalInstance, settings) {

  angular.extend($scope, settings);

  $scope.ok = function () {
    $modalInstance.close(true);
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
};

3) included the link into the header to take on default styling

<link data-require="bootstrap-css@3.x" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />

4) overwrote the styling in my own css

5) Here is my delete prompt template

<div id="overlayClearMainDiv" class="dialog-modal">
  <div id="overlayClearText">
    <span>{{modalBody}}</span>
  </div>
  <div id="overlayClearButton">
    <button id="overlayClearYesButton" class="confirmButton" type="button" ng-click="ok()" ng-show="okButton">{{okButton}}</button> 
    <button class="confirmButton-white" ng-click="cancel()" ng-show="cancelButton">{{cancelButton}}</button> 
  </div>
</div>
Demodave
  • 6,242
  • 6
  • 43
  • 58
0

Here's a quick one for you - http://plnkr.co/edit/YklthDZcknmvMjU5A6pe?p=preview

So basically if you are interested in showing a modal dialog once a user clicks on let's say, a button there's no need to make it difficult. All you need is a simple directive that encapsulate $modal service found in ui-bootstrap.

In my simple example I just pass in a string representing a message and then defining a on-confirm attribute that my directive invokes once the user confirms. Invoking the function itself leverages the awesomeness of $parse to resolve the expression and once resolved, invoke it with the scope.

Nice and clear and here's how it looks like.

View

<body ng-controller="AppController">

    <input type="button" value="Delete" 
           confirm="'Are you sure you want to delete me?'" on-confirm="delete()" />



    <script type="text/ng-template" id="modal.html">
        <div class="modal-header">
            <h3 class="modal-title">Confirm</h3>
        </div>
        <div class="modal-body">
            <p>The world won't be a better place if you delete me.</p>
        </div>
        <div class="modal-footer">
            <button class="btn btn-primary" ng-click="ok()">OK</button>
            <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
        </div>
    </script>

 </body>

Controller / Directive

angular
    .module('App', ['ui.bootstrap'])
    .controller('AppController', ['$scope', function($scope){
        $scope.delete = function(){
            alert('Woho, Im deleted!');
        };
    }])
    .directive('confirm', ['$modal', '$parse', function($modal, $parse){
        return {
            link: function(scope, el, attr){
              el.bind('click', function(){
                var instance = $modal.open({
                  templateUrl: 'modal.html',
                  controller: ['$scope', '$modalInstance', modalController]
                });

                instance.result.then(function(){
                  // close - action!
                  $parse(attr.onConfirm)(scope);
                },function(){
                  // dimisss - do nothing
                });
              });
            }
        };

        function modalController(modalScope, $modalInstance){
            modalScope.ok = function(){
                modalInstance.close();
            };
            modalScope.cancel = function(){
                modalInstance.dismiss();
            };
        }
    }]);
mengstrom
  • 233
  • 3
  • 13