Angular JS material dialog with multiple options to save - angularjs

I'm trying to have a dialog with multiple options to save and another option to save and close as well as cancel option, where the (save and close) button will save the data and close the dialog, while the (save) button will save the data in modal then open an empty instance of the modal, the problem is when adding two options with save I see only buttons for save and cancel, here is the angular Material Snippet example I'm modifying:
$scope.showConfirm = function(ev) {
// Appending dialog to document.body to cover sidenav in docs app
var confirm = $mdDialog.confirm()
.title('Would you like to delete your debt?')
.textContent('All of the banks have agreed to forgive you your debts.')
.ariaLabel('Lucky day')
.targetEvent(ev)
.ok('Save and Close')
.ok('Save')
.cancel('Cancel');
When clicking on Confirm Dialog button I would like to see three buttons, below is the modified code:
https://codepen.io/anon/pen/dgWzjw

You can't achieve the dialog presentation you've described using $mdDialog.confirm().
This method provides a preconfigured dialog that can only ever have two action buttons. You can build the dialog you want by providing more configuration parameters to $mdDialog.show().
Here's an example.
You'll need to provide the HTML for your custom dialog:
<script type="text/ng-template" id="custom-confirm.html">
<md-dialog>
<md-dialog-content>
<md-content layout-padding>
<div>...</div>
</md-content>
</md-dialog-content>
<md-dialog-actions>
<md-button ng-click="cancel()">Cancel</md-button>
<md-button ng-click="save()">Save</md-button>
<md-button ng-click="saveAndClose()">Save and Close</md-button>
</md-dialog-actions>
</md-dialog>
</script>
Then provide a custom dialog configuration to $mdDialog.show():
$scope.showCustomConfirm = function () {
$mdDialog.show({
controller: function ($scope, $mdDialog) {
$scope.cancel = function () {
$mdDialog.cancel();
};
$scope.save = function () {
/* ... */
};
$scope.saveAndClose = function () {
/* ... */
};
},
templateUrl: 'custom-confirm.html',
});
};
I've forked your CodePen to include the example described above.
Edit
To have the save button reopen the same dialog, simply chain the the call to open the dialog onto a call to first hide it. You can do this because the return value of $mdDialog.hide() is a promise that resolves once the dialog has hidden itself.
To follow on from the above example, you'll need to do some some slight refactoring to make sure you're not shadowing $scope:
$scope.showCustomConfirm = showCustomConfirm;
function showCustomConfirm() {
$mdDialog.show({
controller: function ($scope, $mdDialog) {
$scope.save = function () {
// Save data...
$mdDialog.hide().then(showCustomConfirm);
};
// Everything else as before...
},
});
}
And here's an updated CodePen fork.

Related

$mdDialog not close

I am having mdDialog in one controller to show details from another controller.when I click the close button, it does not close.
I used both :
$mdDialog.hide() and $mdDialog.cancel()
re-check all these:
correct usage of $mdDialog API:
$mdDialo.show({
controller:function($scope,$mdDialog){
$scope.close = function(){$mdDialog.hide();}
},
templateUrl:'some-template.dialog.html'
})
correct link from controller to template.
i.e. if you are using $scope.close = function(){ ... }, then your template should look like : <md-button ng-click=close()>close</md-button>
and if you are using controller:
this.close = function(){...}
then you need to add a controllerAs in your $mdDialog.show() config object:
controllerAs:'$ctrl'
and in your template:
<md-button ng-click=$ctrl.close()></md-button>

ngClick on mdDialog's action button not triggering controller function

I am trying to undertsand angular dialogs. I have created a very simple code to display a custom alert dialog. I am trying to pass a string message to the dialog. Dialog has an OK button which shoudl close it.
The message data is being passed correctly. However for some reason, the OK button is not triggering the dialog controller's function.
Here is my code
Alert.html - Contains template for Alert dialog
<md-dialog>
<md-dialog-content>
{{message}}
</md-dialog-content>
<md-dialog-actions>
<md-button ng-click="alertFunc">
OK
</md-button>
</md-dialog-actions>
</md-dialog>
AlertCtrl.js - Contains Controller for Alert Dialog
app.controller('AlertCtrl', function ($scope, $mdDialog, message) {
$scope.message = message
$scope.alertFunc = function () {
console.log("Closing Alert Dialog")
$mdDialog.hide()
}
})
DialogService.js - Contains API to launch alert dialog
app.service('DialogService', function($mdDialog) {
// Launch Alert Dialog
this.alert = function (message) {
// Show Dialog
$mdDialog.show({ templateUrl: 'Alert.html',
controller: 'AlertCtrl',
clickOutsideToClose: true,
locals: { message: message }
})
}
})
The log inside alertFunc never shows up.
I also tried putting controllerAs : 'ctrl' inside the $mdDialog.show. Then I changed the alertFunc declaration to this.alertFunc = function... and changed ng-click='ctrl.alertFunc'. However this did not work as well.
Can someone help me figure out the problem.
Thank You
It should be ng-click="alertFunc()" instead of ng-click="alertFunc". 2 hours of effort and a stupid parenthesis.
Amazing how ignorant a programmer can get.

Manage ng-if from another controller

I have prepared a Plunkr that provides the code:
https://plnkr.co/edit/WXtQxVcdgyvnaajbs4eJ?p=preview
I have assigned the object $scope.menu.show in controller toolbarCtrl that controls whether the menu is created or not.
<md-menu id="menu" ng-controller="toolbarCtrl" ng-if="true"> ...
When toolbarCtrl gets initialized I can control whether the menu is shown but I would like to control it from another controller. In this case I created two buttons in mainCtrl that produce true or false and I'm trying to assign the value to the $rootScope but that doesn't work.
How can I do that?
UPDATE:
Although jtmingus solution works perfectly I came up with another solution that doesn't need events.
I created this factory service:
app.factory('menuState', function () {
var state = {val: false};
function getState() {
return state;
}
function setState(newState) {
state.val = newState;
}
return {
getState: getState,
setState: setState
}
});
Then at the toolbarCtrl controller I initially hide the menu
app.controller('toolbarCtrl', function($scope, menuState) {
// hide the menu
menuState.setState(false);
// pass the value to the menu
$scope.state = menuState.getState();
});
on the mainCtrl I define the button clicks like that:
app.controller('mainCtrl', function($scope, $rootScope, menuState) {
$scope.state = menuState.setState;
});
and 2 buttons on the the main.html
<md-button class="md-primary md-raised" ng-click="setState(true)">Show</md-button>
<md-button class="md-primary md-raised" ng-click="setState(false)">Hide</md-button>
The way that data generally should be shared through controllers is by using a service. Because you need both controllers to watch the same state, you can use Angular's events, specifically the $on event.
Here is an article that will walk you through having multiple controllers watch the same state:
https://variadic.me/posts/2013-10-15-share-state-between-controllers-in-angularjs.html
Edit: After looking through this article some more, I realized it's not the best explanation, and there are some things in the code that don't work. For example, if you try to pass arguments in with a $broadcast call, the $on function needs two paramaters–event and args.
On the controller from which you want to initiate showing the menu,
app
.controller('MainCtrl', ['$scope', function($scope) {
$scope.$broadcast('menu:show');
}])
.controller('ToolbarCtrl', ['$scope', function($scope) {
var ToolbarCtrl = this;
$scope.$on('menu:show', function() {
ToolbarCtrl.showMenu = true;
});
}])
In the markup switch using this value
<md-menu id="menu" ng-controller="ToolbarCtrl" ng-if="ToolbarCtrl.showMenu"> ...

Pass variable to UI-bootstrap modal without using $scope

Since I am a beginner using AngularJS the $scope approach to pass data between different controllers and (in my case) a modal drives me crazy. Due to that reason, I googled around the web and found an interesting blogpost about passing data to a UI-bootstrap modal without using $scope.
I had a deeper look at this blogpost and the delivered plunk which works pretty nice and started to adopt this to my own needs.
What I want to achieve is to open a modal delivering an text input in which the user is able to change the description of a given product. Since this would provide more than a minimal working example I just broke everything down to a relatively small code snippet available in this plunk.
Passing data from the main controller into the modal seems to work as the default product description is displayed in the modal text input as desired. However, passing the data back from the modal to the main controller displaying the data in index.html does not seem to work, since the old description is shown there after it was edited in the modal.
To summarize my two questions are:
What am I doing wrong in oder to achieve a 'two-way-binding' from the main controller into the modal's text input and the whole way back since the same approach works in the mentioned blogpost (well, as the approach shown in the blogpost works there must be something wrong with my code, but I cannot find the mistakes)
How can I implement a proper Accept button in order to accept the changed description only if this button is clicked and discard any changes in any other case (clicking on Cancel button or closing the modal by clicking next to it)?
In your main controller, create two resolver functions: getDescription and setDescription.
In your modal controller, use them.
Your modal HTML
<div class="modal-header">
<h3 class="modal-title">Test Text Input in Modal</h3>
</div>
<div class="modal-body">
Product description:
<input type="text" ng-model="modal.description">
</div>
<div class="modal-footer">
<button ng-click="modal.acceptModal()">Accept</button>
<button ng-click="modal.$close()">Cancel</button>
</div>
Your main controller
function MainCtrl($modal) {
var self = this;
self.description = "Default product description";
self.DescriptionModal = function() {
$modal.open({
templateUrl: 'modal.html',
controller: ['$modalInstance',
'getDescription',
'setDescription',
ModalCtrl
],
controllerAs: 'modal',
resolve: {
getDescription: function() {
return function() { return self.description; };
},
setDescription: function() {
return function(value) { self.description = value; };
}
}
});
};
};
Your modal controller
function ModalCtrl($modalInstance, getDescription, setDescription) {
var self = this;
this.description = getDescription();
this.acceptModal = function() {
setDescription(self.description);
$modalInstance.close();
};
}

reusable modal windows design patterns angularJS

I have been trying to create a modal windows that are re-usable with angularJS.
While I am googling I come to know that re-usable modals are created by using
custom directives.
services.
I'm in a confusion which method to follow.
What is the most customized method for creating modal windows in angularJS? and any resources on how to create re-usable modal windows (design patterns for modal windows) in angularJS
Note: Please suggest solutions without angular-bootstrap-ui or bootstrap.
Update:
I am trying to develop a similar kind of screen.
when the user clicks on "select Bus" link a modal window will be displayed.
Title, Content of the modal window is based on the corresponding hyper link.
I've already done this screen by using custom directives with the help of
How to Create a Simple Modal Dialog Directive in Angular.js
But what I am going to rewrite it as a reusable module or directive.
So suggest some design patterns to create re usable custom modal dialog windows using angularJS
Please check the following link for re-usable windows in angular.
That may resolve your issues.
http://www.dwmkerr.com/the-only-angularjs-modal-service-youll-ever-need/
A fairly simple one is ng-modal (https://github.com/adamalbrecht/ngModal), it is a directive, meaning it is highly re-usable. Services are do-able as well, but they are meant to get/set/process data rather than display HTML; hence directives are the way to go.
I've used ng-modal and adding just a little code to it went a long way to make it super-reusable. You can place it in it's own controller and have it always injected so that you can open the modal and show a message or some html.
HTML:
<div ng-controller="modalCtrl">
<modal-dialog show='dialogShown' dialog-title='titleToUse' height='400px' width='75%'>
<p ng-bind="messageToShow"> </p>
<div ng-bind-html="someHTML"></div>
</modal-dialog>
</div>
JS:
myApp.controller('modalCtrl', function($scope){
$scope.titleToUse = "Modal Title";
$scope.messageToShow = "Testing Message"; // default message
$scope.someHTML = "<div>Whoa! A Div!</div>";
$scope.$on('changeMessageEvent', function($event, message){ // listens for change message event and sets new message
$scope.messageToShow = message;
});
});
myApp.controller('someOtherController', function($scope, $rootScope){ // import rootScope
var messageToSendToModal = "New Message!";
$rootScope.$broadcast('changeMessageEvent', messageToSendToModal );
});
Update:
If you want dynamic template and controllers, that is easily to do with ng-modal, you just have to use ng-include:
<div ng-controller="modalCtrl">
<modal-dialog show='dialogShown' dialog-title='titleToUse' height='400px' width='75%'>
<p ng-bind="messageToShow"> </p>
<div ng-bind-html="someHTML"></div>
<!-- some dynamic template -->
<ng-include src="pathToTemplate"></ng-include>
</modal-dialog>
</div>
where in modalCtrl you have
$scope.pathToTemplate = "/path/to/template.html";
that template can contain a controller and can be switched out dynamically via variables.
If anyone is looking for another example I just had a crack at creating a custom modal service and directive myself, with them you can add modals to views like this:
<button ng-click="vm.openModal('custom-modal-1')">Open Modal 1</button>
<modal id="custom-modal-1">
<div class="modal">
<div class="modal-body">
<h1>A Custom Modal!</h1>
<p>
Home page text: <input type="text" ng-model="vm.bodyText" />
</p>
<button ng-click="vm.closeModal('custom-modal-1');">Close</button>
</div>
</div>
<div class="modal-background"></div>
</modal>
Here's the controller that opens and closes the modal:
(function () {
'use strict';
angular
.module('app')
.controller('Home.IndexController', Controller);
function Controller(ModalService) {
var vm = this;
vm.openModal = openModal;
vm.closeModal = closeModal;
initController();
function initController() {
vm.bodyText = 'This text can be updated in modal 1';
}
function openModal(id){
ModalService.Open(id);
}
function closeModal(id){
ModalService.Close(id);
}
}
})();
This is the custom modal service:
(function () {
'use strict';
angular
.module('app')
.factory('ModalService', Service);
function Service() {
var modals = []; // array of modals on the page
var service = {};
service.Add = Add;
service.Remove = Remove;
service.Open = Open;
service.Close = Close;
return service;
function Add(modal) {
// add modal to array of active modals
modals.push(modal);
}
function Remove(id) {
// remove modal from array of active modals
var modalToRemove = _.findWhere(modals, { id: id });
modals = _.without(modals, modalToRemove);
}
function Open(id) {
// open modal specified by id
var modal = _.findWhere(modals, { id: id });
modal.open();
}
function Close(id) {
// close modal specified by id
var modal = _.findWhere(modals, { id: id });
modal.close();
}
}
})();
And this is the custom modal directive:
(function () {
'use strict';
angular
.module('app')
.directive('modal', Directive);
function Directive(ModalService) {
return {
link: function (scope, element, attrs) {
// ensure id attribute exists
if (!attrs.id) {
console.error('modal must have an id');
return;
}
// move element to bottom of page (just before </body>) so it can be displayed above everything else
element.appendTo('body');
// close modal on background click
element.on('click', function (e) {
var target = $(e.target);
if (!target.closest('.modal-body').length) {
scope.$evalAsync(Close);
}
});
// add self (this modal instance) to the modal service so it's accessible from controllers
var modal = {
id: attrs.id,
open: Open,
close: Close
};
ModalService.Add(modal);
// remove self from modal service when directive is destroyed
scope.$on('$destroy', function() {
ModalService.Remove(attrs.id);
element.remove();
});
// open modal
function Open() {
element.show();
$('body').addClass('modal-open');
}
// close modal
function Close() {
element.hide();
$('body').removeClass('modal-open');
}
}
};
}
})();
For more info you can check out this blog post

Resources