feat: 🎸 implement initial confirm service and decorator

for super easy application of confirmation dialogs on existing action
This commit is contained in:
Randall Morey committed 2020-09-30 17:53:31 -04:00
1 parent a5a69b0674
commit 4cf322f6ad
6 files changed
+98 -2

No files matched your search

+4 -1
View File
@@ -2,6 +2,7 @@ import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import loading from 'ember-loading/decorator';
import { confirm } from '../../../utilities/confirm';
export default class ScopesScopeProjectsRoute extends Route {
// =services
@@ -9,6 +10,7 @@ export default class ScopesScopeProjectsRoute extends Route {
@service intl;
@service notify;
@service session;
@service confirm;
// =methods
@@ -84,12 +86,13 @@ export default class ScopesScopeProjectsRoute extends Route {
*/
@action
@loading
@confirm('questions.delete-confirm')
async delete(project) {
try {
await project.destroyRecord();
await this.replaceWith('scopes.scope.projects');
this.refresh();
this.notify.success(this.intl.t('notifications.delete-success'));
this.transitionTo('scopes.scope.projects');
} catch (error) {
// TODO: replace with translated strings
this.notify.error(error.message, { closeAfter: null });
+53
View File
@@ -0,0 +1,53 @@
import Service from '@ember/service';
import { getOwner } from '@ember/application';
import { bind } from '@ember/runloop';
import { Promise, resolve } from 'rsvp';
import config from '../config/environment';
export default class ConfirmService extends Service {
// =attributes
/**
* When the confirm service is disabled, it always returns resolving promises.
* @type {boolean}
*/
enabled = config.enableConfirmService;
/**
* The window's confirm function, bound to the window scope.
* @type {Function}
*/
get confirmFn() {
// The Ember way of accessing globals...
const document =
getOwner(this).lookup('service:-document').documentElement;
// defaultView === window, but without using globals directly
const windowRef = document.parentNode.defaultView;
const { confirm } = windowRef;
return bind(windowRef, confirm);
}
// =methods
/**
* When the service is enabled, returns a promise that resolves if the user
* accepts the confirmation, otherwise rejects. If the service is disabled,
* this method always returns a resolving promise.
* @param {string} text
* @return {Promise}
*/
confirm(text) {
if (this.enabled) {
return new Promise((resolve, reject) => {
if (this.confirmFn(text)) {
return resolve();
} else {
return reject();
}
});
} else {
return resolve();
}
}
}
+23
View File
@@ -0,0 +1,23 @@
import { getOwner } from '@ember/application';
/**
* Decorates a method whereby calls to the method will first request
* confirmation via the confirmation service. If the user accepts, the
* original method is executed. If the user denies, it is not.
*/
export function confirm(confirmationTextKey) {
return function (_target, _propertyKey, desc) {
const method = desc.value;
desc.value = function () {
const owner = getOwner(this);
const confirmService = owner.lookup('service:confirm');
const intl = owner.lookup('service:intl');
const text = intl.t(confirmationTextKey);
return confirmService.confirm(text)
// If user confirmed, execute the decorated method
.then(() => method.apply(this, arguments))
// If the user denied, gracefully handle promise rejection with no-op
.catch(() => {});
};
}
}
+2
View File
@@ -34,6 +34,7 @@ module.exports = function (environment) {
appName: APP_NAME,
notifyTimeout: 4000,
enableConfirmService: true,
contentSecurityPolicyHeader: 'Content-Security-Policy',
contentSecurityPolicyMeta: true,
@@ -98,6 +99,7 @@ module.exports = function (environment) {
// Notification timeout should be 0 for fast tests
ENV.notifyTimeout = 0;
ENV.enableConfirmService = false;
}
if (environment === 'production') {
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Service | confirm', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let service = this.owner.lookup('service:confirm');
assert.ok(service);
});
});
+4 -1
View File
@@ -17,4 +17,7 @@ titles:
global: Global
actions: Actions
descriptions:
empty-set: There are no items to display yet. You may be able to add items or try back later.
empty-set: There are no items to display yet. You may be able to add items or try back later.
questions:
delete-confirm: Are you sure you want to delete this resource?
remove-confirm: Are you sure you want to remove this association?