I am use to working in Angular and now I am on AngularJS ( The otherway round)
I've a directive:
<li ng-mouseover="vm.setCurrentEditedTile(item.id)">
<panel-buttons-directive ></panel-buttons-directive>
</li>
My panel-buttons-directive has a controller called ButtonsController.
What I would like when user hovers on top of <li> element, it run a function that is inside the child controller. So that I have a separate "Module" where I have buttons HTML in the directive and function in the controller and from the parent I can call the function.
Link: https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
One approach is to have the directive publish an API when initialized:
<fieldset ng-mouseover="pbdAPI.setCurrentEditedTile(item.id)">
Mouseover Me
</fieldset>
<panel-buttons-directive on-init="pbdAPI=$API">
</panel-buttons-directive>
app.directive("panelButtonsDirective", function() {
return {
scope: { onInit: '&' },
bindToController: true,
controller: ButtonsController,
controllerAs: '$ctrl',
template: `<h3>Panel Buttons Component</h3>
<p>Current edited tile = {{$ctrl.id}}</p>
`,
};
function ButtonsController() {
var $ctrl = this;
var API = { setCurrentEditedTile: setCurrentEditedTile };
this.$onInit = function() {
this.onInit({$API: API});
};
function setCurrentEditedTile(id) {
$ctrl.id = id;
}
}
})
The directive in the above example uses expression & binding to publish its API when initialized.
The DEMO
angular.module("app",[])
.directive("panelButtonsDirective", function() {
return {
scope: { onInit: '&' },
bindToController: true,
controller: ButtonsController,
controllerAs: '$ctrl',
template: `<h3>Panel Buttons Component</h3>
<p>Current edited tile = {{$ctrl.id}}</p>
`,
};
function ButtonsController() {
var $ctrl = this;
var API = { setCurrentEditedTile: setCurrentEditedTile };
this.$onInit = function() {
this.onInit({$API: API});
};
function setCurrentEditedTile(id) {
$ctrl.id = id;
}
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<h3>Mouseover Component DEMO</h3>
<p><input ng-model="item.id" ng-init="item.id='tile0'"/></p>
<fieldset ng-mouseover="pbdAPI.setCurrentEditedTile(item.id)">
Mouseover Me
</fieldset>
<panel-buttons-directive on-init="pbdAPI=$API">
</panel-buttons-directive>
</body>
I created a custom directive to display a ui.bootstrap modal and I cannot populate the dialog with the values I want. I don't understand why is it not working. I checked other questions and posts but I could not find and example suitable for my case.
I've created a plunker with a similar version of my real code to show the problem.
In the example, I am using a button to launch the modal but in my application I am doing it by code when some conditions are satisfied.
This is the link http://plnkr.co/edit/7CCV7uHHj7SrWbsp1PRJ
Basically I have a directive:
var app = angular.module('app', ['ui.bootstrap']);
app.directive('revModal', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
id: "#",
title: "#",
size: "#",
bodyMessage: '#',
acceptButton: '#',
dismissButton: '#'
},
controller: function($scope, $modal) {
$scope.title = $scope.title ? $scope.title : "Alert";
$scope.size = $scope.size ? $scope.size : "sm";
$scope.acceptButton = $scope.acceptButton ? $scope.acceptButton : "OK";
$scope.dismissButton = $scope.dismissButton ? $scope.dismissButton : "Cancel";
$scope.openModal = function() {
var modalInstance = $modal.open({
templateUrl: 'modalAlert.html',
animation: true,
backdrop: true,
windowClass: 'app-modal-window',
size: $scope.size,
resolve: {
acceptButton: $scope.acceptButton,
dismissButton: $scope.dismissButton
}
});
modalInstance.result.then(function() {}, function() {
console.log('Modal dismissed at: ' + new Date());
});
};
},
link: function($scope, element) {
element[0].open = function() {
return $scope.openModal();
}
}
}
});
In the directive I've also tried to resolve the values using function but it didn't work either.
resolve:{
dismissButton: function(){return $scope.dismissButton}
...
}
The directive template is the following, and my problem is that the values inside curly braces are not replaced. The logic to link the ng-click functions was not done yet.
<div class="modal-header">
<h3 class="modal-title">{{title}}</h3>
</div>
<div class="modal-body">
{{bodyMessage}}
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="acceptModal()">{{acceptButton}}</button>
<button class="btn btn-warning" ng-click="dismissModal()">{{dismissButton}}</button>
</div>
To use this directive I just insert the following tag in the html:
<div ng-controller="TimeRangeModalController">
<rev-modal id="timeRangeModal" title="test" accept-button="acepto" dismiss-button="calcelo" body-message="cuerpo"></rev-modal>
</div>
The controller to open the modal is:
var app = angular.module('app');
app.controller('TimeRangeModalController', function($scope, $document) {
$scope.open= function(modalId){
if (undefined !== modalId) {
var modal = $document[0].getElementById(modalId);
if (null !== modal) {
modal.open();
} else {
console.error('DOM element with id ' + 'modalId' + 'does not exist');
}
}
}
});
Any suggestion on how should I resolve the values to populate the modal?
Thanks and regards, Daniela.
Finally I've discovered the issue, I was missing to set the scope in the modalInstance.
The final open modal function will be:
$scope.openModal = function() {
var modalInstance = $modal.open({
templateUrl: 'modalAlert.html',
animation: true,
backdrop: true,
windowClass: 'app-modal-window',
size: $scope.size,
scope:$scope
});
Note also that the resolve clause is not need it.
I've updated the Plunkr http://plnkr.co/edit/7CCV7uHHj7SrWbsp1PRJ
I wrote a bootstrap3 modal for my project before learning angular which was triggered using a button. Now I want to toggle it using angular variable. Taking reference from this article, i have simplified my code as follows:
My modal html:
<div id="registered" tabindex="-1" modal-toggle role="dialog" aria-labelledby="registeredModal" aria-hidden="true" class="modal fade">
<div class="modal-dialog modal-lg">
...
...
</div>
</div>
modalToggle directive:
app.directive("modalToggle",function(){
return function(scope, element, attrs){
scope.$watch(scope.loaded.showModal, function(value) {
if (value) element.modal('show');
else element.modal('hide');
});
}
})
scope.loaded.showModal is set on following controller, which is called after i hit tab on my form input:
app.controller('validatectrl',[ '$http', '$scope', '$upload', $location, function($http, $scope, $location){
unique: function(param){
$scope.loading={};
$scope.loaded={};
$scope.loading[param.field]=true;
var currentPath=$location.path();
var webCall = $http({
method: 'POST',
url: currentPath+'/validation',
async : true,
headers: {
'Content-Type': 'application/json'
},
timeout:10000,
data: param});
webCall.then(handleSuccess,handleError);
function handleSuccess(response) {
...
if(response.data.status===1) {
...
}
else if(response.data.status===0){
$scope.loaded["showModal"]=true;
alert("duplicate item");
}
}
function handleError(response){
$scope.loaded[param.field]={};
$scope.loading[param.field]=false;
$scope.loaded[param.field]["error"]=true;
$scope.loaded[param.field]["Message"]="Cannot fetch data from server";
};
}
Everything else works except for the modal does not pop up.
Can you try to change from
scope.$watch(scope.loaded.showModal, function(value) {
if (value) element.modal('show');
else element.modal('hide');
});
to
scope.$watch(function(){ return scope.loaded.showModal; }, function(value) {
if (value) element.modal('show');
else element.modal('hide');
});
Hope this help.
Bootstrap 3 provides Bootstrap: event messages: success, info, warning, danger.
However sometimes the view doesn't have enough space to show up the event message.
Is there easy way to wrap event with modal in Angular?
This is a template I started to play with
I'll answer on my own question.
Simple way
The flow is pretty simple and straightforward. We don't reinvent the wheel here.
We don't need nor header neither footer:
Dialog template HTML:
<div class="modal-body" style="padding:0px">
<div class="alert alert-{{data.mode}}" style="margin-bottom:0px">
<button type="button" class="close" data-ng-click="close()" >
<span class="glyphicon glyphicon-remove-circle"></span>
</button>
<strong>{{data.boldTextTitle}}</strong> {{data.textAlert}}
</div>
</div>
We even don't need to use ng-class:
class="alert-{{data.mode}}"
where mode might be: success, info, warning, danger
Modal Instance Controller:
var ModalInstanceCtrl = function ($scope, $modalInstance, data) {
$scope.data = data;
$scope.close = function(/*result*/){
$modalInstance.close($scope.data);
};
};
And this is modal configuration and content:
$scope.data = {
boldTextTitle: "Done",
textAlert : "Some content",
mode : 'info'
}
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
backdrop: true,
keyboard: true,
backdropClick: true,
size: 'lg',
resolve: {
data: function () {
return $scope.data;
}
}
});
Demo Plunker
Directive way
Demo 2 Plunker
We can put all above written code into directive for better maintenance:
HTML
<button class="btn btn-success" ng-click="open()" >success
<my-alert
bold-text-title="Done"
text-alert="Some content"
mode="success"
></my-alert>
</button>
Directive
.directive('myAlert', function($modal,$log) {
return {
restrict: 'E',
scope: {
mode: '#',
boldTextTitle: '#',
textAlert : '#'
},
link: function(scope, elm, attrs) {
scope.data= {
mode:scope.mode,
boldTextTitle:scope.boldTextTitle,
textAlert:scope.textAlert
}
var ModalInstanceCtrl = function ($scope, $modalInstance, data) {
console.log(data);
scope.data= {
mode:scope.mode || 'info',
boldTextTitle:scope.boldTextTitle || 'title',
textAlert:scope.textAlert || 'text'
}
};
elm.parent().bind("click", function(e){
scope.open();
});
scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
backdrop: true,
keyboard: true,
backdropClick: true,
size: 'lg',
resolve: {
data: function () {
return scope.data;
}
}
});
modalInstance.result.then(function (selectedItem) {
scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
}
}
};
})
Hope it will save time to someone.
I've made a service and controller which depends of eachother:
.service('AlertService', function($uibModal){
/*
headerText - presents text in header
bodyText - presents text in body
buttonText - presents text in button. On its click if method parameters is not passed, modal will be closed.
In situation that the method parameters is passed, on its click, method will be called. For situations
like that, there is parameter buttonText2 which will be used as cancel modal functionality.
method - presents passed function which will be called on confirmation
buttonText2 - presents text in button for cancel
*/
var alert = function(headerText, bodyText, buttonText, method, buttonText2){
method = method || function(){};
buttonText2 = buttonText2 || '';
$uibModal.open({
animation: true,
templateUrl: '/static/angular_templates/alert-modal.html',
controller: 'AlertModalInstanceCtrl',
size: 'md',
resolve: {
headerText: function () {
return headerText;
},
bodyText: function () {
return bodyText;
},
buttonText: function () {
return buttonText;
},
method: function () {
return method;
},
buttonText2: function () {
return buttonText2;
}
}
});
};
return{
alert: alert
};
})
.controller('AlertModalInstanceCtrl', function ($scope, $uibModalInstance, headerText, bodyText, buttonText, method, buttonText2) {
$scope.headerText = headerText;
$scope.bodyText = bodyText;
$scope.buttonText = buttonText;
$scope.method = method;
$scope.buttonText2 = buttonText2;
$scope.ok = function () {
$scope.method();
$uibModalInstance.dismiss('cancel');
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
});
and html file:
<!--Modal used for alerts in AlertService-->
<div class="modal-header">
<h3 class="modal-title">{[{ headerText }]}</h3>
</div>
<div class="modal-body">
<p>{[{ bodyText }]}</p>
</div>
<div class="modal-footer">
<button class="btn btn-default" ng-click="cancel()" ng-if="buttonText2">{[{ buttonText2 }]}</button>
<button class="btn btn-primary" ng-click="ok()">{[{ buttonText }]}</button>
</div>
Now, depending for what type you want to use it, you have a few options:
-If you pass headerText, bodyText and buttonText, it will behave like a classic alert modal
AlertService.alert('Some header', 'Some message', 'Text button');
-If you pass headerText, bodyText, buttonText and method, it will behave like a classic alert modal but with the function which you can pass and later handle in the controller
AlertService.alert('Are you sure?', 'Are you sure you want to create this round', 'Ok', $scope.createRound);
$scope.createRound = function(){
//do something
}
-And the last one. If you pass all the parameters, it will act like the previous one, just with the possibility to cancel and close modal.
AlertService.alert('Are you sure?', 'Are you sure you want to create this round', 'Ok', $scope.createRound, 'Cancel');
$scope.createRound = function(){
//do something
}
Of course, if you want to use this, you'll have to inject angular ui bootstrap. I wasted a lot of time to develop this, but it worth. It was annoying to create every time a new controller, new template and all the other things.
From the controller then you can easily use it, just inject it first.
Thanks for answering your own question, it was helpful.
Here's a version as a service you can wire in and fire off from any controller without needing to include directive mark-up.
It uses the latest angular UI Bootstrap paradigm for modals.
It has some convenience methods (info, error, warn, success).
It fires off an event when closed with the data as an event argument in case you need to know that.
Enjoy!
angular.module('modal.alert.service', [], function ($provide) {
'use strict';
$provide.factory('ModalAlertService', ['$rootScope', '$uibModal',
function ($rootScope, $uibModal) {
var factory = {
alert: function(mode, title, text) {
var modalData = {
mode : mode,
title : title,
text : text
};
var modalInstance = $uibModal.open({
template: '<div class="modal-body" style="padding:0px">' +
'<div class="alert alert-{{data.mode}}" style="margin-bottom:0px">' +
'<button type="button" class="close" data-ng-click="close()" >' +
'<span class="glyphicon glyphicon-remove-circle"></span>' +
'</button><strong>{{data.title}}</strong>: {{data.text}}</div></div>',
controller : 'ModalAlertController',
backdrop : true,
keyboard : true,
backdropClick : true,
size : 'lg',
resolve : {
data : function() {
return modalData;
}
}
});
modalInstance.result.then(function(data) {
$rootScope.$broadcast('modal-alert-closed', { 'data' : data });
});
},
info: function(title, text) {
factory.alert('info', title, text);
},
error: function(title, text) {
factory.alert('danger', title, text);
},
warn: function(title, text) {
factory.alert('warning', title, text);
},
success: function(title, text) {
factory.alert('success', title, text);
}
};
return factory;
}]);
}).controller('ModalAlertController', function ($scope, $uibModalInstance, data) {
$scope.data = data;
$scope.close = function() {
$uibModalInstance.close($scope.data);
};
});
I'm running into an issue where, through a directive, I am trying to set a property on the scope of a controller. The issue is that, for some reason, the scope on the directive seems to be isolating itself, but only in this instance. It works fine in other places of the application. So when I attempt to use $scope.files in my controller, it's coming back as undefined.
Controller:
app.controller('newProjectModalController', function($scope, $modalInstance, $http, $location, account, $http){
$scope.account = account.data;
$scope.project = {
name: '',
client: '',
users: [],
image: '/assets/images/add-project-photo.jpg'
};
$scope.cancel = function(){
$modalInstance.dismiss('cancel');
};
$scope.updateImage = function(item){
var filereader = new FileReader();
filereader.readAsDataURL($scope.files.item(0));
filereader.onload = function(event){
$scope.project.image = event.target.result;
}
}
$scope.submit = function(){
var formData = new FormData();
formData.append('file', $scope.files.item(0));
$http.post($scope.api_url + '/Project', $scope.project)
.success(function(data, status, headers, config){
$modalInstance.close();
$location.path('/project/' + data.id);
});
};
});
Directive:
app.directive('fileUpload', function($parse){
return {
restrict: 'A',
transclude: true,
template: '<input type="file" name="file" multiple style="height:100%;width:100%;display:inline-block;opacity:0.0;position:absolute;top:0;left:0" />',
link: function(scope, element, attrs){
var onFileChange = $parse(attrs.fileUpload);
var file = element.children('input');
file.on('change', function(){
scope.files = file[0].files;
onFileChange(scope);
})
}
}
});
Template:
<div class="row fieldset not" id="photo">
<div class="col-sm-8 col-sm-offset-2">
<h2 class="tight">Add project photo</h2>
<div class="add-project-photo" file-upload="updateImage()" style="background-image: url({{project.image}})"></div>
<span class="note">300px <i>by</i> 120px</span>
</div>
</div>
The template is only partial... the file itself is rather large
Edit: I should mention that the modal is being built using UI-Bootstrap
Since you are not transcluding anything, you can remove transclude: true from your fileUpload directive. You can also set scope: false to tell the directive to use parent (controller) scope.
app.directive('fileUpload', function($parse){
return {
restrict: 'A',
scope: false,
template: '<input type="file" name="file" multiple style="height:100%;width:100%;display:inline-block;opacity:0.0;position:absolute;top:0;left:0" />',
link: function(scope, element, attrs){
var onFileChange = $parse(attrs.fileUpload);
var file = element.children('input');
file.on('change', function(){
scope.files = file[0].files;
onFileChange(scope);
})
}
}
});