` tag within a
19 | `` tag. This has been changed so that just a `` tag is now used.
20 | This is unlikely to be a breaking change, unless you were relying on
21 | the elements for page layout and/or CSS.
22 |
23 | ### Removed
24 | - **BREAKING** The previously deprecated `open` method on the SweetAlert
25 | service has been removed. Use the `fire` method instead.
26 |
27 | ## [2.0.1] - 2021-01-14
28 |
29 | ### Fixed
30 | - Fixed JS linting issues.
31 |
32 | ## [2.0.0] - 2021-01-14
33 |
34 | ### Changed
35 | - Upgraded addon to Ember 3.24 (from 3.19).
36 | - Minimum supported Ember version is now 3.16 LTS.
37 |
38 | ### Fixed
39 | - Import test helpers from `@ember/test-helpers` rather than `ember-test-helpers`.
40 |
41 | ## [2.0.0-alpha.1] - 2020-07-22
42 |
43 | ### Changed
44 | - **BREAKING:** Upgrade Sweet Alert to from `7.33` to `9.17`.
45 | - [v8 to v9 Upgrade Guide](https://github.com/sweetalert2/sweetalert2/releases/tag/v9.0.0)
46 | - [v7 to v8 Upgrade Guide](https://github.com/sweetalert2/sweetalert2/releases/tag/v8.0.0)
47 | - **BREAKING:** Sweet Alert component no longer supports positional parameters.
48 | - **BREAKING:** The Sweet Alert component now follows the DDAU pattern. This means
49 | that the `onClose` action must be used in conjunction with the `show` attribute
50 | to ensure the show value is toggled between true/false. There is an
51 | [example in the readme document](./README.md#Opening).
52 | - Minimum Ember version is now `3.16`.
53 |
54 | ### Deprecated
55 | - The `SweetAlertService.open()` method is deprecated in favour of
56 | `SweetAlertService.fire()`, and will be removed in `3.0`.
57 |
58 | ## [1.1.0] - 2020-07-22
59 |
60 | ### Added
61 | - Updated list of configuration keys on the component to match those available
62 | in Sweet Alert. Added the following:
63 | - `customContainerClass`
64 | - `keydownListenerCapture`
65 | - `cancelButtonColor`
66 | - `showCloseButton`
67 | - `validationMessage`
68 | - `onAfterClose`
69 | - Update the list of methods available on the Sweet Alert service to match
70 | those available on Sweet Alert. Added the following:
71 | - `getButtonWrapper`
72 | - `stopTimer`
73 | - `resumeTimer`
74 | - `toggleTimer`
75 | - `isTimerRunning`
76 | - `increaseTimer`
77 | - `showValidationMessage` (use instead of `showValidationError`)
78 | - `resetValidationMessage` (use instead of `resetValidationError`)
79 | - `getValidationMessage`
80 |
81 | ## [1.0.0] - 2020-07-22
82 |
83 | ### Changed
84 | - Upgraded package to Ember CLI `3.19`, requiring at least Node 10.
85 | - Minimum supported Ember version is now `3.8`.
86 |
87 | ## [1.0.0-rc.1] - 2019-03-28
88 |
89 | ### Added
90 | - Now supports default configuration for all alerts.
91 | - Execute callbacks on the Ember run loop.
92 | - Provide test helpers.
93 | - Added package tests.
94 | - New Sweet Alert service to trigger alerts from controllers, routes etc.
95 |
96 | ### Changed
97 | - Upgraded to SweetAlert2 v7
98 | - Install SweetAlert2 via NPM instead of Bower.
99 |
--------------------------------------------------------------------------------
/addon/services/swal.js:
--------------------------------------------------------------------------------
1 | import Service from '@ember/service';
2 | import { getOwner } from '@ember/application';
3 | import { scheduleOnce } from '@ember/runloop';
4 | import { Promise } from 'rsvp';
5 | import Swal from 'sweetalert2';
6 |
7 | export default class SweetAlertService extends Service {
8 | sweetAlert;
9 |
10 | constructor() {
11 | super(...arguments);
12 | let config = getOwner(this).resolveRegistration('config:environment') || {};
13 | this.sweetAlert = Swal.mixin(config['ember-sweetalert'] || {});
14 | }
15 |
16 | fire(...args) {
17 | return new Promise((resolve, reject) => {
18 | this.sweetAlert.fire(...args).then(resolve, reject);
19 | });
20 | }
21 |
22 | isVisible() {
23 | return this.sweetAlert.isVisible();
24 | }
25 |
26 | mixin(params) {
27 | return this.sweetAlert.mixin(params);
28 | }
29 |
30 | update(params) {
31 | return this.sweetAlert.update(params);
32 | }
33 |
34 | close() {
35 | this._run('close');
36 | }
37 |
38 | getContainer() {
39 | return this.sweetAlert.getContainer();
40 | }
41 |
42 | getHeader() {
43 | return this.sweetAlert.getHeader();
44 | }
45 |
46 | getTitle() {
47 | return this.sweetAlert.getTitle();
48 | }
49 |
50 | getProgressSteps() {
51 | return this.sweetAlert.getProgressSteps();
52 | }
53 |
54 | getCloseButton() {
55 | return this.sweetAlert.getCloseButton();
56 | }
57 |
58 | getContent() {
59 | return this.sweetAlert.getContent();
60 | }
61 |
62 | getImage() {
63 | return this.sweetAlert.getImage();
64 | }
65 |
66 | getActions() {
67 | return this.sweetAlert.getActions();
68 | }
69 |
70 | getFooter() {
71 | return this.sweetAlert.getFooter();
72 | }
73 |
74 | getFocusableElements() {
75 | return this.sweetAlert.getFocusableElements();
76 | }
77 |
78 | getConfirmButton() {
79 | return this.sweetAlert.getConfirmButton();
80 | }
81 |
82 | getDenyButton() {
83 | return this.sweetAlert.getDenyButton();
84 | }
85 |
86 | getCancelButton() {
87 | return this.sweetAlert.getCancelButton();
88 | }
89 |
90 | enableButtons() {
91 | this._run('enableButtons');
92 | }
93 |
94 | disableButtons() {
95 | this._run('disableButtons');
96 | }
97 |
98 | showLoading() {
99 | this._run('showLoading');
100 | }
101 |
102 | hideLoading() {
103 | this._run('hideLoading');
104 | }
105 |
106 | isLoading() {
107 | return this.sweetAlert.isLoading();
108 | }
109 |
110 | getTimerLeft() {
111 | return this.sweetAlert.getTimerLeft();
112 | }
113 |
114 | stopTimer() {
115 | return this.sweetAlert.stopTimer();
116 | }
117 |
118 | resumeTimer() {
119 | return this.sweetAlert.resumeTimer();
120 | }
121 |
122 | toggleTimer() {
123 | return this.sweetAlert.toggleTimer();
124 | }
125 |
126 | isTimerRunning() {
127 | return this.sweetAlert.isTimerRunning();
128 | }
129 |
130 | increaseTimer(n) {
131 | return this.sweetAlert.increaseTimer(n);
132 | }
133 |
134 | clickConfirm() {
135 | this._run('clickConfirm');
136 | }
137 |
138 | clickDeny() {
139 | this._run('clickDeny');
140 | }
141 |
142 | clickCancel() {
143 | this._run('clickCancel');
144 | }
145 |
146 | getInput() {
147 | return this.sweetAlert.getInput();
148 | }
149 |
150 | disableInput() {
151 | this._run('disableInput');
152 | }
153 |
154 | enableInput() {
155 | this._run('enableInput');
156 | }
157 |
158 | showValidationMessage(error) {
159 | this._run('showValidationMessage', error);
160 | }
161 |
162 | resetValidationMessage() {
163 | this._run('resetValidationMessage');
164 | }
165 |
166 | getValidationMessage() {
167 | return this.getValidationMessage();
168 | }
169 |
170 | queue() {
171 | this._run('queue', ...arguments);
172 | }
173 |
174 | getQueueStep() {
175 | return this.sweetAlert.getQueueStep();
176 | }
177 |
178 | insertQueueStep() {
179 | this._run('insertQueueStep', ...arguments);
180 | }
181 |
182 | deleteQueueStep(index) {
183 | this._run('deleteQueueStep', index);
184 | }
185 |
186 | isValidParameter(param) {
187 | return this.sweetAlert.isValidParameter(param);
188 | }
189 |
190 | isUpdatableParameter(param) {
191 | return this.sweetAlert.isUpdatableParameter(param);
192 | }
193 |
194 | _run(method, ...args) {
195 | scheduleOnce('afterRender', this.sweetAlert, method, ...args);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/integration/components/sweet-alert-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 | import {
6 | open,
7 | confirmAndClose,
8 | cancelAndClose,
9 | } from 'ember-sweetalert/test-support';
10 |
11 | module('Integration | Component | sweet-alert', function (hooks) {
12 | setupRenderingTest(hooks);
13 |
14 | test('it renders', async function (assert) {
15 | await render(hbs`
16 |
20 | `);
21 |
22 | assert
23 | .dom('[data-test-swal]')
24 | .hasTagName('span')
25 | .hasAttribute('aria-hidden', 'true');
26 |
27 | assert.dom('.swal2-title').hasText('Any fool can use a computer');
28 |
29 | await confirmAndClose();
30 |
31 | assert.dom('.swal2-container').doesNotExist();
32 | });
33 |
34 | test('it has params', async function (assert) {
35 | await render(hbs`
36 |
41 | `);
42 |
43 | assert.dom('.swal2-title').hasText('The Internet?', 'title');
44 | assert
45 | .dom('.swal2-content')
46 | .hasText('That thing is still around?', 'content');
47 | assert.dom('.swal2-icon.swal2-question').hasClass('swal2-icon-show');
48 |
49 | await confirmAndClose();
50 |
51 | assert.dom('.swal2-container').doesNotExist();
52 | });
53 |
54 | test('it can be toggled', async function (assert) {
55 | this.set('isOpen', false);
56 |
57 | await render(hbs`
58 |
65 |
66 |
67 | `);
68 |
69 | assert.dom('.swal2-container').doesNotExist();
70 |
71 | await open('button');
72 |
73 | assert.strictEqual(this.isOpen, true);
74 | assert.dom('.swal2-title').hasText('The Internet?', 'title');
75 |
76 | await confirmAndClose();
77 |
78 | assert.dom('.swal2-container').doesNotExist();
79 | assert.strictEqual(this.isOpen, false);
80 |
81 | await open('button');
82 |
83 | assert.strictEqual(this.isOpen, true);
84 | assert.dom('.swal2-container').exists('it can be opened a second time');
85 |
86 | await confirmAndClose();
87 |
88 | assert.strictEqual(this.isOpen, false);
89 | });
90 |
91 | /**
92 | * The `onClose` action is deprecated and will be removed in the next
93 | * major release of SweetAlert2
94 | */
95 | test('it can be toggled using on-close', async function (assert) {
96 | this.set('isOpen', false);
97 |
98 | await render(hbs`
99 |
106 |
107 |
108 | `);
109 |
110 | assert.dom('.swal2-container').doesNotExist();
111 | await open('button');
112 | assert.dom('.swal2-title').hasText('The Internet?', 'title');
113 | await confirmAndClose();
114 | assert.dom('.swal2-container').doesNotExist();
115 | await open('button');
116 | assert.dom('.swal2-container').exists('it can be opened a second time');
117 | });
118 |
119 | test('it has a confirm action', async function (assert) {
120 | assert.expect(1);
121 | this.set('confirmed', ({ value }) => assert.ok(value, 'it was confirmed'));
122 | this.set('cancelled', () => assert.ok(false, 'it was cancelled'));
123 |
124 | await render(hbs`
125 |
133 | `);
134 |
135 | await confirmAndClose();
136 | });
137 |
138 | test('it has a cancel action', async function (assert) {
139 | this.set('confirmed', () => assert.ok(false, 'it was confirmed'));
140 | this.set('cancel', ({ dismiss }) => this.set('cancellation', dismiss));
141 |
142 | await render(hbs`
143 |
151 | `);
152 |
153 | await cancelAndClose();
154 |
155 | assert.equal(this.cancellation, 'cancel');
156 | });
157 | });
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Ember Sweet Alert
2 | ==============================================================================
3 |
4 | An [ember-cli](http://www.ember-cli.com/) addon for using
5 | [SweetAlert2](https://sweetalert2.github.io/) in Ember applications.
6 |
7 | Compatibility
8 | ------------------------------------------------------------------------------
9 |
10 | * Ember.js v3.16 or above
11 | * Ember CLI v2.13 or above
12 | * Node.js v10 or above
13 |
14 | Installation
15 | ------------------------------------------------------------------------------
16 |
17 | ```
18 | ember install ember-sweetalert
19 | ```
20 |
21 | IE11 requires the Babel polyfill to be present, otherwise you'll get a
22 | `Promise is undefined` error.
23 | [As per this comment](https://github.com/babel/ember-cli-babel/issues/40#issuecomment-268952820)
24 | you can add it via your `ember-cli-build.js` file as follows:
25 |
26 | ```js
27 | // ember-cli-build.js
28 | let app = new EmberApp(defaults, {
29 | 'ember-cli-babel': {
30 | includePolyfill: true
31 | }
32 | });
33 | ```
34 |
35 |
36 | Usage
37 | ------------------------------------------------------------------------------
38 |
39 | ### In your templates
40 |
41 | #### Basic Usage
42 |
43 | The `sweet-alert` component allows setting SweetAlert's attributes.
44 |
45 | ```hbs
46 |
47 | ```
48 |
49 | By default the alert will be open as soon as the template is rendered. See below
50 | for controlling whether the alert is open.
51 |
52 | #### Configuration
53 |
54 | All Sweet Alert options [Sweet Alert configuration options](https://sweetalert2.github.io/#configuration)
55 | can also be passed in as arguments:
56 |
57 | ```hbs
58 |
65 | ```
66 |
67 | If there are defaults that you want to set for every alert, you can set these
68 | in your environment config, e.g.:
69 |
70 | ```js
71 | ENV['ember-sweetalert'] = {
72 | target: '#my-sweetalert',
73 | allowOutsideClick: false
74 | };
75 | ```
76 |
77 | #### Opening
78 |
79 | By default the alert will be open when the component is rendered. To control
80 | this behaviour, use the `show` attribute. For example to open the alert when
81 | a button is clicked:
82 |
83 | ```hbs
84 | {{! sayHello === false to start }}
85 |
91 |
92 |
93 | ```
94 |
95 | The Sweet Alert component follows the Data-Down, Action Up (DDAU) pattern.
96 | This means in the example above, the alert will only show once, as `sayHello`
97 | will remain `true` once the alert is closed. To allow an alert to be
98 | open/closed any number of times, use an action to set the show variable back
99 | to `false` once the alert is closed. For example:
100 |
101 | ```hbs
102 | {{! sayHello === false to start }}
103 |
110 |
111 |
112 | ```
113 |
114 | #### Actions
115 |
116 | The component supports all the Sweet Alert actions allowed via configuration:
117 | - `willOpen`
118 | - `didOpen`
119 | - `didRender`
120 | - `willClose`
121 | - `didClose`
122 | - `didDestroy`
123 |
124 | In addition, the component also supports the following two actions:
125 | - `onConfirm`: invoked if the user clicks the confirm button within the alert.
126 | - `onCancel`: invoked if the user closes the alert without confirmation.
127 |
128 | Both actions receive the return value from Sweet Alert.
129 |
130 | The following example collects an email from a user, giving them a different
131 | message based on whether they provided the email or cancelled:
132 |
133 | ```js
134 | import Component from '@glimmer/component';
135 | import { tracked } from '@glimmer/tracking';
136 | import { action } from '@ember/object';
137 |
138 | export default class JoinMailingListComponent extends Component {
139 | @tracked enterEmail = false;
140 | @tracked email;
141 | @tracked sayThankYou = false;
142 | @tracked didNotJoin = false;
143 |
144 | @action
145 | collectEmail() {
146 | this.enterEmail = true;
147 | }
148 |
149 | @action
150 | join({ value }) {
151 | this.email = value;
152 | this.enterEmail = false;
153 | this.sayThankYou = true;
154 | }
155 |
156 | @action
157 | didCancel() {
158 | this.enterEmail = false;
159 | this.didNotJoin = true;
160 | }
161 |
162 | @action
163 | reset() {
164 | this.enterEmail = false;
165 | this.email = null;
166 | this.sayThankYou = false;
167 | this.didNotJoin = false;
168 | }
169 | }
170 | ```
171 |
172 | ```hbs
173 |
174 |
175 |
184 |
185 |
192 |
193 |
199 | ```
200 |
201 | ### In your code
202 |
203 | #### Service
204 |
205 | The recommended way to use SweetAlert in your code is to inject the `swal`
206 | service and use the `fire` method. The service ensures your default
207 | SweetAlert config is used, plus integrates with the Ember run loop.
208 |
209 | Here is an example:
210 |
211 | ```js
212 | import Component from '@ember/component';
213 | import { inject as service } from '@ember/service';
214 | import { action } from '@ember/object';
215 |
216 | export default class DeleteModelComponent extends Component {
217 | @service swal;
218 |
219 | @action
220 | async confirm() {
221 | let { value } = await this.swal.fire({
222 | title: 'Are you sure?',
223 | showCancelButton: true
224 | });
225 |
226 | if (value) {
227 | this.args.model.destroyRecord();
228 | }
229 | }
230 | }
231 | ```
232 |
233 | The service also exposes the [SweetAlert methods](https://sweetalert2.github.io/#methods),
234 | scheduling any action methods on the Ember run loop.
235 |
236 | #### Import it
237 |
238 | If you really need to you can import SweetAlert easily with:
239 |
240 | ```js
241 | import Swal from 'sweetalert2';
242 | ```
243 |
244 | > Using SweetAlert directly as an import will not have your default settings
245 | and will not be run-loop aware.
246 |
247 | ### In your tests
248 |
249 | #### Setup
250 |
251 | You will need to set the target for Sweet Alert to the Ember testing `div`.
252 | Add the following to your environment config:
253 |
254 | ```js
255 | if (environment === 'test') {
256 | ENV.APP.rootElement = '#ember-testing';
257 | // ...
258 | ENV['ember-sweetalert'] = { target: ENV.APP.rootElement };
259 | }
260 | ```
261 |
262 | #### Test Helpers
263 |
264 | This addon provides a number of test helpers that can be used in acceptance or
265 | rendering tests.
266 |
267 | Test helpers can be imported from `ember-sweetalert/test-support`. The
268 | available helpers are:
269 |
270 | | Helper | Description |
271 | | :--- | :--- |
272 | | `open(target)` | Clicks the specified target and waits for Sweet Alert to open. |
273 | | `confirm` | Clicks the Sweet Alert confirm button. |
274 | | `confirmAndClose` | Clicks the Sweet Alert confirm button and waits for it to close. |
275 | | `cancel` | Clicks the Sweet Alert cancel button. |
276 | | `cancelAndClose` | Clicks the Sweet Alert cancel button and waits for it to close. |
277 | | `waitForOpen` | Wait for Sweet Alert to open. |
278 | | `waitForClose` | Wait for Sweet Alert to close. |
279 |
280 | An example acceptance test:
281 |
282 | ```js
283 | import { module, test } from 'qunit';
284 | import { visit, fillIn } from '@ember/test-helpers';
285 | import { setupApplicationTest } from 'ember-qunit';
286 | import { open, confirmAndClose } from 'ember-sweetalert/test-support';
287 |
288 | module('Acceptance | join mailing list', function(hooks) {
289 | setupApplicationTest(hooks);
290 |
291 | test('user can join mailing list', async function(assert) {
292 | await visit('/');
293 | await open('button.join');
294 | await fillIn('input[type="email"]', 'foo@example.com');
295 | await confirmAndClose();
296 |
297 | assert.dom('.email').hasText('Your email is: foo@example.com');
298 | });
299 | });
300 | ```
301 |
302 | Contributing
303 | ------------------------------------------------------------------------------
304 |
305 | See the [Contributing](CONTRIBUTING.md) guide for details.
306 |
307 |
308 | License
309 | ------------------------------------------------------------------------------
310 |
311 | This project is licensed under the [MIT License](LICENSE.md).
312 |
--------------------------------------------------------------------------------