Skip to content

Commit

Permalink
Introduce custom modal confirmation dialogs (#9859)
Browse files Browse the repository at this point in the history
* 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
stacey-gammon authored Jan 19, 2017
1 parent 8029e04 commit 6b009a1
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 114 deletions.
2 changes: 1 addition & 1 deletion src/ui/public/autoload/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import 'ui/parse_query';
import 'ui/persisted_log';
import 'ui/private';
import 'ui/promises';
import 'ui/safe_confirm';
import 'ui/modals';
import 'ui/state_management/app_state';
import 'ui/state_management/global_state';
import 'ui/storage';
Expand Down
1 change: 0 additions & 1 deletion src/ui/public/courier/courier.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import _ from 'lodash';
import errors from 'ui/errors';
import 'ui/es';
import 'ui/promises';
import 'ui/safe_confirm';
import 'ui/index_patterns';
import uiModules from 'ui/modules';
import Notifier from 'ui/notify/notifier';
Expand Down
85 changes: 85 additions & 0 deletions src/ui/public/modals/__tests__/safe_confirm.js
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);
});
});
});
});
24 changes: 24 additions & 0 deletions src/ui/public/modals/confirm_modal.html
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>
49 changes: 49 additions & 0 deletions src/ui/public/modals/confirm_modal.js
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();
}
};
});
3 changes: 3 additions & 0 deletions src/ui/public/modals/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './confirm_modal';
import './safe_confirm';

1 change: 1 addition & 0 deletions src/ui/public/modals/modal_overlay.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="kuiModalOverlay"></div>
20 changes: 20 additions & 0 deletions src/ui/public/modals/modal_overlay.js
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();
}
}
16 changes: 16 additions & 0 deletions src/ui/public/modals/safe_confirm.js
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);
});
});
82 changes: 0 additions & 82 deletions src/ui/public/safe_confirm/__tests__/safe_confirm.js

This file was deleted.

30 changes: 0 additions & 30 deletions src/ui/public/safe_confirm/safe_confirm.js

This file was deleted.

0 comments on commit 6b009a1

Please sign in to comment.