I'm building application on AngularJS and I'm using ui-modal, but instead of creating separete views for edit and create (which are identical) i want two instances of modal operating on the same view. This is what I did
I have one main controller which has functions for create and edit, both of them opens their own instance of modal :
(function()
{
'use strict';
angular.module( 'app.projects' )
.controller('ProjectsController', ProjectsController );
ProjectsController.$inject = ['$scope', '$modal'];
function ProjectsController( $scope, $modal )
{
$scope.edit = function( projectId )
{
var modalInstance = $modal.open(
{
templateUrl: 'modules/projects/view/modal/project.html',
controller: 'EditProjectModalInstController',
size: 'lg',
resolve: {
projectId: function(){
return projectId;
}
}
});
};
$scope.create = function()
{
var modalInstance = $modal.open(
{
templateUrl: 'modules/projects/view/modal/project.html',
controller: 'CreateProjectModalInstController',
size: 'lg',
});
};
};
})();
Here are those modal instances controllers:
EditProjectModalInstanceController
(function()
{
'use strict';
angular.module( 'app.projects' )
.controller( 'EditProjectModalInstController', EditProjectModalInstController );
EditProjectModalInstController.$inject = ['ProjectsService', '$scope', '$modalInstance', 'projectId'];
function EditProjectModalInstController(projectsService, $scope, $modalInstance, projectId)
{
$scope.project = function(){return projectsService.project;};
$scope.statuses = [
{name: 'Active', value: 0},
{name: 'Finished', value: 1}
];
$scope.cancel = function () {};
$scope.save = function(){};
};
})();
and here is CreateProjectModalInstanceController.js
(function()
{
'use strict';
angular.module( 'app.projects' )
.controller( 'CreateProjectModalInstController', CreateProjectModalInstController );
CreateProjectModalInstController.$inject = ['ProjectsService', '$scope', '$modalInstance'];
function CreateProjectModalInstController( projectsService, $scope, $modalInstance )
{
$scope.project = function(){return {};};
$scope.statuses = [
{name: 'Active', value: 0},
{name: 'Finished', value: 1}
];
$scope.cancel = function(){};
$scope.save = function(){};
};
})();
So each instance controller has same function, is just that they should do different things depends on action(edit or create).
And here is the actual template:
project.html
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<label for="inputName" class="control-label col-xs-2">Name</label>
<div class="col-xs-10">
<input type="text" class="form-control" id="inputName" placeholder="Name" ng-model="project().name">
</div>
</div>
<div class="form-group">
<label for="inputName" class="control-label col-xs-2">Status</label>
<div class="col-xs-10">
<select class="form-control" ng-model="project().status">
<option ng-repeat="status in statuses" value="{{status.value}}">{{status.name}}</option>
</select>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button ng-click="save()"> Save </button>
<button ng-click="cancel()">Cancel</button>
</div>
And finally the issue, my problem here is that edit functionality is working fine, but when the create modal is opened, i can not edit field. I'm felling that I'm doing something wrong here, in sense of design principals and I would really appreciate if someone with more experience could point out correct way of doing this type of thing.
The issue you are having with not being able to edit a create form is because your $scope.project function is returning a new object every time it is called. This means almost any action you take will overwrite anything saved in that object. To remedy this just assign the empty object to $scope.project instead of using a function:
$scope.project = {};
And then reference in the template like so:
<input type="text" class="form-control" id="inputName" placeholder="Name" ng-model="project.name">
The only reason it works for editing is because the function returns the same object from the projectsService instead of creating a new one each time.
Once you have that working you can begin refactoring by DRYing up your controllers. There are several ways to approach this but I would just have different resolve options for create and edit; create would have a null project ID and edit would have the editing project ID.
Create:
$scope.create = function() {
var modalInstance = $modal.open({
templateUrl: 'modules/projects/view/modal/project.html',
controller: 'ProjectFormModalInstController',
size: 'lg',
resolve: {
projectId: null
}
});
};
Edit:
$scope.edit = function() {
var modalInstance = $modal.open({
templateUrl: 'modules/projects/view/modal/project.html',
controller: 'ProjectFormModalInstController',
size: 'lg',
resolve: {
projectId: projectId
}
});
};
This way you can use the same controller (I named it ProjectFormModalInstController but you can choose your own generic name) and the controller or the service can initialize a new project if the ID does not exist or return the project if an id is present.
function ProjectFormModalInstController( projectsService, $scope, $modalInstance, projectId ) {
if ( projectId === null ) {
$scope.project = {};
} else {
$scope.project = projectsService.project;
}
// ...some controller code
$scope.save = function(){
if ( project.id === null ) {
// create project
} else {
// update project
}
};
};
Related
I'm trying to open a modal using angular material and inject the scope of the parent, where my data is.
I am attempting to loosely follow the custom/advanced example here:
https://material.angularjs.org/latest/demo/dialog
I do not need the dialog to do anything once closed, so I've eliminated the Dialogcontroller, which seems to only handle 'cancel' and 'answer' upon close. (correct me if I'm wrong here)
The ultimate goal here is to have access to survey in the modal, which is an object in the scope of the parent.
My parent HTML has the review button:
<input type="button" value="Review" ng-click="vm.openReview($event)" />
which triggers the function:
(function () {
appModule
// parent controller
.controller('tenant.views.surveys.index', [
'$scope', 'abp.services.app.survey', '$filter', '$mdDialog',
function ($scope, surveyService, $filter, $mdDialog) {
var vm = this;
vm.openReview = function (ev) {
$mdDialog.show({
//controller: DialogController,// not needed
templateUrl: "/App/tenant/views/surveys/review_modal.html",
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose: true,
fullscreen: true
})
//.then(function (answer) {// not needed
// vm.modalStatus = 'You said the information was "' + answer + '".';
//}, function () {
// vm.modalStatus = 'You cancelled the dialog.';
//});
};
// not needed
//function DialogController($scope, $mdDialog) {
// $scope.hide = function() { $mdDialog.hide(); };
// $scope.cancel = function() { $mdDialog.cancel(); };
// $scope.answer = function(answer) { $mdDialog.hide(answer); };
//}
}
])
.
// then the modal controller
.controller('tenant.views.surveys.review', [
'$scope', 'abp.services.app.survey',
function ($scope, surveyService) {
var vmModal = this;
}
]);
})();
Now, in my modal, I have some content:
<div ng-controller="tenant.views.surveys.review as vmModal" class="md-padding dialogdemoBasicUsage" id="popupContainer" ng-cloak="">
<div ng-controller="tenant.views.surveys.review" class="md-padding dialogdemoBasicUsage" id="popupContainer" ng-cloak="">
<md-dialog aria-label="Mango (Fruit)">
<form ng-cloak>
{{vmModal.survey}}
</form>
</md-dialog>
</div>
But I think I'm missing a piece. I don't think vmModal actually contains the scope of the parent. I want access to vmModal.survey.
Do I need to / can I inject the scope directly into the modal controller?
I think I'm missing something but cannot figure what.
Basically I'm trying to pass an object to the modal like below, but instead of getting the passed object I gets null...so I think is a problem with the scope but I'm new in Angular and need some help.
Controller
app.controller("musicViewModel", function ($scope, $http, $location, $uibModal, $log) {
$scope.selected = null;
$scope.open = function (item) {
$scope.selected = item;
$log.info('Open' + $scope.selected); // get right passes object
var modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
controller: 'musicViewModel',
size: 'lg',
resolve: {
items: function () {
return $scope.selected;
}
}
});
};
$scope.toggleAnimation = function () {
$scope.animationsEnabled = !$scope.animationsEnabled;
};
});
View
<div class="row" ng-controller="musicViewModel">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li>
{{ selected }} // always gets null
</li>
</ul>
</div>
</script>
</div>
I'd suggest you to pass the scope of your own controller instead of passing same controller again, by doing that you can remove the resolve also.
var modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
scope: $scope, //passed current scope to the modal
size: 'lg'
});
Otherwise you need to create a new controller and assign that controller for modal while opening it.
When you use resolve, the map is injected into the given controller.
I recommend that u use a different controller to handle the modal functionality (separation of concerns).
I also recommend to use dependency injection to support minification of the code. Step 5 on the Angular tutorial wil explain this.
A simplified example of the modal controller would be.
(function () {
'use strict';
var app = angular.module('App');
app.controller('musicDetailController',
['$scope', '$uibModalInstance', 'items',
function ($scope, $uibModalInstance, items) {
$scope.items = items;
}]);
}());
You cannot pass an object directly.
I've tried all the solutions above, but wasn't really satisfied. I've solved the issue by writing a simple parser that enables you to pass both strings and objects directly to the modal, through the provided resolve function.
app.controller('ModalController', ['$uibModal', '$scope', function ($uibModal, $scope) {
// Initialize $modal
var $modal = this;
// Open component modal
$modal.open = function (component, size, data) {
// Init modal
var modalInstance = $uibModal.open({
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
component: component,
size: size || 'md',
resolve: parseResolve(data)
});
};
// Parse the resolve object
function parseResolve(data) {
if (typeof data === 'string') {
return {
data: function() {
return data;
}
}
}
else if (typeof data === 'object') {
var resolve = {};
angular.forEach(data, function(value, key) {
resolve[key] = function() {
return value;
}
})
return resolve;
}
}
}]);
When usings strings
Template:
<button ng-click="$modal.open('modalSomething', 'md', 'value'">
Click
</button>
Component:
bindings: {
resolve: '#'
}
When using objects
Template:
<button ng-click="$modal.open('modalSomething', 'md', {key1: value1, key2: value2})">
Click
</button>
Component:
bindings: {
resolve: '<'
}
I got the below code working:
this.OpenModal = function() {
var modalInstance = $uibModal.open({
url: "/name?parameter=" + $scope.Object.ParamValue,
templateUrl: 'views/module/page.html',
controller: myController
});
}
I am trying to automatically check a checkbox when a modal closes. I am using one controller for the modal and another controller for the main page. The code below isn't working. Do I need to use a directive in order to accomplish this, or is there another way?
HTML - Main Page:
<label>
<input type="checkbox" ng-model="agreementForm.value1"> I agree.
</label>
HTML - Modal:
<div class="modal-footer">
<button ng-click="agreementForm.cancel()" class="btn btn-warning">Cancel</button>
<button ng-click="agreementForm.agree()" ng-disabled="agreementForm.$invalid" class="btn btn-primary" >I agree</button>
</div>
Javascript for Controllers:
myApp.controller('AgreementFormCtrl', function ($location, $stateParams, $modalInstance) {
var agreementForm = this;
agreementForm.cancel = function () {
$modalInstance.dismiss('cancel');
};
agreementForm.agree = function() {
agreementForm.value1=true;
$modalInstance.close(agreementForm.selected);
});
myApp.controller('ContactFormCtrl',
function ($location, $stateParams, Contacts) {
var contactForm = this;
});
contactForm.save = function () {
Contacts.$add(contactForm.contact).then(function (data) {
$location.path('/');
});
};
Router for Modal:
.state('payment.agreement', {
url: '/agreement',
onEnter: ['$stateParams', '$state', '$modal', function ($stateParams, $state, $modal) {
$modal.open({
templateUrl: 'views/agreement.html',
controller: 'AgreementFormCtrl as agreementForm'
}
)
.result.finally(function () {
$state.go('^');
});
}]
})
your can do it by using $rootScope and also you missed to initialzed the $scope
First you need to add ng-checked directive in the checkbox
<input type="checkbox" **ng-checked="agreementForm.IsChecked"**
ng-model="agreementForm.value1"> I agree.
Then you need initialize the rootscope in your controller
myApp.controller('AgreementFormCtrl', function ($location, $stateParams, $modalInstance,**$rootScope,$scope**) {
//code
};
and finally you can assign the value for checked object when a modal closes
$scope.agreementForm.cancel = function () {
$modalInstance.dismiss('cancel');
$rootScope.agreementForm.IsChecked="Checked";//True or false
};
I've spent some time having a play with the AngularJS Bootstrap popup, and for intents it's working great, but what i'd like to do is bind it, and it's dependant script to the same controller, what i can't get working is the close button now though. If i create a NEW controller, and inject $modalInstance it works great and i can wireup the close button without any issues at all, but i don't want a second controller, it seems to be over complication: i want all my controller logic in the formController really.
Why would i actually want two controllers? Passing the scope between two controllers just seems overkill to me, and the larger a project becomes the more un-managable it will become. Am i trying to over-simplify this unnecessarily? :)
The script:
(function(){
var app = angular.module('ngModalDemo', ['ui.bootstrap'])
.controller('formController', function($scope, $modal){
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: 'SomeModal.html',
controller: 'formController'
});
};
$scope.closeModal = function () {
// Code needed here :)
};
})
})();
The HTML body (excuse the HTML in script for the purposes of the DEMO):
<div ng-controller="formController">
<button class="btn btn-default" ng-click="openModal()">Let's do some stuff!</button>
<script type="text/ng-template" id="SomeModal.html">
<div class="modal-header">Do some stuff in this modal y'all.</div>
<div class="modal-footer">
<button class="btn btn-info" ng-click="closeModal()">Close</button>
</div>
</script>
</div>
The answer based on Kaspars' input :)
(function(){
var app = angular.module('ngModalDemo', ['ui.bootstrap'])
.controller('formController', function($scope, $modal, $log){
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: 'SomeModal.html',
controller: [
'$scope', '$modalInstance', function($scope, $modalInstance){
$scope.closeModal = function () {
$modalInstance.close();
};
}
]
});
};
})
})();
I was struggling with the same problem and the best thing I came up with was to use anonymous function as a modal controller. In that way all the logic is in the same controller and you don't have to create separate controller for each modal window.
This would look like this:
(function(){
var app = angular.module('ngModalDemo', ['ui.bootstrap'])
.controller('formController', function($scope, $modal){
$scope.openModal = function () {
var modalInstance = $modal.open({
templateUrl: 'SomeModal.html',
controller: [
'$scope', '$modalInstance', 'data', function($scope, $modalInstance, data) {
$scope.data = data;
$scope.ok = function() {
$modalInstance.close();
};
$scope.closeModal = function() {
$modalInstance.dismiss();
};
}
]
});
};
})
})();
PS. Haven't tested code above, just put it together from your provided code and fragments from one of my projects.
You can also try this
var modalInstance = $modal.open({
templateUrl : 'someTemplate.html',
scope : $scope,
size : 'md'
});
I have a ng-repeat and I am trying to add a modal that passes the same scope variable to the modal window. I am able to get the modal window to open but the scope value from ng-repeat is not showing inside the modal. Hopefully my code explains better. This is what I have so far:
<div ng-controller="CustomerController">
<div ng-repeat="customer in customers">
<button class="btn btn-default" ng-click="open()">{{ customer.name }}</button>
<!--MODAL WINDOW-->
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3>The Customer Name is: {{ customer.name }}</h3>
</div>
<div class="modal-body">
This is where the Customer Details Goes<br />
{{ customer.details }}
</div>
<div class="modal-footer">
</div>
</script>
</div>
</div>
The controller:
app.controller('CustomerController', function($scope, $timeout, $modal, $log, customerServices) {
$scope.customers= customerServices.getCustomers();
// MODAL WINDOW
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
});
};
});
The above opens the modal window. However, the customer details such as {{ customer.name }} from the ng-repeat is not passed into the modal window. Have I got something wrong with the Controller?
I am trying to create this after looking at the Angular Bootrap UI example here: http://angular-ui.github.io/bootstrap/
EDIT:
Here is a jsfiddle sample: http://jsfiddle.net/Alien_time/8s9ss/3/
I've updated your fiddle and below code as well. I hope that will help.
Regards
var app = angular.module('app', ['ui.bootstrap']);
app.controller('ModalInstanceCtrl', function ($scope, $modalInstance, customer)
{
$scope.customer = customer;
});
app.controller('CustomerController', function($scope, $timeout, $modal, $log) {
$scope.customers = [
{
name: 'Ricky',
details: 'Some Details for Ricky',
},
{
name: 'Dicky',
details: 'Some Dicky Details',
},
{
name: 'Nicky',
details: 'Some Nicky Details',
}
];
// MODAL WINDOW
$scope.open = function (_customer) {
var modalInstance = $modal.open({
controller: "ModalInstanceCtrl",
templateUrl: 'myModalContent.html',
resolve: {
customer: function()
{
return _customer;
}
}
});
};
});
This is how I setup my modals for handleing items that I ng-repeat over and want to edit. I suggest setting it up to work with a different controller, because then you can use DI to inject the resolved item to the child scope.
$scope.openModal = function(item)
// This sets up some of the options I want the modal to open with
var options = {}
angular.extend(options, {
templateUrl: '/views/userItems/form.html',
controller: 'ItemEditController',
resolve: {
// I resolve a copy of the so it dont mess up the original if they cancel
item: function() { return angular.copy(item); }
}
});
$modal.open(options).result.then(function(updatedItem) {
// after the user saves the edits to the item it gets passed back in the then function
if(updatedItem) {
// this is a service i have to deal with talking to my db
modelService.editItem(updatedItem).then(function(result) {
// get the result back, error check then update the scope
if(result.reason) {
$scope.addAlert({type: 'error', title: 'Application Error', msg: result.reason});
} else {
angular.extend(vital, result);
$scope.addAlert({type: 'success', msg: 'Successfully updated Item!'});
}
});
}
});
};