angularJsNewbie
angularJsNewbie

Reputation: 495

AngularJS nested modal dialogs

I am porting a large CRUD business application that was created with Oracle-forms-like technology to the web (HTML5 / AngularJS / RESTful-web-services).

Part of the way the business logic is set depends on the availability of modal dialog boxes, showing CRUD grids. For starters, the users...

I've built that part, via AngularUI's ng-grid.

The next step is where I stumbled:

When I tried this via Angular UI's $dialog service, I quickly met walls - there are many issues with nested $dialog's (e.g. see this issue on the GitHub repos), and what's far more worrying, is that they recently (2 months ago) decided to simply forgo the old dialog because it had way too many issues, and rewrite it from scratch.

My question to any fellow Angular devs out there, is how do you cope with nested modal dialogs? Are you using other libs - e.g. jQueryUI's dialog? If so, how do you use them "in an Angular way"? i.e. without mixing DOM processing in your controllers? I tried to follow the example of another SO question and indeed it works, but it embeds the dialog's HTML in the page's partial - which is not good (e.g. imagine having to embed all the HTML code for your help dialog shown via F1 (the "show keyboard shortcuts dialog") in all your Angular HTML template partials!

I am considering loading dialog templates (like Angular UI dialog does) via $http and injecting the contents via ngInclude, but that would mean that I would have to keep a placeholder in my DOM for them ('#dialogPlace' or something) - and since I have a potentially unlimited "Depth" of nesting to handle, I am afraid I will have to code "stack handling" on my own, adding DOM elements all the way. God knows what that will cause in itself...

I really like Angular, but my problem domain requires a solid working component for nested modal dialogs. I hope someone out there has faced the problem and has a clean, Angular-like solution...

Upvotes: 2

Views: 7262

Answers (3)

Kendar
Kendar

Reputation: 710

struggling for the same problem i putted togethere various parts, building sgDialogService. It's a small module service that allows multiple nested dialogs/alerts/confirm with a syntax similar to the standard Angular dialog:

var modalInstance = sgDialogService.openModal({
        templateUrl:'sample/dialogContent.html',
        controller:"sampleController",
        data:{fromParent:dialogParam},
         callback: function(result){ $scope.callbackResult=result;}
    });
}

The idea was to

  • Use a single template for all the dialogs (based on bootstrap one)
  • Avoid any connection with other than Angular & standard Javascript
  • Build and compile the internal views dynamically with the support of the $controller and $compile services
  • Allow the usage of the dialog (whit or) without creating new scopes
  • Finally drag the dialog (nothing weird, but seems a major issue on the internet.

ASAP i'll put a version on github...will just need to add the unit tests

Edit 1

Setup the repository on github: sgDialogService

Upvotes: 0

angularJsNewbie
angularJsNewbie

Reputation: 495

Since there was no way to solve this with the "under-reconstruction" AngularUI's dialog service, I used jQueryUI's dialog (which has no issues with modals spawning modals spawning modals...), and created a sort of "mini-framework" of my own. I based a lot of what I did on the sources of AngularUI's dialog, and it seems to work fine.

This is the Angular service I coded (typescript code, so it has some type specs - but pure Javascript otherwise). Since it will spawn modal dialogs, I called the service "Plato" :-)

...

export function addNewServices(application:ng.IModule) {
    application.factory('Plato', ['$http', '$compile', function($http, $compile) {
        return {
            showDialog: function(scope, strTile:string, templateUrl:string, dialogOptions, callback) {
                scope.dialogOptions = dialogOptions;
                scope.dialogOptions.callback = callback;
                $http.get(
                    templateUrl,
                    {timeout:globals.timeoutInMs, cache:false}
                ).success(function(response, status, header, config) {
                    var newDialogId = Sprintf.sprintf("npInnerDlg%d", globals.dialogCounter);
                    globals.dialogCounter += 1;
                    var modalEl = angular.element('<div id="' + newDialogId + '">');
                    modalEl.html(response);
                    $('body').append(modalEl);
                    $compile(modalEl)(scope);
                    var component = $('#' + newDialogId);
                    scope.dialogOptions.jquiDialog = component;
                    component.dialog({
                        autoOpen:false,
                        modal:true,
                        title:strTile
                    });
                    component.dialog("open");
                }).error(function(data, status, header, config) {
                    document.body.style.cursor  = 'default';
                    if (status == 406) {
                        console.log("Received 406 for:" + header + " # " + config);
                        alert("Received 406 from web service...");
                    } else {
                        console.log("Status:" + status);
                        console.dir(config);
                        alert("Timed-out waiting for data from server...");
                    }
                });

            }
        };
    }]);
}

...and use it like this:

First, the calling code that wants to show the dialog:

var dialogOptions = {
    callback: function() {
        if (dialogOptions.result !== undefined) {
            cust.mncId = dialogOptions.result.whateverYouWant;
        }
    },
    result: {}
};

Plato.showDialog(
    $scope,
    'Choose something...',
    '/static/partials/municipalityLOV.html',
    dialogOptions
}

with the HTML partial template using angular controllers and directives as usual:

<div data-ng-controller="controllerMunicipalitiesLOV">
    <div data-ng-grid="..."
    ...

and the modal dialog controller having handlers like this:

var dialogOptions = $scope.$parent.dialogOptions;
$scope.close = function(result) {
    dialogOptions.result.whatever = ....;
    dialogOptions.jquiDialog.dialog("close");
    dialogOptions.callback();
}; 

I basically pass to showDialog:

  • the caller's scope, where I store the passed "dialogOptions"
  • the dialog title
  • the dialog's HTML template
  • the "dialogOptions" where things are passed to and from the dialog in the .result
  • the "dialogOptions" also contains a callback

The design is a complete hack, to put it mildly, but it works: I use the caller's scope to store dialogOptions, and inside the dialog's controller, I use $scope.$parent.dialogOptions, to both read incoming stuff from the caller, and store any results that the callback will read ("dialogOptions" act as a bridge between the two scopes).

At least as of 2013/Jul, it is the only Angular-y way I found/hacked to create a modal dialog that can be spawned in a nested manner (e.g. the controllerMunicipalitiesLOV controller calling showDialog in turn, and another controller calling it again, etc).

I wish I knew a way to pass "dialogOptions" as an additional parameter to the modal dialog's controller - unfortunately I am not well-versed in the Angular insides ; any help most welcome (it would make this interface far cleaner).

Hope this helps someone.

Upvotes: 2

Christopher Marshall
Christopher Marshall

Reputation: 10736

We used http://angular-ui.github.io/bootstrap/#/modal for our modals.

We essentially had the same issue's listed here. Large grid with binding data, edit button to bring up modal, delete, save etc.

You would only have one modal div hidden on the page, then pass in the grid rows model on click (which was a pain).

e.g.

$scope.editRow = function (model) {
   // do stuff.
};

<span class="icon-edit" ng-click="editRow(model.row)">Edit</span>

It was weird at field passing objects through all the ng-click/controller methods just the get the modal working, but it worked out in the end and was DRY.

Not sure if this is helpful.

Upvotes: 1

Related Questions