ember-confirmed
v2.5.1
Published
An Ember asynchronous confirmation addon
Downloads
37
Readme
ember-confirmed
An Ember asynchronous confirmation addon
A very simple promise-like API for easily showing a confirmation and
resolving to the result of the user input. Its main goal is to replace or
enhance the typical window.confirm
and many adhoc event based solutions (see
example below).
Special thanks to FunnelCloud Inc. for graciously providing inspiration, R+D, and support.
License: MIT
Installation
ember install ember-confirmed
Usage
import Confirmer from 'confirmer';
Much like the Promise
API the constructor for the Confirmer
takes
a function. That function is passed a resolver object which has four functions
on it that you can use to fulfill the Confirmer
.
new Confirmer(function (resolver) {
// Affirm confirmation
resolver.confirm();
// Reject confirmation
resolver.reject();
// Cancel confirmation
resolver.cancel();
// Reject with an Error
resolver.error(new Error());
})
Each state can take an optional value. The Confirmer
is a wrapper around
a Promise
and can be treated as a promise. For example to capture any errors
or exceptions you may trigger you would use the catch()
function.
new Confirmer(function (resolver) { … })
.catch(function (reason) { console.error(reason); });
The then()
function will be passed the underlying data object:
new Confirmer(function (resolver) { … })
.then(function (result) {
console.log(result.reason);
console.log(result.value);
});
The reason
being one of rejected
, confirmed
, or cancelled
. And the
value
is the value passed to one of the resolver functions.
The following methods are chainable:
onCanceled
Is called when resolver.cancel()
is triggered. Used to denote that the
confirmation was cancelled and perhaps should do nothing.
onConfirmed
Is called when resolver.confirm()
is triggered. Used to denote that the user
has confirmed in some way. ("OK" button, correct login credentials, etc.)
onRejected
Is called when resolver.rejected()
is triggered. Used to denote that the user
has performed an action that denied the confirmation. ("No" button, bad
password, etc.)
onDone
Is called when any of the resolver functions are triggered. This is used for
clean up like closing the dialog and removing stale event handlers. This is also
called if the resolver.error
is triggered or something throws an exception in
the initialization function (which can be captued by the catch()
function just
like a promise).
Examples
The following are example situations that I've run into and how this module can help reason about them.
Basic window.confirm
In this example we will wrap the window.confirm
. Although this is not
asynchronous it does illustrate the API.
new Confirmer(function (resolver) {
if (confirm('Whould you like to do this?')) {
resolver.confirm();
} else {
resolver.cancel();
}
})
.onConfirmed(function () { console.log('Ok! let\'s crack on!'); })
.onCancelled(function () { console.log('Maybe next time then?'); })
.onDone(function () { console.log('Confirm completed') });
Example component
<p>Result was confirmed: {{if confirmed 'YES' 'NO'}}</p>
{{#if resolver}}
<p>Confirmation?</p>
<button onclick={{action resolver.cancel}}>Cancel</button>
<button onclick={{action resolver.confirm}}>Ok</button>
{{else}}
<button onclick={{action "showDialog"}}>Show Dialog</button>
{{/if}}
import Component from '@ember/component';
import Confirmer from 'confirmer';
export default Component.extend({
actions: {
showDialog() {
new Confirmer(resolver => this.set('resolver', resolver))
.onConfirmed(() => this.set('confirmed', true))
.onCancelled(() => this.set('confirmed', false))
.onDone(() => this.set('resolver', null));
}
}
});
Password prompt
Maybe the resolution of the confirmation needs more logic. For example asking for a password.
<p>Result was confirmed: {{if confirmed 'YES' 'NO'}}</p>
{{#if resolver}}
<label>
Password: {{input type="password" value=password}}
</label>
<button onclick={{action resolver.cancel}}>Cancel</button>
<button onclick={{action resolver.confirm password}}>Ok</button>
{{else}}
<button onclick={{action "showDialog"}}>Show Dialog</button>
{{/if}}
import Component from '@ember/component';
import Confirmer from 'confirmer';
const REAL_PASSWORD = 'password';
export default Component.extend({
actions: {
showDialog() {
new Confirmer(resolver => this.set('resolver', resolver))
.onConfirmed(password => {
this.set('confirmed', password === REAL_PASSWORD);
})
.onCancelled(() => this.set('confirmed', false))
.onDone(() => this.set('resolver', null));
}
}
});
Auto closing message box
Here is an example of a message box that auto closes after 5 seconds.
Notice that you can call the resolver functions multiple times and only the first one wins.
import Component from '@ember/component';
import { later } from '@ember/runloop';
import Confirmer from 'confirmer';
const DIALOG_AUTO_CLOSE_DELAY = 5000;
export default Component.extend({
actions: {
showDialog() {
new Confirmer(resolver => {
later(resolver.cancel, DIALOG_AUTO_CLOSE_DELAY);
this.set('resolver', resolver);
})
.onConfirmed(() => this.set('confirmed', true))
.onCancelled(() => this.set('confirmed', false))
.onDone(() => this.set('resolver', null));
}
}
});
Unsaved changes confirmation on route transition
Here is an example if trapping a route transition and showing a confirmation dialog if the data is not saved (dirty).
It shows how you can pass a Confirmer object around between each other.
app/routes/index.js
import Route from '@ember/routing/route';
export default Route.extend({
actions: {
willTransition(transition) {
let confirmer = this.controller.showDirtyConfirmation();
if (confirmer) {
transition.abort();
confirmer.onConfirmed(() => transition.retry());
}
}
}
});
app/controllers/index.js
import Controller from '@ember/controller';
import Confirmer from 'confirmer';
export default Controller.extend({
showDirtyConfirmation() {
if (!this.get('model.hasDirtyAttributes')) { return; }
return new Confirmer(resolver => {
this.set('modalAction', resolver);
this.set('showConfirmationModal', true);
})
.onConfirmed(() => this.get('model').rollbackAttributes())
.onDone(() => this.set('showConfirmationModal', false));
}
});
app/templates/index.hbs
{{#if showConfirmationModal}}
<div class="modal">
<p>You have unsaved changes. Are you sure wish to abandon them?<p>
<button {{action (action modalActions.cancel)}}>No</button>
<button {{action (action modalActions.confirm)}}>Yes</button>
</div>
{{/if}}
Example using ember-remodal
We have an {{#ember-remodal-redux}}
component to handle the complexity of
using remodal as a component instead of a global/service. If this does not fit
your needs then you can still use remodal manually.
This is an example using the ember-remodal addon on your own.
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import Confirmer from 'confirmer';
export default Controller.extend({
remodal: service(),
showConfirmationModal() {
const remodal = this.get('remodal');
let modalName = 'confirmation'; // Change as needed
return new Confirmer(resolver => {
this.set('modalResolver', resolver);
let promise = remodal.open(modalName);
resolver.dispose(() => {
// https://github.com/sethbrasile/ember-remodal/issues/3
return promise
.then(() => {
let modalState = tryInvoke(this.get('modal'), 'getState');
// https://github.com/vodkabears/Remodal/issues/291
if (modalState !== 'opened') { return; }
return remodal.close(modalName);
})
.then(() => this.set('modalResolver', null));
});
});
},
actions: {
resolveModal(method, value) {
this.get('modalResolver')[method](value);
}
}
});
{{#ember-remodal
forService=true
name="confirmation"
onConfirm=(action "resolveModal" "confirm")
onCancel=(action "resolveModal" "reject")
onClose=(action "resolveModal" "cancel")
as |modal|}}
{{#modal.cancel}}<button class="pull-right">Close</button>{{/modal.cancel}}
<p>Pick yes or no:</p>
{{#modal.cancel}}<button>No</button>{{/modal.cancel}}
{{#modal.confirm}}<button>Yes</button>{{/modal.confirm}}
{{/ember-remodal}}
Example using ember-sweetalert
This is an example of wrapping the ember-sweetalert addon in a Confirmer object.
import Confirmer from 'confirmer';
import sweetAlert from 'ember-sweetalert';
export default function sweetAlertConfirmer(sweetAlertOptions) {
return new Confirmer(resolver => {
sweetAlert(sweetAlertOptions)
.then(result => {
if (result.dismiss) {
resolver.cancel(result.dismiss);
} else {
resolver.confirm(result.value);
}
})
.catch(resolver.error);
});
}