$mdDialog stacked/nested dialogs, is it possible? - angularjs

I want to open one $mdDialog on top of the other. If possible, have unlimited overlapping dialogs.
Any ideas?

UPDATE: As per #Magnus, it was updated to multiple as of v1.1.2
Add skipHide: true to the second dialog's options object.
This works for me: http://webiks.com/mddialog-with-a-confirmation-dialog/

Gabriel Anzaldo Alvarado gave the right answer in my opinion, the correct answer is shared in a Plunker link. But as requested by many users, I'm adding the actual code in case the link becomes unavailable in the future.
Basically, while opening your dialog using the .show({}) function, add the option skipHide: true.
HTML
<body>
<div ng-controller="AppCtrl as ctrl"
ng-cloak=""
class="virtualRepeatdemoVerticalUsage"
ng-app="MyApp">
<md-content layout="column">
<md-button ng-click="ctrl.moreInfo(1)">
Open Dialog
</md-button>
</md-content>
</div>
</body>
JavaScript
(function () {
'use strict';
angular
.module('MyApp', ['ngMaterial'])
.controller('AppCtrl', function ($interval, $mdDialog) {
var vm = this;
vm.moreInfo = function moreInfo(thing) {
$mdDialog.show({
controllerAs : 'dialogCtrl',
clickOutsideToClose : true,
bindToController : true,
controller : function ($mdDialog) {
this.click = function click() {
$mdDialog.show({
controllerAs : 'dialogCtrl',
controller : function ($mdDialog) {
this.click = function () {
$mdDialog.hide();
}
},
preserveScope : true,
autoWrap : true,
skipHide : true,
template : '<md-dialog class="confirm"><md-content><md-button ng-click="dialogCtrl.click()">I am in a 2nd dialog!</md-button></md-content></md-dialog>'
})
}
},
autoWrap : false,
template : '<md-dialog class="stickyDialog" data-type="{{::dialogCtrl.thing.title}}"><md-content><md-button ng-click="dialogCtrl.click()">I am in a dialog!</md-button></md-content></md-dialog>',
locals : {
thing : thing
}
})
}
});
})();
The code above worked for me.
As noted by Vincenzo in another answer, while stacking mdDialogs, the dialogs beneath will not grey-out, there is a CSS trick to solve that: https://stackoverflow.com/a/38013682/366662
UPDATE
This answer works for version 1.1.1, from version 1.1.2 the Material team changed the property from skipHide to multiple. So, be careful when copy-pasting the snippet. Check your Material version and use the correct property accordingly.

No, it is not possible right now to have multiple $mdDialog. Honestly I really need this feature and tried it to get it to work but no success so far. Lets hope they allow this feature in future releases. Although there is discussion here, you can find something useful there.
NOTE: This is no longer the correct answer, as opposed to the time period it was answered, look below for more answers.

As Gabriel Anzaldo Alvarado wrote in the comment, it is possible as you can see on this Plunker:
http://plnkr.co/edit/Ga027OYU5nrkua3JxNRy?p=preview
In addition, you can add some css classes to get the same background grey overlay:
https://github.com/angular/material/issues/7262
._md-dialog-backdrop:nth-of-type(even) {
z-index: 81;
}
._md-dialog-backdrop:nth-of-type(odd) {
z-index: 79;
}
.md-dialog-container:nth-of-type(even) {
z-index: 80;
}
.md-dialog-container:nth-of-type(odd) {
z-index: 82;
}
UPDATE:
From Angular Material v1.1.2 the option skipHide has been replaced by multiple.

skiphide has been deprecated. Use multiple key instead. See documentation here
here is a code snippet
myCtrl.demoClick = function moreInfo(thing) {
$mdDialog.show({
controllerAs: 'dialogCtrl',
clickOutsideToClose: true,
bindToController: true,
controller: function ($mdDialog) {
this.click = function click() {
$mdDialog.show({
preserveScope: true,
multiple: true,
controllerAs: 'dialogCtrl',
controller: function ($mdDialog) {
this.click = function () {
$mdDialog.hide();
}
},
template: '<md-dialog class="confirm"><md-content>I am in a 2nd dialog!<md-button class="md-raised" ng-click="dialogCtrl.click()">Confirm!</md-button></md-content></md-dialog>'
})
}
},
autoWrap: false,
template: '<md-dialog class="stickyDialog" data-type="{{::dialogCtrl.thing.title}}"><md-content>I am in a dialog!<md-button class="md-raised" ng-click="dialogCtrl.click()">Click me to do something</md-button></md-content></md-dialog>',
locals: {
thing: thing
}
})}

Here is a workaround to have nested dialogs.
The idea is when the second is open save the state of the first, and when the second is closed launch the first dialog again.

From Angular Material version 1.1.2 an above: Use option multiple.
Use option skipHide for previous versions.
Example
$mdDialog.show({
template: 'asdf'
controller: "xyzController",
multiple: true // Replace with "skipHide" on Angular Material 1.1.1 or before
})

I got this to work with very little effort and a bit of angular hacking.
To make things clear, I'm using Angular v1.5.3 & Angular Material v1.0.6.
With previous versions if you add skipHide: true to your dialog definition object, it will allow multiple dialogs. Your issue then comes to the cancel button which will close the wrong dialog box.
The solution is rather calling $mdDialog.cancel we want to call $mdDialog.hide as it correctly resolves the right dialog box. Rather than making sure you have setup every instance correctly, or even making sure 3rd party libs also follow this pattern, we can decorate the $mdDialogProvider.
$provide.decorator
$provide.decorator(name, decorator);
Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behavior of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
angular.module('module').config(function($provide) {
$provide.decorator('$mdDialog', function($delegate) {
var methodHandle = $delegate.show;
function decorateDialogShow() {
var args = angular.extend({}, arguments[0], {
skipHide: true
});
return methodHandle(args);
}
$delegate.show = decorateDialogShow;
$delegate.cancel = function() {
return $delegate.hide(null);
}
return $delegate;
});
});
The above will simply replace the cancel method, with the existing and working hide method. Also sets a global default so that skipHide is set initially on all dialog boxes.
Winner winner!

$mdDialog.show({
parent: angular.element(document.body),
templateUrl: 'template.html',
clickOutsideToClose: true,
fullscreen: true,
preserveScope: true,
autoWrap: true,
skipHide: true,
controllerAs: 'customDialog',
controller: function ($mdDialog) {
this.callNewDialog = function (dialogCallback) {
dialogCallback();
};
}});
call in view:
ng-click="customDialog.callNewDialog(vm.addNewCustomer)"
and vm.addNewCustomer will be a function that open new dialog

Yes it's possible, just add "skipHide: true" where you calling mdDialog..
just like:
$scope.xyz = function(anything) {
$mdDialog.show({
controller: "xyzController",
skipHide: true,
templateUrl: 'path-to-partial/xyzDialog.html',
parent: angular.element(document.body),
clickOutsideToClose: true
})
}
Update: this is for angularJs (Angular 1)

Actually you can use mdPanels. Little snippet:
return $q(function(resolve, reject){
$mdPanel.open(Object.assign({
hasBackdrop: true,
zIndex: 85, //greater than modal and lower than autocomplete\select
locals: Object.assign({
onClose: resolve
}, locals),
template: getCommonTemplate(template, heading),
bindToController:true,
controller: 'PanelDummyController as $ctrl',
panelClass: 'rl-modal-panel',
position: $mdPanel.newPanelPosition()
.absolute()
.center()
}))
});
controller('PanelDummyController', function (mdPanelRef) {
'ngInject';
const close = () => mdPanelRef.close().then(() => {
this.onClose(Object.assign({}, this));
});
this.$mdDialog = {
cancel: close,
hide: close
};
});
and add little styling to class.
It's not fully copy of modal, but it's quite good implementation and could be improved to fully copy.

in the latest AngularJs Material dialog, you can find this solution:
https://material.angularjs.org/latest/api/service/$mdDialog#multiple-dialogs
It uses multiple dialog preset or configuration.

Absolutely not possible.* for now. It is understandable for some situations but other way is needed too.
So, I preferred to use custom popup dialogs variates for same functionalities.
Otherwise md-dialog gives pain to doing job.
Yes I did that but if 3 nested needed then writing code ok but another one can not understand it.
You can do the job by giving scope and preservescope parameters then manage when to close and which to open next ...brrrr...

Related

angular array item not updating on scope change

Spent a few hours on this already, sifted through numerous stack posts and blogs but can't seem to get this to make my model update. More specifically, I am trying to update an array item (ng-repeat). In the simple case below, I iterate over venues list, and upon toggling a "like" button, I update the server appropriately, and reflect the change on the venues item on the $scope.
in my search.html I have a directive:
<ion-content>
<venues-list venues="venues"></venues-list>
</ion-content>
and search controller I have:
app.controller('bleh', function(Service) {
...
$scope.venues = [{ id: 1, name: 'Venue1', like: false },{ id: 2, name: 'Venue2', like: false }];
...
});
Nothing unusual there. Then in my venues-list directive:
app.directive('venues-list', function() {
function venueListController($scope, Service) {
$scope.likeToggle = function(venue, $index) {
Service.likeVenue(venue.id, !venue.like).then(function() {
$scope.venues[$index].like= !venue.like;
});
}
}
return {
strict: 'E',
templateUrl: 'venue.html',
controller: venueListController,
scope: {
venues: '='
}
}
});
then in my venue.html I have:
<div ng-repeat="venue in venues">
<p>{{venue.name}}</p>
<button ng-click="likeToggle(venue, $index)">Like</button>
</div>
I have tried many different options:
$scope.$apply() // after updating the $scope or updating scope within apply's callback function;
$scope.$digest()
$timeout(function() { // $scope.venues[$index] .... }, 0);
safe(s,f){(s.$$phase||s.$root.$$phase)?f():s.$apply(f);}
so
safe($scope, function() { $scope.venues[$index].like = !venue.like });
I haven't yet used the link within the directive, but my venues.html template is obviously a little more elaborate than presented here.
EDIT:
Just to keep the discussion relevant, perhaps I should have mentioned - the data is coming back from the server with no issues, I am handling errors and I am definitely hitting the portion of the code where the $scope is to be updated. NOTE: the above code is a small representation of the full app, but all the fundamental pieces are articulated above.
Search Controller
Venues Service
venue-list directive and venue.html template to accompany the directive
directive controller
EDIT #2
$scope.foo = function() {
$scope.venues[0].like = !$scope.venues[0].like;
}
Just to keep it even simpler, the above doesn't work - so really, the bottom line is my items within an array are not reflecting the updates ...
EDIT #3
$scope.foo = function() {
$scope.venues[0].like = !$scope.venues[0].like;
}
My apologies - just to re-iterate what I was trying to say above - the above is changing the scope, however, the changes are not being reflected on the view.
Perhaps the issue is with your service and promise resolution.. Can you put a console.log there and see if the promise resolution is working fine? or Can you share that code bit. Also where are you checking for scope update? within directive or outside
OK after some refactoring I finally got it working.
The "fix" (if you want to call it that) to my specific problem was:
instead of passing an array of venues, I was iterating over the array on the parent controller, passing in a venue as an element attribute that would bind (two-way) on the isolated scope of the directive.
so, instead of:
<ion-content>
<venues-list venues="venues"></venues-list>
</ion-content>
I now have:
<ion-content>
<venues-list ng-repeat="venue in venues" venue="venue"></venues-list>
</ion-content>
and my directive now looks like:
app.directive('venues-list', function() {
function venueController($scope, Service) {
$scope.likeToggle = function(venue) {
Service.likeVenue(venue.id, !venue.like).then(function() {
$scope.venue.like = !venue.like;
});
}
}
return {
strict: 'E',
templateUrl: 'venue.html',
controller: venueController,
scope: {
venue: '='
}
}
});
This did the trick!

Get height of a element in ui-view

I have a state that can have a variable height (depending on the content). I want to set that height as a css attribute for my background-color.
But I'm having trouble getting the correct height because the code fires before the content is loaded in the state.
I have this state,
$stateProvider
.state('home.suggestions', {
url: '',
views: {
"suggestions": {
templateUrl: '../assets/angular-app/templates/_suggestions.html',
controller: function(){
showHeight = $('.suggestions-container-wrapper').outerHeight();
console.log(showHeight)
}
},
},
})
But it always returns 175 (the height of the element before it loads the ng-repeat content.
How do I run the code after the all the calls are done?
Just found a simple solution. But a ng-init after the ng-repeat element, then it fires the function when it gets triggerd.
https://stackoverflow.com/a/24817862/848706
%div{"ng-init" => "setHeight()"}
Controller,
$scope.setHeight = function (){
$timeout(function(){
showHeight = $('.suggestions-container-wrapper').outerHeight();
console.log(showHeight)
}, 0);
}
Check out the docs on $viewContentLoaded. You might be able to do:
controller: function(){
$scope.$on('$viewContentLoaded', function(){
showHeight = $('.suggestions-container-wrapper').outerHeight();
console.log(showHeight)
});
}
Here is also another post that discusses it.

How to Check for unsaved changes when switching nested views in a same Page?

When we are switch between one state to another state here i am able to check unsaved changes and i can ask user for conformation before moving one page to another but in a same view i have multiple forms in that if am switch between one form to another form first i have to to give message to the user are you sure u want leave page without saving the data then only i can move to another form How can i do this.
example of multiple forms in same view below i have metioned
<div ng-switch-when="formTab">
<div ng-include="'/views/tmpl/workForm.html'"></div>
</div>
<div ng-switch-when="effortTab">
<div ng-include="'/views/tmpl/workEffort.html'"></div>
</div>
In the above example while moving from formTab to effortTab i need to show that unsaved changes message
Use the $dirty property of the form:
console.log(myForm.$dirty);
Plunker
I got solution for this Instead of using ng-switch we can use nested states with the parent state.
using unsavedChanges directive we can check automatically whether any unsaved data is there in the current form. if condition matches this directive will warn the user this will work based on ui-state so it will check automatically when we are injected directive no need to do manually..
Below example
$stateProvider.state('edit', {
abstract: true,
url: '/edit',
templateUrl: 'edit.html',
controller: function($scope) {
$scope.model = {};
$scope.validator = {
basic: null,
extended: null,
isValid: function() {
return this.basic && this.extended;
}
};
}}).
state('edit.formTab', {
url: '/formTab',
templateUrl: 'formTab.html',
controller: function($scope) {
$scope.$watch('formTab.$valid', function(value) {
$scope.validator.formTab= value;
});
}}).
state('edit.effortTab', {
url: '/effortTab',
templateUrl: 'effortTab.html',
controller: function($scope) {
$scope.$watch('effortTab.$valid', function(value) {
$scope.validator.effortTab= value;
});
}});
we can use below link :
http://plnkr.co/edit/OyVBpp?p=preview

AngularJS: What is the best way to bind a directive value to a service value changed via a controller?

I want to create a "Header" service to handle the title, buttons, and color of it.
The main idea is to be able to customize this header with a single line in my controllers like this:
function HomeCtrl($scope, Header) {
Header.config('Header title', 'red', {'left': 'backBtn', 'right': 'menuBtn'});
}
So I created a service (for now I'm only focussing on the title):
app.service('Header', function() {
this.config = function(title, color, buttons) {
this.title = title;
}
});
...And a directive:
app.directive('header', ['Header', function(Header) {
return {
restrict: 'E',
replace: true,
template: '<div class="header">{{title}}</div>',
controller: function($scope, $element, $attrs) {
$scope.$watch(function() { return Header.title }, function() {
$scope.title = Header.title;
});
}
};
}]);
So, this actually works but I'm wondering if there are no better way to do it.
Especially the $watch on the Header.title property. Doesn't seem really clean to me.
Any idea on how to optimize this ?
Edit: My header is not in my view. So I can't directly change the $scope value from my controller.
Edit2: Here is some of my markup
<div class="app-container">
<header></header>
<div class="content" ng-view></div>
<footer></footer>
</div>
(Not sure this piece of html will help but I don't know which part would actually...)
Thanks.
If you are using title in your view, why use scope to hold the object, rather than the service? This way you would not need a directive to update scope.header, as the binding would update it if this object changes
function HomeCtrl($scope, Header) {
$scope.header = Header.config('Header title', 'red', {'left': 'backBtn', 'right': 'menuBtn'});
}
and refer to title as
<h1>{{header.title}}</h1>
Update
Put this in a controller that encapsulates the tags to bind to the header:
$scope.$on("$routeChangeSuccess", function($currentRoute, $previousRoute) {
//assume you can set this based on your $routeParams
$scope.header = Header.config($routeParams);
});
Simple solution may be to just add to rootScope. I always do this with a few truly global variables that every controller will need, mainly user login data etc.
app.run(function($rootScope){
$rootScope.appData={
"header" : {"title" : "foo"},
"user" :{}
};
});
.. then inject $rootScope into your controllers as warranted.

Using a Modal Window with KendoUI inside of AngularJS

Does anyone have any experience using KendoUI's window with AngularJS?
I'm currently using Angular-Kendo but I'm not entirely sure hot to cleanly use the window. Or if there is any other solutions for presenting a modal dialog and filling it with a form loaded via a partial I'm open to that as well.
My current code looks something like this:
HTML:
<div kendo-window id="addWindow" ng-hidden></div>
JS:
$scope.addSection = function() {
$("#addWindow").data("kendoWindow").open();
return false;
};
But I hate this, and it feels wrong for how I'm doing everything else. Any thoughts on a better way?
Check out this blog post:
http://www.kendoui.com/blogs/teamblog/posts/13-06-24/announcing-angular-kendo-ui.aspx?utm_content=bufferbbe83&utm_source=buffer&utm_medium=twitter&utm_campaign=Buffer
They rewrote Angular-Kendo and have an example of a clean way to use a window.
#anise thanks for ur information
finally i also resolve the issue.
Controller
$scope.window;
$scope.OpenWindow= function() // custom function on click
{
$scope.DlgOptions = {
width: 550,
height: 400,
visible: false,
actions: [
"Maximize",
"Close"
]
};
$scope.window.setOptions($scope.DlgOptions);
$scope.window.center(); // open dailog in center of screen
$scope.window.open();
};
View
<div kendo-window="window" k-visible="false" k-modal="true"> </div>
Check out this library
https://github.com/kjartanvalur/angular-kendo-window
var windowInstance = $kWindow.open({
options:{
modal: true,
title: "Window title",
width: 400,
},
templateUrl: 'modal1.html',
controller: 'modalController',
resolve: {
parameter1: function () {
return "Test...";
}
}
});
windowInstance.result.then(function (result) {
// Here you can get result from the window
});

Resources