Passed value still edited after cancel angular mddialog - javascript

I pass a value to my mdDialog controller to edit the content in a modal, but of the user cancels the modal no adjustments can be saved, but still if I change to content within the modal, I see the changes happening on the list behind (on the parent view) and when I cancel the modal, the changes aren't undone.
The option bindToController is set to true, so a copy should be passed instead of a reference.
vm.editFaq = function (faqToEdit, ev){
var useFullScreen = ($mdMedia('sm') || $mdMedia('xs')) && vm.customFullscreen;
$mdDialog.show({
controller: 'editFaqController'
, controllerAs: 'dvm'
, templateUrl: './app/components/faq/modals/editFaq.html'
, parent: angular.element(document.body)
, targetEvent: ev
, clickOutsideToClose: true
, fullscreen: useFullScreen
, locals: { faq : faqToEdit }
, bindToController: true
}).then(function(result){
if(result){
_.findWhere(vm.allFaqs, { _id: faqToEdit._id }) = result;
}
});
$scope.$watch(function () {
return $mdMedia('xs') || $mdMedia('sm');
}, function (wantsFullScreen) {
$scope.customFullscreen = (wantsFullScreen === true);
});
};
So when to modal is hidden, the "then" promise is called and the adjustments can be committed.

I used angular.copy, a friend told me that even with bindToController, or what ever other option of mdDialog that said to work always passes a reference of the object.

you can solve like that:
vm.editFaq = function (faqToEdit, ev){
var useFullScreen = ($mdMedia('sm') || $mdMedia('xs')) && vm.customFullscreen;
faqToEdit = angular.copy(faqToEdit);
$mdDialog.show({
controller: 'editFaqController',
controllerAs: 'dvm',
templateUrl: './app/components/faq/modals/editFaq.html',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose: true,
fullscreen: useFullScreen,
locals: {
faq: faqToEdit
},
bindToController: true
}).then(function(result) {
if (result) {
_.findWhere(vm.allFaqs, {
_id: faqToEdit._id
}) = result;
}
});
}

Related

Add $mdDialog to angular function

there i want to apply $mdDialog to the following angular function and I'm using angularjs 1.6.4. $mdDialog should prompt a message to select Yes or no to delete the file. Here is my code
$scope.deleteFile = function (key) {
$scope.documentEventText = "Document Deleted";
FileResourceService.deleteFile('KeyPhotos', key, $state.params.propertyId, $scope.key.id, $scope.getFiles);
}
Open the dialog inside the function as follows,
var confirm = $mdDialog.confirm({
controller: DialogController,
templateUrl: 'dialog1.tmpl.html',
parent: angular.element(document.body),
targetEvent: ev,
})
$mdDialog.show(confirm).then(function() {
$scope.status = 'Confirmed';
}
DEMO

angular multiple $mdDialog

I work with modal tabs and I have notification pop-up window which is always shown to user when he logs into my application. It contains all events which happends when user was offline. Problem is when i click on any objects from list it close my pop-up window and display new modal tab.
I want to archieve this functionality. When user log in, notification pop-up window will be shown to user and if he clicks on any object it will open another window without closing my notification pop-up window(New events). I want something like that on picture below which I made.
I checked angular material documentation, but there is no demo at all and not even well explained how to work with multiple: true option and I dont know exactly how to make it work like I want.
https://material.angularjs.org/latest/api/service/$mdDialog
This is my code for displaying notification pop-up window.
//show new notifications when user log in
NotificationService.getUnreadedNotifications(function (data) {
//initialization
$scope.notification = [];
$scope.OverAllCount = 0;
$scope.messageNotification = [];
$scope.OverAllMessageCount = 0;
if (data.ProjectNotifications != null) {
angular.forEach(data.ProjectNotifications, function (key, value) {
$scope.notification.push(key);
$scope.OverAllCount = $scope.OverAllCount + 1;
});
}
if (data.TasksNotifications != null) {
angular.forEach(data.TasksNotifications, function (key, value) {
$scope.notification.push(key);
$scope.OverAllCount = $scope.OverAllCount + 1;
});
}
if (data.MessageNotifications != null) {
angular.forEach(data.MessageNotifications, function (key, value) {
$scope.OverAllMessageCount = $scope.OverAllMessageCount + 1;
$scope.messageNotification.push(key);
});
}
popUpNotification();
$scope.hide = function () {
$mdDialog.hide();
};
$scope.cancel = function () {
$mdDialog.cancel();
};
$scope.answer = function (answer) {
$mdDialog.hide(answer);
};
//mark notifications as readed when user click on notification
function popUpNotification() {
$mdDialog.show({
controller: NotificationController,
templateUrl: 'app/components/templates/PopUpNotification.html',
parent: angular.element(document.body),
//targetEvent: ev,
clickOutsideToClose: true,
fullscreen: false,
scope: $scope,
multiple:true,
preserveScope: true,
onComplete: function () {
$scope.notificationPopUp = $scope.notification;
}
})
.then(function () {
}, function () {
//fail
});
}
});
This is code for displaying details of object on which user clicked in new overlaying modal tab
//mark notifications as readed when user click on notification
$scope.popUpDetail = function (notification, index, ev) {
$mdDialog.show({
controller: NotificationController,
templateUrl: 'app/components/templates/TaskDetailsDialog.html',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose: true,
fullscreen: false,
scope: $scope,
multiple: true,
preserveScope: true,
onComplete: function () {
//was readed => update database
NotificationResourceService.update({ id: notification.Id }, notification);
$scope.OverAllCount -= 1;
$scope.notification.splice(index, 1);
TaskService.get({ id: notification.EntityId })
.$promise.then(function (task) {
$scope.task = task;
});
}
})
.then(function () {
}, function () {
//fail
});
}
Somehow i found working solution for my problem. It might help somebody in future.
Working code:
function popUpNotification() {
$mdDialog.show({
templateUrl: 'app/components/templates/PopUpNotification.html',
clickOutsideToClose: true,
bindToController: true,
scope: $scope,
preserveScope: true,
controller: function ($scope, $mdDialog) {
$scope.notificationPopUp = $scope.notification;
$scope.popUpDetail = function (notification, index, ev) {
$mdDialog.show({
controller: function ($mdDialog) {
this.click = function () {
$mdDialog.hide();
}
},
targetEvent: ev,
clickOutsideToClose: true,
preserveScope: true,
autoWrap: true,
skipHide: true,
scope: $scope,
preserveScope: true,
templateUrl: 'app/components/templates/TaskDetailsDialog.html',
onComplete: function () {
TaskService.get({ id: notification.EntityId })
.$promise.then(function (task) {
$scope.task = task;
});
}
})
}
},
autoWrap: false,
})
}
});
Add 'multiple: true' as a parameter:
// From plain options
$mdDialog.show({
multiple: true
});
// From a dialog preset
$mdDialog.show(
$mdDialog
.alert()
.multiple(true)
);
From documentation: https://material.angularjs.org/latest/api/service/$mdDialog
The key is using skipHide: true as parameter in the object we pass into $mdDialog.show(). I tried without multiple: true and it still works. This parameter has to be passed into the second (or nth) dialog. So it will look somethink like this:
// second dialog
$mdDialog.show({
// some fields
skipHide: true,
//some fields
});

Passing a variable to an angular 1.x component that is in a Angular Material Dialog

I am trying to pass an object to a component that is inside an angular material dialog.
The function I use to display the Dialog is:
ctrl.openCampaignSplitDialog = function(ev, split){
$mdDialog.show({
template: '<campaign-split-dialog split="$ctrl.split"></campaign-split-dialog>',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
fullscreen: $scope.customFullscreen // Only for -xs, -sm breakpoints.
}).then(function(split) {
ctrl.addCampaignSplit(split);
}, function() {
$scope.status = 'You cancelled the dialog.';
});
};
This correctly opens up the dialog.
This is the code for the component:
angular
.module('app')
.component('campaignSplitDialog', {
templateUrl: 'app/components/campaignSplitDialog/campaignSplitDialog.html',
controller: campaignSplitDialogCntrlr,
bindings:{
split: '<'
}
});
/** #ngInject */
function campaignSplitDialogCntrlr($mdDialog) {
var ctrl = this;
console.log('splitter', ctrl.split);
}
The issue arrises from the fact I am not sure how to pass in the split object from the open dialog function to the component module. In the 'template' URL there I have split="$ctrl.split". I have tried multiple different ways but none worked. I have tried double brackets, plain variable name, and using the controllerAs syntax.
I have also tried passing the value in through the dialog by using the locals:{} paramter but because I do not specify a controller, since it is configured when the component is called upon, it does not appear in the component.
I'll answer the first line of your question - "I am trying to pass an object to a component that is inside an angular material dialog" - as the way you are trying to achieve it doesn't look correct.
CodePen
Markup
<div ng-controller="MyController as vm" ng-cloak="" ng-app="app">
<md-button class="md-primary md-raised" ng-click="vm.open($event)">
Custom Dialog
</md-button>
<script type="text/ng-template" id="test.html">
<md-dialog aria-label="Test">
<campaign-split split="text"></campaign-split>
</md-dialog>
</script>
</div>
JS
angular.module('app',['ngMaterial'])
.component('campaignSplit', {
template: '<div>{{$ctrl.split}}</div>',
bindings:{
split: '<'
}
})
.controller('MyController', function($scope, $mdDialog) {
this.open = function(ev) {
$scope.text = "Hello";
$mdDialog.show(
{
templateUrl: "test.html",
clickOutsideToClose: true,
scope: $scope,
preserveScope: true,
controller: function($scope) {
},
});
};
this.save = function () {
$mdDialog.cancel();
}
})
Hopefully this will point you in the right direction.
Check the property options.locals https://material.angularjs.org/HEAD/#mddialog-show-optionsorpreset
$mdDialog.show({
template: '<campaign-split-dialog split="$ctrl.split"></campaign-split-dialog>',
locals:{
split: $ctrl.split
},
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
fullscreen: $scope.customFullscreen // Only for -xs, -sm breakpoints.
})
The way you currently are doing it, is skipping the dialogs controller.
In this scenario there are three controllers.
1) state controller
2 dialog controller
3) component controller
The proper way to do this is:
ctrl.openCampaignSplitDialog = function(ev, split){
$mdDialog.show({
template: '<campaign-split-dialog split="split"></campaign-split-dialog>',
parent: angular.element(document.body),
targetEvent: ev,
locals: {split: $ctrl.split}
controller: function($scope, split){
$scope.split = split;
},
clickOutsideToClose:true,
fullscreen: $scope.customFullscreen // Only for -xs, -sm breakpoints.
}).then(function(split) {
ctrl.addCampaignSplit(split);
}, function() {
$scope.status = 'You cancelled the dialog.';
});
};
This is the code for the component:
angular
.module('app')
.component('campaignSplitDialog', {
templateUrl: 'app/components/campaignSplitDialog/campaignSplitDialog.html',
controller: campaignSplitDialogCntrlr,
bindings:{
split: '<'
}
});
/** #ngInject */
function campaignSplitDialogCntrlr($mdDialog) {
var ctrl = this;
console.log('splitter', ctrl.split);
}
So what is happening is, the variable $ctrl.split is going from the state controller to the dialog controller using locals, then in the dialog controller, you bind it to $scope, then from there you can pass the variable $scope.split to the component tag, that passes the variable to the component bindings

Trying to pass a local variable to my ngAside controller

I am using ngAside to display some data. The data depends on a variable chosen by the user so I need to pass an Id to ngAside's controller.
Right now my controller looks like this:
$aside.open({
templateUrl: 'details.html',
placement: position,
size: 'lg',
backdrop: true,
locals: {
mode: "create"
},
controller: function Ctrl($scope, $uibModalInstance, mode) {
$scope.test = mode;
$scope.ok = function (e) {
$uibModalInstance.close();
e.stopPropagation();
};
$scope.cancel = function (e) {
$uibModalInstance.dismiss();
e.stopPropagation();
};
}
})
I have based this on what I've seen written on ngMaterial here.
It is not working as expected though and Im getting
Unknown provider: modeProvider <- mode
How can I fix this and be able to pass variables to be displayed on my ngAside template?
Since you want to inject "mode" variable in a controller, my guess would be using the standard "resolve" parameter.
$aside.open({
templateUrl: 'details.html',
placement: position,
size: 'lg',
backdrop: true,
resolve: {
mode: function() {
return "create";
}
},
controller: function Ctrl($scope, $uibModalInstance, mode) {
[...]
}
});
Basically, for each controller parameter that is not registered in the Dependency Injection list, you need to provide a function which will return the value you want to be injected in the variable.
This function :
mode: function() {
return "create";
}
Will be executed and its return value will populate the "mode" controller argument.

Unit testing angular-bootstrap $modal

I'm having problems trying to write a jasmine unit test for an Angular-Bootstrap $modal. The exact error is
Expected spy open to have been called with [ { templateUrl : '/n/views/consent.html', controller : 'W2ConsentModal as w2modal', resolve : { employee : Function }, size : 'lg' } ] but actual calls were [ { templateUrl : '/n/views/consent.html', controller : 'W2ConsentModal as w2modal', resolve : { employee : Function }, size : 'lg' } ]
The expected and actual modal options object are the same. What is going on?
Controller
(function () {
'use strict';
angular
.module('app')
.controller('W2History', W2History);
W2History.$inject = ['$scope', '$modal', 'w2Service'];
function W2History($scope, $modal, w2Service) {
/* jshint validthis:true */
var vm = this;
vm.showModal = showModal;
function showModal(employee) {
var modalInstance = $modal.open({
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
});
modalInstance.result.then(function (didConsent) {
// code omitted
});
}
}
})();
Test
describe('W2History controller', function () {
var controller, scope, modal;
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function (item) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack(item);
},
dismiss: function (type) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback(type);
}
};
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
};
beforeEach(function () {
module('app');
inject(function (_$controller_, _$rootScope_, _$modal_) {
scope = _$rootScope_.$new();
modal = _$modal_;
spyOn(modal, 'open').and.returnValue(fakeModal);
controller = _$controller_('W2History', {
$scope: scope,
$modal: modal,
w2Service: w2Srvc
});
});
});
it('Should correctly show the W2 consent modal', function () {
var employee = terminatedaccessMocks.getCurrentUserInfo();
controller.showModal(employee);
expect(modal.open).toHaveBeenCalledWith(modalOptions);
});
});
Try this:
describe('W2History controller', function () {
var controller, scope, modal;
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function (item) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack(item);
},
dismiss: function (type) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback(type);
}
};
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: jasmine.any(Function)
},
size: 'lg'
};
var actualOptions;
beforeEach(function () {
module('plunker');
inject(function (_$controller_, _$rootScope_, _$modal_) {
scope = _$rootScope_.$new();
modal = _$modal_;
spyOn(modal, 'open').and.callFake(function(options){
actualOptions = options;
return fakeModal;
});
controller = _$controller_('W2History', {
$scope: scope,
$modal: modal
});
});
});
it('Should correctly show the W2 consent modal', function () {
var employee = { name : "test"};
controller.showModal(employee);
expect(modal.open).toHaveBeenCalledWith(modalOptions);
expect(actualOptions.resolve.employee()).toEqual(employee);
});
});
PLUNK
Explanation:
We should not expect the actual resolve.employee to be the same with the fake resolve.employee because resolve.employee is a function which returns an employee (in this case the employee is captured in closure). The function could be the same but at runtime the returned objects could be different.
The reason your test is failing is the way javascript compares functions. Take a look at this fiddle. Anyway, I don't care about this because we should not expect function implementations. What we do care about in this case is the resolve.employee returns the same object as we pass in:
expect(actualOptions.resolve.employee()).toEqual(employee);
So the solution here is:
We expect everything except for the resolve.employee:
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: jasmine.any(Function) //don't care about the function as we check it separately.
},
size: 'lg'
};
expect(modal.open).toHaveBeenCalledWith(modalOptions);
Check the resolve.employee separately by capturing it first:
var actualOptions;
spyOn(modal, 'open').and.callFake(function(options){
actualOptions = options; //capture the actual options
return fakeModal;
});
expect(actualOptions.resolve.employee()).toEqual(employee); //Check the returned employee is actually the one we pass in.
This is a pass by reference vs pass by value issue. The resolve.employee anonymous function used in $modal.open:
var modalInstance = $modal.open({
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
});
is not the same (by reference) as the resolve.employee anonymous function in your test:
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
};
Your test should be:
resolve: {
employee: jasmine.any(Function)
}
If it's essential that the resolve function be tested, you should expose it somewhere where you can get a reference to the same function in your tests.
I am not sure if this will help you now, but when you spy on something you can get the argument that is passed to the $uibModal.open spy, you can then call that function to test that it returns what is in the resolve method.
it('expect resolve to be have metadataid that will return 9999', () => {
spyOn($uibModal, 'open');
//add test code here that will call the $uibModal.open
var spy = <jasmine.Spy>$uibModal.open;
var args = spy.calls.argsFor(0);
expect(args[0].resolve.metadataId()).toEqual(9999);
});
***** my code is using typescript, but this works for me.**
I have come across the same scenario. I have come across the problem with the below given solution
//Function to open export modal
scope.openExportModal();
expect( uibModal.open ).toHaveBeenCalledWith(options);
expect( uibModal.open.calls.mostRecent().args[0].resolve.modalData() ).toEqual(modalData);
Hope this may help if you want a quick fix.

Categories

Resources