-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce custom modal confirmation dialogs (#9859)
* Introduce custom modal confirmation dialogs * address code review comments and implement new angular style suggestions from Joe * refactor no point making vanilla code that's really only going to work with angular i guess... also broke apart the confirm modal from the injector, and made safeConfirm just use the confirm modal * Clean up from merge - Move safe_confirm into modals folder - make data-test-subj camelCase. - get rid of $timeout call, doesn’t seem necessary anymore, now that window.confirm is not being used. * Fix tests * Use ui-framework styles * Address latest round of comments and remove adoption of new patterns. * Improve error message. Use sinon in tests. * Focus on the confirm button by default * Indent message
- Loading branch information
1 parent
8029e04
commit 6b009a1
Showing
11 changed files
with
199 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import angular from 'angular'; | ||
import expect from 'expect.js'; | ||
import ngMock from 'ng_mock'; | ||
import sinon from 'sinon'; | ||
|
||
describe('ui/modals/safe_confirm', function () { | ||
|
||
let $rootScope; | ||
let message; | ||
let safeConfirm; | ||
let promise; | ||
|
||
beforeEach(function () { | ||
ngMock.module('kibana'); | ||
ngMock.inject(function ($injector) { | ||
safeConfirm = $injector.get('safeConfirm'); | ||
$rootScope = $injector.get('$rootScope'); | ||
}); | ||
|
||
message = 'woah'; | ||
|
||
promise = safeConfirm(message); | ||
}); | ||
|
||
afterEach(function () { | ||
const confirmButton = angular.element(document.body).find('[data-test-subj=confirmModalConfirmButton]'); | ||
if (confirmButton) { | ||
$rootScope.$digest(); | ||
angular.element(confirmButton).click(); | ||
} | ||
}); | ||
|
||
context('before timeout completes', function () { | ||
it('returned promise is not resolved', function () { | ||
const callback = sinon.spy(); | ||
promise.then(callback, callback); | ||
$rootScope.$apply(); | ||
expect(callback.called).to.be(false); | ||
}); | ||
}); | ||
|
||
context('after timeout completes', function () { | ||
it('confirmation dialogue is loaded to dom with message', function () { | ||
$rootScope.$digest(); | ||
const confirmModalElement = angular.element(document.body).find('[data-test-subj=confirmModal]'); | ||
expect(confirmModalElement).to.not.be(undefined); | ||
|
||
const htmlString = confirmModalElement[0].innerHTML; | ||
|
||
expect(htmlString.indexOf(message)).to.be.greaterThan(0); | ||
}); | ||
|
||
context('when confirmed', function () { | ||
it('promise is fulfilled with true', function () { | ||
const confirmCallback = sinon.spy(); | ||
const cancelCallback = sinon.spy(); | ||
|
||
promise.then(confirmCallback, cancelCallback); | ||
const confirmButton = angular.element(document.body).find('[data-test-subj=confirmModalConfirmButton]'); | ||
|
||
$rootScope.$digest(); | ||
confirmButton.click(); | ||
|
||
expect(confirmCallback.called).to.be(true); | ||
expect(cancelCallback.called).to.be(false); | ||
}); | ||
}); | ||
|
||
context('when canceled', function () { | ||
it('promise is rejected with false', function () { | ||
const confirmCallback = sinon.spy(); | ||
const cancelCallback = sinon.spy(); | ||
promise.then(confirmCallback, cancelCallback); | ||
|
||
const noButton = angular.element(document.body).find('[data-test-subj=confirmModalCancelButton]'); | ||
|
||
$rootScope.$digest(); | ||
noButton.click(); | ||
|
||
expect(cancelCallback.called).to.be(true); | ||
expect(confirmCallback.called).to.be(false); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<div class="kuiModal" style="width: 450px" data-test-subj="confirmModal"> | ||
<div class="kuiModalBody"> | ||
<div class="kuiModalBodyText"> | ||
{{message}} | ||
</div> | ||
</div> | ||
|
||
<div class="kuiModalFooter"> | ||
<button | ||
class="kuiButton kuiButton--hollow" | ||
data-test-subj="confirmModalCancelButton" | ||
ng-click="onCancel()" | ||
> | ||
{{cancelButtonText}} | ||
</button> | ||
<button | ||
class="kuiButton kuiButton--primary" | ||
data-test-subj="confirmModalConfirmButton" | ||
ng-click="onConfirm()" | ||
> | ||
{{confirmButtonText}} | ||
</button> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { noop } from 'lodash'; | ||
import uiModules from 'ui/modules'; | ||
import template from './confirm_modal.html'; | ||
import { ModalOverlay } from './modal_overlay'; | ||
|
||
const module = uiModules.get('kibana'); | ||
|
||
module.factory('confirmModal', function ($rootScope, $compile) { | ||
let modalPopover; | ||
|
||
return function confirmModal(message, customOptions = {}) { | ||
const defaultOptions = { | ||
onConfirm: noop, | ||
onCancel: noop, | ||
confirmButtonText: 'Confirm', | ||
cancelButtonText: 'Cancel' | ||
}; | ||
|
||
const options = Object.assign(defaultOptions, customOptions); | ||
if (modalPopover) { | ||
throw new Error('You\'ve called confirmModal but there\'s already a modal open. ' + | ||
'You can only have one modal open at a time.'); | ||
} | ||
|
||
const confirmScope = $rootScope.$new(); | ||
|
||
confirmScope.message = message; | ||
confirmScope.confirmButtonText = options.confirmButtonText; | ||
confirmScope.cancelButtonText = options.cancelButtonText; | ||
confirmScope.onConfirm = () => { | ||
destroy(); | ||
options.onConfirm(); | ||
}; | ||
confirmScope.onCancel = () => { | ||
destroy(); | ||
options.onCancel(); | ||
}; | ||
|
||
const modalInstance = $compile(template)(confirmScope); | ||
modalPopover = new ModalOverlay(modalInstance); | ||
modalInstance.find('[data-test-subj=confirmModalConfirmButton]').focus(); | ||
|
||
function destroy() { | ||
modalPopover.destroy(); | ||
modalPopover = undefined; | ||
confirmScope.$destroy(); | ||
} | ||
}; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import './confirm_modal'; | ||
import './safe_confirm'; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<div class="kuiModalOverlay"></div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import angular from 'angular'; | ||
import modalOverlayTemplate from './modal_overlay.html'; | ||
|
||
/** | ||
* Appends the modal to the dom on instantiation, and removes it when destroy is called. | ||
*/ | ||
export class ModalOverlay { | ||
constructor(modalElement) { | ||
this.overlayElement = angular.element(modalOverlayTemplate); | ||
this.overlayElement.append(modalElement); | ||
angular.element(document.body).append(this.overlayElement); | ||
} | ||
|
||
/** | ||
* Removes the overlay and modal from the dom. | ||
*/ | ||
destroy() { | ||
this.overlayElement.remove(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import uiModules from 'ui/modules'; | ||
import 'ui/modals'; | ||
|
||
const module = uiModules.get('kibana'); | ||
|
||
module.factory('safeConfirm', function ($timeout, Promise, confirmModal) { | ||
return (message) => new Promise((resolve, reject) => { | ||
const confirmOptions = { | ||
onConfirm: resolve, | ||
onCancel: reject, | ||
confirmButtonText: 'Okay', | ||
cancelButtonText: 'Cancel' | ||
}; | ||
confirmModal(message, confirmOptions); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.