AngularJS: Refactoring a confirmation modal directive - angularjs

I need some advice on refactoring a modal directive I have. I am just getting started with directives, so any other approach to my problem is welcome.
My program needs a confirmation modal, where we can confirm or cancel the desired action. It will appear in many places and needs to be able to have a programmable button. Cancel is consistent in that it will only hide the modal, the confirmation button needs to perform whatever action required.
I am currently using $rootScope to show / hide / configure the modal. Is this a bad idea? Please tell me.
This is what I am working with right now (roughly, as I have cut out a lot of the other unnecessary code):
index.html
<!doctype html>
<html lang="en">
<head>
<title>My App</title>
</head>
<body ng-controller="MenuCtrl">
<confirmmodal ng-show="$root.confirmModal.isVisible"></confirmmodal>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
<div ng-view></div>
<!-- build:js scripts/main.js -->
<script data-main="scripts/main" src="lib/requirejs/require.js"></script>
<!-- endbuild -->
</body>
</html>
So my modal sits atop the ng-view and can be called from anywhere. It is inside a pseudo global controller, called MenuCtrl.
Here is the modal directive code:
directives.js
/* Confirm Modal */
.directive('confirmmodal', [function() {
return {
restrict: 'E',
templateUrl: 'view/templates/modal-confirm.tpl.html'
};
}])
It serves as a template for the following code:
modal-confirm.tpl.html
<!-- Confirm Modal Template -->
<div class="overlay">
<div class="overlay-content extended">
<span>{{$root.confirmModal.content}}</span>
<div class="buttons">
<button class="btn btn-default" ng-click="$root.confirmModal.secondary.action()">{{$root.confirmModal.secondary.content}}</button>
<button class="btn btn-primary" ng-click="$root.confirmModal.primary.action()">{{$root.confirmModal.primary.content}}</button>
</div>
</div>
</div>
I set a bunch of defaults in the app.run function:
app.js
app.run(['$rootScope', function ($rootScope) {
_.extend($rootScope, {
confirmModal: {
isVisible: false,
content: '',
primary: {
action: function() {
console.log('hello world');
},
content: 'Submit'
},
secondary: {
action: function() {
$rootScope.confirmModal.isVisible = false;
},
content: 'Cancel'
}
}
});
}]);
So I've also coded a modal trigger directive, the idea being that I can create different triggers that perform different actions with the modal.
directives.js
/* Resolve Event */
.directive('resolveevent', ['RequestService', '$location', function (RequestService, $location) {
return {
restrict: 'A',
scope: {
eventtype: '#',
eventid: '#',
index: '#'
},
controller: ['$scope', function($scope) {
$scope.remove = function(id) {
// remove the event from the events array
$scope.$parent.$parent.$parent.$parent.events.splice(id, 1);
},
$scope.config = function(config) {
_.extend($scope.$root.confirmModal, config);
},
$scope.isVisible = function() {
$scope.$apply(function() {
$scope.$root.confirmModal.isVisible = true;
});
}
}],
link: function( $scope, element, attrs ) {
var config = {
content: 'Are you sure you wish to resolve this event?',
primary: {
action: function() {
var config = {
url: '/Events/' + $scope.eventid,
method: 'PUT',
data: {
event_status: 'resolved'
},
cache: false
}
/* Update event with resolved status */
RequestService.makeApiRequest(config).success(function(response) {
$scope.$root.confirmModal.isVisible = false;
$scope.remove($scope.index);
});
},
content: 'Resolve Event'
}
}
element.on('click', function() {
if (!$scope.$root.confirmModal.isVisible) {
$scope.config(config);
$scope.isVisible();
}
});
}
}
}]);
And then I use a button on the view where my ng-repeat is found which is able to trigger the modal:
eventlist.html
<li ng-repeat="event in events">
<p>Event: {{ event.number }}</p>
<p>Group: {{ event.group_name }}</p>
<p>Record Date: {{ event.event_date | moment: 'MM/DD/YYYY h:mm A' }}</p>
<button resolveevent index="{{$index}}" eventid="{{ event.number }}" class="btn btn-default">Resolve</button>
</li>
This is what I've got, and it is working, however it seems like overkill, inefficient, and a nightmare to maintain. Can anyone chime in on a way to improve this? I appreciate any help, thanks in advance.

You can have a look at the bootstrap-ui project : http://angular-ui.github.io/bootstrap/
If you're using Bootstrap 3, be careful about the templates, and use the version without them. You can download bootstrap3 compliant templates here : https://github.com/angular-ui/bootstrap/tree/bootstrap3_bis2_modalPatch

A simple directive to confirm:
/**
* A generic confirmation for risky actions.
* Usage: Add attributes: ng-really-message="Really?" ng-really-click="takeAction()" function
*/
angular.module('app').directive('ngReallyClick', [function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngReallyMessage;
if (message && confirm(message)) {
scope.$apply(attrs.ngReallyClick);
}
});
}
}
}]);

My method might not be according to best practises, but I usually end up creating dedicated service that both has access to modal's scope and manipulates dom. Think of it as self injecting directive.
Here's the modal's container html (uses bootstrap's styling):
<div class="modal-backdrop"></div>
<div class="modal fade">
<div class="modal-dialog" ng-style="{width: width}">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" ng-click="close()" aria-hidden="true">×</button>
<h4 class="modal-title">{{title}}</h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button ng-repeat="(name, callback) in buttons" type="button" ng-click="callback()">{{name}}</button>
</div>
</div>
</div>
</div>
Then there's pseudo code of the DialogService:
.service('DialogService', function($compile, $http, $rootScope) {
this.open = function(options) {
//options contain various properties
//e.g. title, width, template or templateUrl, button map with callbacks
loadModalContainer()
.then(loadModalBody)
.then(init);
function init() {
modal = $('body').append(containerHtml).find('.modal');
modal.append(bodyHtml);
scope = (options.scope || $rootScope).$new();
if (options.controller) $controller(options.controller, {$scope: scope});
$compile(modal)(scope);
listenForEscKey();
}
function close() {
//clean up event listeners
//
if (options.onClose) options.onClose();
scope.$destroy();
$('body').find('.modal,.modal-backdrop').remove();
}
}
});
Of course, because of the async nature of the service, you have to implement some auto-close logic if second modal pops-up. From there is really easy, you can define concrete dialogs as separate services to abstract away the details:
.service('TermsModal', function(DialogService) {
this.open = function(acceptCallback, declineCallback, scope) {
DialogService.open({
templateUrl: '',
width: '',
buttons: {
accept: acceptCallback,
decline: declineCallback
},
scope: scope
});
}
})
Then from any controller you can open modal with an one-liner: TermsModal.open(acceptCallback, declineCallback, $scope)
There are several issues. First of all, it would be great to use transclusion, since now modal's child scope is littered with title, buttons, width properties.
Another thing is that I pass around modal body's width, but that's just my laziness (I cannot style bootstraps modal body width properly since it's hardcoded).
Also, I pass around local scopes from controllers because very often modal's body content is in one or another way related to the controller that invokes the modal. If, say, we have ItemController with item as scope property and we have an edit button to edit item's value in a modal, the child scope has to know about the model it's dealing with. So either it's passing around scope or passing needed values directly in options. I prefer scope because that gives more flexibility and with child scope intialization it is really hard to mess up orginal model.
All in all, the power and flexibility this set-up gives justifies the fact that service is messing a bit with the DOM. Your rootScope becomes free of global state (the service manages its own state without giving details to the outside world) and your main template is free of modal partials/directives/whatever that may or may not be used.

I have created a small confirmation directive which, opens a modal and executes the code you want, if the modal is confirmed:
app.html
<button type="button" class="btn btn-default"
nait-confirm-click
confirm="Do you really want to remove this record?"
confirm-if="user.disabled == true"
do="remove(user)">
Remove
</button>
script.js
angular
.module('xyz', ['ui.bootstrap'])
.directive('naitConfirmClick', function($modal, $parse) {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
if (!attrs.do) {
return;
}
// register the confirmation event
var confirmButtonText = attrs.confirmButtonText ? attrs.confirmButtonText : 'OK';
var cancelButtonText = attrs.cancelButtonText ? attrs.cancelButtonText : 'Cancel';
element.click(function() {
// action that should be executed if user confirms
var doThis = $parse(attrs.do);
// condition for confirmation
if (attrs.confirmIf) {
var confirmationCondition = $parse(attrs.confirmIf);
if (!confirmationCondition(scope)) {
// if no confirmation is needed, we can execute the action and leave
doThis(scope);
scope.$apply();
return;
}
}
$modal
.open({
template: '<div class="modal-body">' + attrs.confirm + '</div>'
+ '<div class="modal-footer">'
+ '<button type="button" class="btn btn-default btn-naitsirch-confirm pull-right" ng-click="$close(\'ok\')">' + confirmButtonText + '</button>'
+ '<button type="button" class="btn btn-default btn-naitsirch-cancel pull-right" ng-click="$dismiss(\'cancel\')">' + cancelButtonText + '</button>'
+ '</div>'
})
.result.then(function() {
doThis(scope);
scope.$apply()
})
;
});
}
};
})
;
Now, if you click on the button with the nait-confirm-click it opens a modal with two buttons and the text you have passed by the confirm attribute. If you click the cancel button, nothing will happen. If you confirm by clicking "OK", the expression, which you have passed by the do attribute, will be executed.
If you pass an expression in the optional confirm-if attribute, the modal will only be opened if the expression is true. If the expression is false, the action will be executed without asking.
I hope this snippet will help someone ;)

Related

Retrofitting "alert()" on delete buttons

I have an angular application which contains several delete buttons somewhat like this:
<button class="btn btn-default" ng-click="delete($index)">x</button>
Now as we are nearing production deployment and I want delete buttons to behave nicely and "alert()" the user before actually deleting the object.
How can I retro-fit this feature in all the delete buttons through a directive. I would like to have a directive called "ask":
<button ask class="btn btn-default btn-xs" ng-click="delete($index)">x</button>
which I can use to affect the behaviour of any button.
Somehow I am not able to think through this
app.directive("ask", function() {
return function(scope, elems, attrs) {
// what to do here:
// 1. shall I change the elems.attr("ng-click")
// 2. or scope.attrs("ngClick")
????
}
});
Please guide me with the best practice and some code samples. Also note that the ng-click callback of all the delete buttons are different and the application already makes extensive use of isolate scope and child scope directives.
Try this implementation
angular
.module('test', [])
.directive('ask', function() {
return {
restrict: 'A',
scope: {ask: '#', onAsk: '&'},
link: function(scope, elem, attrs) {
elem.on('click', function(e) {
if(confirm(attrs.ask)) {
scope.onAsk();
scope.$apply();
}
});
}
};
})
.controller('ItemsCtrl', function($scope) {
$scope.items = [1,2,3,4,5];
$scope["delete"] = function(index) {
$scope.items.splice(index, 1);
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ItemsCtrl">
<ul><li ng-repeat="item in items track by $index">{{item}}
<button ask="Are you sure you want to delete item?" on-ask="delete($index)">x</button>
</li>
</ul>
</div>
You can build a directive for this and in template of directive have button element and the ng-click event.
Pass index and delete function as inputs to this directive.
app.directive("deleteFruit", function(){
return{
// You can also pass one more binding if you want to have a specific alert message
scope:{
index: '#', delete:'&'
},
template:'<button class="btn btn-default" ng-click="deleteFruit()">x</button>',
link: function(scope){
scope.deleteFruit = function(){
// When user clicks, first call the alert function then use the promise returned by dialog and check whether user wants to delete or not
//modal.dialog().then(function(userSelectedOption){
// If user wants to delete then go a head and call delete function on main controller
// if(userSelectedOption){
// scope.delete(scope.index);
// }
//})
scope.delete(scope.index);
}
}
}
})
MainCtrl HTML
<delete-fruit delete="delete(index)" index={{$index}}></delete-fruit>
Plunker.

$ionicSlideBoxDelegate not scrolling and not updating index

I have a slide box inside a modal.
The slide box used to be inside the modal template, with the relevant functions inside a controller.
I realised that it was better to move it inside a directive, and move all the functions inside the directive's link function.
So, here's what I did:
The modal template calls only the directive: <my-directive></my-directive> and nothing else.
The directive code is the following:
angular.module('feedback', []).directive('myDirective', [
'$ionicSlideBoxDelegate', '$rootScope', 'Info', function($ionicSlideBoxDelegate, $rootScope, Info) {
var linkFunction;
linkFunction = function(scope) {
scope.data = {
timestamp: new Date,
details: {
app: Info.getAppInfo(),
device: Info.getDeviceInfo()
},
user: $rootScope.currentUser,
message: ''
};
scope.slideChanged = function(index) {
scope.slideIndex = index;
};
scope.disableSwipe = function() {
$ionicSlideBoxDelegate.enableSlide(true);
};
scope.slideTo = function(index) {
console.log("Got here...");
console.log("Index is: ", index);
$ionicSlideBoxDelegate.slide(index);
};
};
return {
restrict: 'E',
replace: false,
templateUrl: './directives/feedback/feedback.tpl.html',
link: linkFunction
};
}
]);
and the template is the following:
<div>
<ion-slide-box ng-init="disableSwipe()" on-slide-changed="slideChanged(index)" class="feedback-slider" show-pager="false">
<ion-slide>
<ion-item class="item-icon item-icon-right" ng-click="slideTo(1)">
</ion-slide>
<ion-slide>
<button class="button button-positive button-clear no-animation"
ng-click="slideTo(0)">Back</button>
</ion-slide>
</ion-slide-box>
</div>
This is a trip down version of the template, but it should be enough to explain what it's going on.
basically, none of the functions are called. I mean, they are called as the consoles are printed and are displaying the expected values, but it seams like everything related to $ionicSlideBoxDelegate are not fired, and I really don't understand why.
Any help?
Thanks
So,
After a lot of research and trying to debug the ionic function, I managed to solve this using the ionic forum at this link:
https://github.com/driftyco/ionic/issues/1865
Seams like the problem is when you have multiple sliders on you app, and it is a known issue within the ionic library.
I change my code like this:
$ionicSlideBoxDelegate.$getByHandle('feedbackDirective')._instances[0].enableSlide(false);
and added an handler in the html template as well:
<ion-slide-box ng-init="disableSwipe()"
on-slide-changed="slideChanged(index)"
class="hg-feedback-slider"
show-pager="false"
delegate-handle="feedbackDirective">
...
and now it's working properly again.

Can ng-show directive be used with a delay

I have a spinner this is shown with ng-show="loading>0"
Is there a way I can display this spinner with a delay (say 1 second)?
I can't use a timeout because with multiple requests de loading counter will get out of sync.
What I need is a delay on the ng-show via css transition or similar
My suspicion is that you are looking for a general purpose spinner that includes a delay. The standard, show after 200ms or something like that.
This is a perfect candidate for a directive, and actually pretty easy to accomplish.
I know this is a long code example, but the primary piece is the directive. It's pretty simple.
Listen to a few scope variables and shows after some configurable delay. If the operation takes longer than the delay, it will just get canceled and never show up.
(function() {
'use strict';
function SpinnerDirective($timeout) {
return {
restrict: 'E',
template: '<i class="fa fa-cog fa-spin"></i>',
scope: {
show: '=',
delay: '#'
},
link: function(scope, elem, attrs) {
var showTimer;
//This is where all the magic happens!
// Whenever the scope variable updates we simply
// show if it evaluates to 'true' and hide if 'false'
scope.$watch('show', function(newVal){
newVal ? showSpinner() : hideSpinner();
});
function showSpinner() {
//If showing is already in progress just wait
if (showTimer) return;
//Set up a timeout based on our configured delay to show
// the element (our spinner)
showTimer = $timeout(showElement.bind(this, true), getDelay());
}
function hideSpinner() {
//This is important. If the timer is in progress
// we need to cancel it to ensure everything stays
// in sync.
if (showTimer) {
$timeout.cancel(showTimer);
}
showTimer = null;
showElement(false);
}
function showElement(show) {
show ? elem.css({display:''}) : elem.css({display:'none'});
}
function getDelay() {
var delay = parseInt(scope.delay);
return angular.isNumber(delay) ? delay : 200;
}
}
};
}
function FakeService($timeout) {
var svc = this,
numCalls = 0;
svc.fakeCall = function(delay) {
numCalls += 1;
return $timeout(function() {
return {
callNumber: numCalls
};
}, delay || 50);
};
}
function MainCtrl(fakeService) {
var vm = this;
vm.makeCall = function(delay) {
vm.isBusy = true;
fakeService.fakeCall(delay)
.then(function(result) {
vm.result = result;
}).finally(function() {
vm.isBusy = false;
});
}
}
angular.module('spinner', [])
.service('fakeService', FakeService)
.controller('mainCtrl', MainCtrl)
.directive('spinner', SpinnerDirective);
}());
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<div class="container" ng-app="spinner">
<div class="row" ng-controller="mainCtrl as ctrl">
<div class="col-sm-12">
<h2>{{ctrl.result | json}}
<spinner show="ctrl.isBusy" delay="200"></spinner>
</h2>
<button type="button"
class="btn btn-primary"
ng-click="ctrl.makeCall(2000)"
ng-disabled="ctrl.isBusy">Slow Call
</button>
<button type="button"
class="btn btn-default"
ng-click="ctrl.makeCall()"
ng-disabled="ctrl.isBusy">Fast Call
</button>
</div>
</div>
</div>
Here's a simpler approach that worked for my needs. Depending on what your action is, you would tie function setDelay() to the element. For example, in my case I tied setDelay() to a select input.
Trigger HTML:
<select class="first-option"
ng-change="setDelay()"
ng-options="o.label for o in download.options"
ng-model="optionModel" required>
</select>
In your controller, add a simple function setDelay that will change the flag $scope.delay:
$scope.setDelay = function(){
$scope.delay = true;
$timeout(function(){
$scope.delay = false;
}, 200);
};
Then, you can simply use $scope.delay as a flag in ng-show:
<div class="loading-div" ng-show="delay">
<img src="loading_spinner.gif">
</div>
And show content after done loading:
<div ng-show="!delay">
Content is loaded.
</div>
Now, every time the user selects a new value in the dropdown menu, it will trigger$scope.delay to be set totrue causing the spinner to show, and when it reaches 200, it will be set to false causing the spinner to hide.
I think a pure CSS solution is the best way to do it.
Here is a plunker showing how to do it. Using ng-animate classes for transition and applying a transition delay with a transition of 10ms (0s transition is not working with css).
Relevant part of the code :
.your-element-class.ng-hide {
opacity: 0;
}
.your-element-class.ng-hide-add,
.your-element-class.ng-hide-remove {
transition: all linear 0.01s 1s;
}
The only reason to use a custom directive for it would be using this tons of times in your code with different delays value. A custom directive allow more flexibility with the delay timing.

Creating a Reusable Component in AngularJS

I am new to Stackoverflow. I'm also new to AngularJS. I apologize if I'm not using this correctly. Still, I'm trying to create a UI control with AngularJS. My UI control will look like this:
+---------------------+
| |
+---------------------+
Yup. A textbox, which has special features. I plan on putting it on pages with buttons. My thought is as a developer, I would want to type something like this:
<ul class="list-inline" ng-controller="entryController">
<li><my-text-box data="enteredData" /></li>
<li><button class="btn btn-info" ng-click="enterClick();">Enter</button></li>
</ul>
Please note, I do not want buttons in my control. I also want the enteredData to be available on the scope associated with child controls. In other words, when enterClick is called, I want to be able to read the value via $scope.enteredData. In an attempt to create my-text-box, I've built the following directive:
myApp.directive('myTextBox', [function() {
return {
restrict:'E',
scope: {
entered: '='
},
templateUrl: '/templates/myTextBox.html'
};
}]);
myApp.controller('myTextBoxController', ['$scope', function($scope) {
$scope.doSomething = function($item) {
$scope.entered = $item.name;
// Need to somehow call to parent scope here.
};
}]);
myTextBox.html
<div ng-controller="myTextBoxController">
<input type="text" class="form-control" ng-model="query" placeholder="Please enter..." />
</div>
entryController.js
myApp.controller('entryController', ['$scope', function($scope) {
$scope.enteredData = '';
$scope.enterClick = function() {
alert($scope.enteredData);
};
});
Right now, I have two issues.
When enterClick in entryController is called, $scope.enteredData is empty.
When doSomething in myTextBoxController is called, I do not know how to communicate to entryController that something happened.
I feel like I've setup my directive correctly. I'm not sure what I'm doing wrong. Can someone please point me in the correct direction.
Three suggestions for you.
1) You really shouldn't create a directive with a template that references a controller defined elsewhere. It makes the directive impossible to test in isolation and is generally unclear. If you need to pass data into a directive from a parent scope use the isolate scope object on your directive to bind to that data (Note how the directive template doesn't have a controller) http://jsfiddle.net/p4ztunko/
myApp.directive('myTextBox', [function () {
return {
restrict: 'E',
scope: {
data: '='
},
template: '<input type="text" class="form-control" ng-model="data" placeholder="Please enter..." />'
};
}]);
myApp.controller('entryController', ['$scope', function ($scope) {
$scope.enteredData = 'Stuff';
$scope.enterClick = function () {
alert($scope.enteredData);
};
}]);
<div>
<ul class="list-inline" ng-controller="entryController">
<li>{{enteredData}}
<my-text-box data="enteredData" />
</li>
<li>
<button class="btn btn-info" ng-click="enterClick();">Enter</button>
</li>
</ul>
</div>
2) Don't obfuscate HTML when you don't need to. One of the goals of angular is to make the markup more readable, not replace standard elements with random custom elements. E.g. If you want to watch the value of the input and take action depending on what it is you could do that in the linking function (Note: still not referencing an external controller) http://jsfiddle.net/Lkz8c5jo/
myApp.directive('myTextBox', function () {
return {
restrict: 'A',
link: function(scope, element, attrs){
function doSomething (val) {
alert('you typed ' + val);
}
scope.$watch(attrs.ngModel, function (val) {
if(val == 'hello'){
doSomething(val);
}
});
}
};
});
myApp.controller('entryController', ['$scope', function ($scope) {
$scope.enteredData = 'Stuff';
$scope.enterClick = function (data) {
alert('You clicked ' + data);
};
}]);
<div>
<ul class="list-inline" ng-controller="entryController">
<li>{{enteredData}}
<input type="text" ng-model="enteredData" my-text-box />
</li>
<li>
<button class="btn btn-info" ng-click="enterClick(enteredData)">Enter</button>
</li>
</ul>
</div>
3) Pass data into controller functions from the UI instead of referencing the $scope object in the function like in ng-click="enterClick(enteredData)" It makes testing easier because you remove the $scope dependency from that method
$scope.enteredData is empty because you're not using the correct scope. The $scope in entryController is not the same $scope as in myTextBoxController. You need to specify one of these controllers on your directive, and then use that to reference the proper scope.
It seems like you should move the enterClick and corresponding button into your directive template. Then move the enter click into your text box controller and you will be able to reference $scope.enteredData.
You can notify a parent scope of a change by using $emit. (This is in reference to your comment "// Need to somehow call to parent scope here.")
Furthermore, you may have an issue of not using the proper variable. In myTextBox directive, you declare $scope.entered, yet you are effectively setting $scope.data equal to the value of enteredData in the html.

Call a function in a angular-controller from outside of the controller?

I have a lightbox-dierective and controller that looks like this:
directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div class="ng-modal" ng-show="show"><div class="ng-modal-overlay" ng-click="hideModal()"></div><div class="ng-modal-dialog" ng-style="dialogStyle"><div class="ng-modal-dialog-content" ng-transclude><div class="ng-modal-close" ng-click="hideModal()">X</div></div></div></div>'
};
}).controller('Lightbox', function($scope) {
$scope.modalShown = false;
$scope.toggleModal = function() {
$scope.modalShown = !$scope.modalShown;
};
});
Here is the desierd html, what I need is to open the secon ligthbox from withing the first one:
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>One lightbox <span ng-mousedown='toggleModal()'>Open lightbox two</span></h2>
</modal-dialog>
</div>
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>another lightbox</h2>
</modal-dialog>
</div>
For most cases it works great! I use it in several occations throughout the site, with diffrent lightboxes and different content.
I have now come across a case, where I need to call one of the lightboxes from outside of the controller. Can this be achieved and in that case how do I reference the right lightbox?
I'd extend that setting to an object
var modalSet = {
shown: false,
toggle: function(){ modalSet.shown = !modalSet.shown }
}
Then put it on your main controller (the one with ngApp attribute) and have your entire scope modaleble.
Also, directives do have a controller option, but since only one modal is gonna show up at any given time, you might not want to re-create a controller for every new instance.
Upon re-reading your question: Where is it exactly -> "outside of the controller"?

Resources