UI Bootstrap Modal within Directive - angularjs

I'm using Angular 1.4.1 and UI Bootstrap 0.13.
I have a directive from which I'm opening a modal. The modal opens fine, but the buttons seemingly don't get bound to their handlers - they don't do anything. I've used this same code in another project just fine, except not from within a directive.
My directive:
(function () {
var app = angular.module('myApp');
app.directive('someDirective', function () {
return {
restrict: 'E',
templateUrl: 'SomeDirective.html',
scope: {
list1: '=list1',
list2: '=list2',
save: '&'
},
controller: ['$scope','$modal','myService', function ($scope,$modal,myService) {
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: 'ModalTemplate.html',
controller: 'modalController',
backdrop: 'static',
size: 'sm',
resolve: {
saveData: function () {
//do save action
}
}
});
modalInstance.result.then(
function (itemToSave) {
//save item
},
function () {
//Cancel
}
);
};
}]
}
});
}());
The modal's controller:
(function() {
var app = angular.module('myApp');
app.controller('modalController', [
'$scope', '$modalInstance', 'saveData',
function($scope, $modalInstance, saveData) {
$scope.saveData = saveData;
$scope.save = function() {
$modalInstance.close($scope.saveData);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}
]);
}());
And the template for the modal content:
<div class="modal-header bg-info">
<h3 class="modal-title">Add New Record</h3>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form"></form>
</div>
<div class="modal-footer bg-info">
<button class="btn btn-primary" ng-click="save()">Save</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
My thought is that I'm having issues with scope, but I can't track it down. I have put break points on modalController. The app.controller() call happens when the app loads, I've seen that. But breakpoints within save() and cancel() never get hit.
Can someone help me figure out why the modal's buttons don't do anything?

This turned out to be a stupid mistake. At some point I apparently overwrote the name of one of the other controllers in my project with the same name of the controller I was using for the modal. The other controller did not have save() or cancel() methods so nothing was happening. As soon as I fixed my error and all controllers once again had their proper names this started working again.

Related

Unable to call the controller in the angular ui bootstrap $modal

I want to generate a modal pop up on click of a button. I am using ui bootstrap of angular with $modal service.
Now, when I click on button, I am writing the following code.
$scope.modalOpen = function () {
var modalInstance = $modal.open({
templateUrl: 'views/includes/someModal.html',
controller : //here I want to use the same controller where the current function is getting called
});
};
I am unable to call the same controller. Am I making any mistake? I tried Google but no success :( Please suggest. Thanks in advance :)
I am assuming the whole reason you want to do this is so you can share data (state) between the modal and the current controller.
You can achieve this without having to share the controller, using the resolve field on the modal configuration.
function CurrentController($scope, $modal) {
$scope.list = [];
$scope.modalOpen = function () {
var modalInstance = $modal.open({
templateUrl: 'views/includes/someModal.html',
controller : 'SomeOtherController',
resolve: {
list: function() { return $scope.list; }
}
});
};
}
This means that list will be dependency injected into SomeOtherController.
function SomeOtherController($scope, list) {
$scope.list = list;
}
resolve (Type: Object) - Members that will be resolved and passed
to the controller as locals; it is equivalent of the resolve
property in the router.
See the docs for $modal in angular-ui bootstrap.
If you open your modal from your controller, you can attempt something like this :
var myCtrl = this;
$scope.modalOpen = function () {
var modalInstance = $modal.open({
templateUrl: 'views/includes/someModal.html',
controller : myCtrl
});
};
But I don't think it's a good practice to reuse a controller instance.
You probably don't want to make it use the same controller. Since the scope isn't isolated, your modal can call functions from your current controller. You can also have an inline controller like the following:
The reason why you couldn't use functions from your current scope is you would have to give the modal your current scope to do that. Be careful when you do this. Your modal can mess up your current object. I went ahead and created a "save" and "cancel" button so that you can undo the changes.
Html
<div ng-controller="simpleController">
{{ item.title }}
<button ng-click="open();">Click Me</button>
</div>
modal template
<script type="text/ng-template" id="views/includes/someModal.html">
<div class="modal-header">
<h3 class="modal-title">Title!</h3>
</div>
<div class="modal-body">
title: {{ item.title }}<br/>
Click the buttons below to change the title throught the controller's function
<button ng-click="fn('test');">test</button>
<button ng-click="fn('test 1');">test 1</button>
<button ng-click="fn('test 2');">test 2</button>
</div>
<div class="modal-footer">
<button ng-click="close(true);">Save</button>
<button ng-click="close();">Close</button>
</div>
</script>
javascript
angular.module("myApp", ['ui.bootstrap']).controller("simpleController", ["$scope", "$uibModal", function($scope, $uibModal){
$scope.item = { title: "test", stuff: "other stuff"};
$scope.fn = function(title){
$scope.item.title = title;
};
$scope.open = function () {
var intialItem = angular.copy($scope.item);
var modalInstance = $uibModal.open({
templateUrl: 'views/includes/someModal.html',
controller: ["$scope", function(scope){//add other functionality here
scope.close = function(save){
if(!save){
$scope.item = intialItem;
}
modalInstance.close();
}
}],
scope: $scope
}).result.then(function(){
}, function(){
$scope.item = intialItem;//modal was dismissed
});
};
}]);

ui-bootstrap: accessing modal instance from within directive controller

Noob alert!
The question here is exactly the one that I have: angularjs - Accessing ui-bootstrap modal dismiss and close function from another controller
However, I don't understand the answer that was chosen as correct! I understand the words but don't understand how to share the controller.
I have an app controller that opens a modal, and within the modal's template, I have a directive. I would like to be able manipulate the modal instance from within the directive's controller.
Here's my markup:
<script type="text/ng-template" id="settingsModal">
<div class="modal-header">
<h3 class="modal-title">Confirm update!</h3>
</div>
<div class="modal-body">
<calendar-settings cid="calendarId"/>
</div>
<div class="modal-footer">
<!-- I want these buttons inside the directive instead -->
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
In the body of the modal, I am calling the calendarSettings directive, and I'd like to be able to use the .dismiss and .close methods of the modal instance from within my directive.
Here's what the open method looks like:
var modalInstance = $modal.open({
templateUrl: 'settingsModal',
controller: 'ModalInstanceController',
resolve: {
item: function() {
return $scope.sEntry;
},
cid: function() {
return id;
}
},
reject: {
item: function() {
return $scope.sEntry;
},
cid: function() { return null; }
}
});
And here's the ModalInstanceController:
calendarsApp.controller('ModalInstanceController', function($scope, $modalInstance, item, cid){
$scope.item = item;
$scope.calendarId = cid;
console.log(item);
$scope.ok = function () {
$modalInstance.close($scope.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});
I think what I want to do is easy, but I'm not sure how to expose the modal instance controller to the directive, or if there is a different approach I should be thinking about.
NOTE: I didn't paste the code from the directive in here. But I'm trying to access the close and dismiss methods of the modal instance from within my directive's controller.
Thanks for any help!
So
var modalInstance = $modal.open({
templateUrl: 'settingsModal',
controller: 'ModalInstanceController',
resolve: {
item: function() {
return $scope.sEntry;
},
cid: function() {
return id;
}
},
reject: {
item: function() {
return $scope.sEntry;
},
cid: function() { return null; }
}
//This comes from ModalInstanceController result is
//equivalent to whatever u send back
}).result.then(function(result){
console.log(result); //Press f12 on chrome and go to console.
});
calendarsApp.controller('ModalInstanceController', function($scope, $modalInstance, item, cid){
$scope.item = item;
$scope.calendarId = cid;
console.log(item);
$scope.ok = function () {
//You are sending back $scope.item but if you want everything
//send back the $scope which is equivalent to sending back //ModalInstanceController
// $modalInstance.close($scope.item);
$modalInstance.close($scope);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
});

Passing parent controller to modal (Angular-ui bootstrap)

I feel like this should work, but it's not. I want to be able to pass the controller that is opening the modal so I can use its submit function etc. I am using the controllerAs pattern (so not using $scope).
var _this = this;
this.openModal = function(modalId,dataIn){
this.modalInstance = $modal.open({
templateUrl: modalId,
controller: 'ModalCtrl',
controllerAs: 'modal',
resolve: {
modalObject: function(){
return dataIn;
},
title: function(){
return 'Training Info';
},
parent: function(){
return _this
}
}
});
_this.modalInstance.result.then(function (data) {
$log.log('submitting data for '+_this.doctorId);
experienceFactory.submitTraining(data).then(function(){
loadExperience();
},function(error){
console.log(error);
});
}, function () {
//something on close
$log.info('Modal dismissed at: ' + new Date());
});
};
this.something = function(){
$log.warn('omg it workt?');
};
That is opened up with a simple aCtrl.openModal('a-modal',aCtrl.data) however for some reason I can't access the parent controller
<script type="text/ng-template" id="a-modal">
<div class="modal-header">
<h3 class="modal-title">{{modal.title}}</h3>
</div>
<div class="modal-body">
<button ng-click="parent.something()">Something</button>
</div>
</script>
The button does nothing, but should be printing the warnings. Any ideas appreciated. Thanks.
Unfortunately you didn't include the code of your controller, however I think that you misunderstood how dependencies are provided to the modal's controller.
The resolve part provides dependencies to the controller, it neither binds them to the scope nor to the controller. Take a look at this JS part of UI-Bootstrap's Modal example:
.controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
$scope.items = items;
In your case it should be
.controller('ModalCtrl', ['parent', function (parent) {
this.parent = parent;
And in HTML, if you are using controllerAs pattern:
<button ng-click="modal.parent.something()">Something</button>

How do I add a reusable modal dialog in Angular?

I'm new to Angular and attempting to implement this solution into my project.
It looks painfully easy, however, I'm trying to make this into a re-usable element so that I can call it from anywhere and just pass in the content to be shown (otherwise, what's the point?).
So, my specific question is: assuming I already have a controller that's bound to some DOM element and it has a feature that goes and fetches some factory driven $http call and upon the response I wish to notify the user via this dialog of something, how do I combine *this directive and *this controller with my existing one and how do I do it in a way that allows me to then use it again from a totally different controller?
Or is this perhaps a bad example for this use and should I be looking at a different one?
Compared to other options, below given the minimalist approach, using angular factory. See a sample snippet below.
Note: using Angular JS with UI Bootstrap - AngularUI.
Reusable modal view - ConfirmationBox.html
<div class="modal-header">
<h3 class="modal-title">{{title}}</h3>
</div>
<div class="modal-body">
{{message}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-warn" data-ng-click="ok(); $event.stopPropagation()">OK</button>
<button type="button" class="btn btn-default" data-ng-click="cancel(); $event.stopPropagation()">Cancel</button>
</div>
Reusable module and shared factory, for handling the reusable modal dialog
angular.module('sharedmodule',['ui.bootstrap', 'ui.bootstrap.tpls'])
.factory("sharedService",["$q", "$modal", function ($q, $modal)
{
var _showConfirmDialog = function (title, message)
{
var defer = $q.defer();
var modalInstance = $modal.open({
animation: true,
size: "sm",
templateUrl: 'ConfirmationBox.html',
controller: function ($scope, $modalInstance)
{
$scope.title = title;
$scope.message = message;
$scope.ok = function ()
{
modalInstance.close();
defer.resolve();
};
$scope.cancel = function ()
{
$modalInstance.dismiss();
defer.reject();
};
}
});
return defer.promise;
}
return {
showConfirmDialog: _showConfirmDialog
};
}]);
Portion of your View, using the shared modal dialog
<a data-ng-click="showConfirm()">Go Back to previous page</a>
Controller of your view, opening your shared reusable modal dialog and handling notifications (Ok and Cancel)
var myModule = angular.module("mymodule", ['sharedmodule', 'ui.bootstrap', 'ui.bootstrap.tpls']);
myModule.controller('myController', ["$scope", "sharedService", "$window",
function ($scope, sharedService, $window)
{
$scope.showConfirm = function ()
{
sharedService.showConfirmDialog(
'Confirm!',
'Any unsaved edit will be discarded. Are you sure to navigate back?')
.then(function ()
{
$window.location = '#/';
},
function ()
{
});
};
}]);
Trying using 'ngDialog' library for popup and modal. Very good library. Later you can create a service which internally calls ngDialog functions. Later this service can be injected in your controllers for use. I have implemented this in one project.
The function in services can accept parameters for initialising the ngDialog modal.
Hope it helps :)
for making it better I would suggest you to modify the code to look something as below
Template:
<div class='ng-modal' ng-show='modalContent != null && modalContent != ""'>
<div class='ng-modal-overlay' ng-click='hideModal()'></div>
<div class='ng-modal-dialog' ng-style='dialogStyle'>
<div class='ng-modal-close' ng-click='hideModal()'>X</div>
<div class='ng-modal-dialog-content' ng-transclude></div>
<p>{{ modalContent }}</p>
</div>
</div>
Directive:
app.directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
modalContent: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
link: function(scope, element, attrs) {
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
scope.hideModal = function() {
scope.modalContent= null;
};
},
template: '...' // See below
};
});
and then use the code as below in template
<modal-dialog modal-content='modalMsg' width='750px' height='90%'></modal-dialog>
Once these changes are done you can write a function to set message in variable "modalMsg" and angular will take care of rest
Note: Code is pretty much the same as in the link, the only thing I have changed is the check to display the modal box

angularjs custom confirm box

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/
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
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
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
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
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';
});
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>
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();
};
}
}]);

Resources