Skip to content

Commit

Permalink
Add isDismissDisabled property
Browse files Browse the repository at this point in the history
Switch from checking the callback function return to checking a `isDismissDisabled` property before dismissing a modal dialog
  • Loading branch information
alex-ju committed Nov 21, 2022
1 parent 5e281e9 commit e6d88d5
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 26 deletions.
17 changes: 9 additions & 8 deletions packages/components/addon/components/hds/modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const COLORS = ['neutral', 'warning', 'critical'];

export default class HdsModalIndexComponent extends Component {
@tracked isOpen = false;
@tracked isDismissDisabled = this.args.isDismissDisabled ?? false;

/**
* Sets the size of the modal dialog
Expand Down Expand Up @@ -100,17 +101,17 @@ export default class HdsModalIndexComponent extends Component {
// Register "onClose" callback function to be called when a native 'close' event is dispatched
this.element.addEventListener('close', () => {
if (this.args.onClose && typeof this.args.onClose === 'function') {
if (this.args.onClose() === false) {
// If the callback function returns `false`, we keep the modal open/visible
// As there is no way to `preventDefault` on `close` events, we call the `showModal` function
// preserving the state of the modal dialog
this.args.onClose();
}

// If the dismissal of the modal is disabled, we keep the modal open/visible otherwise we mark it as closed
if (this.isDismissDisabled) {
// If, in a chain of events, the element is not attached to the DOM, the `showModal` would fail
// so we add this safeguard condition that checks for the `<dialog>` to have a parent
if (this.element.parentElement) {
this.element.showModal();
} else {
// If the callback function returns `true` or is `undefined` we mark the modal as closed
this.isOpen = false;
}
} else {
// If there is no callback function we mark the modal as closed
this.isOpen = false;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,35 @@ import { tracked } from '@glimmer/tracking';
export default class ModalController extends Controller {
@tracked basicModalActive = false;
@tracked formModalActive = false;
@tracked formChanged = false;
@tracked isModalDismissDisabled = false;

@action
activateModal(modal) {
this.formChanged = false;
this.isModalDismissDisabled = false;
this[modal] = true;
document.body.style.overflow = 'hidden';
}

@action
deactivateModal(modal) {
this.isModalDismissDisabled = false;
this[modal] = false;
document.body.style.overflow = 'auto';
}

@action markFormAsChanged() {
this.formChanged = true;
this.isModalDismissDisabled = true;
}

@action saveFormAndClose(modal) {
this.isModalDismissDisabled = false;
this.deactivateModal(modal);
}

@action checkBeforeDeactivate(modal) {
if (this.formChanged) {
if (this.isModalDismissDisabled) {
if (window.confirm('Changes that you made may not be saved')) {
this.deactivateModal(modal);
} else {
return false;
}
} else {
this.deactivateModal(modal);
Expand Down
38 changes: 29 additions & 9 deletions packages/components/tests/dummy/app/templates/components/modal.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,18 @@
<p>
Callback function invoked when the modal is closed.
</p>
<p>If you need to prevent the modal from closing (for instance, to avoid accidental data loss in an unsubmitted
form) return
<code class="dummy-code">false</code>
on this callback function.</p>
</dd>
<dt>
isDismissDisabled
<code>boolean</code>
</dt>
<dd>
<p>Default: <span class="default">false</span></p>
<p>Set this boolean to
<code class="dummy-code">true</code>
if you want to prevent the modal from being closed (for instance, to avoid accidental data loss in an
unsubmitted form). Make sure you communicate to users the reason why the modal is still open.
</p>
</dd>
<dt>...attributes</dt>
<dd>
Expand Down Expand Up @@ -250,12 +258,17 @@
/>
\{{#if this.formModalActive}}
<Hds::Modal id="form-modal" @onClose=\{{fn this.checkBeforeDeactivate "formModalActive"}} as |M|>
<Hds::Modal
id="form-modal"
@isDismissDisabled=\{{this.isModalDismissDisabled}}
@onClose=\{{fn this.checkBeforeDeactivate "formModalActive"}}
as |M|
>
<M.Header>
Why do you want to leave the beta?
</M.Header>
<M.Body>
<form \{{on "change" this.markFormAsChanged)}} name="leaving-beta-form" method="dialog">
<form \{{on "change" this.markFormAsChanged}} name="leaving-beta-form">
<Hds::Form::Select::Field autofocus @width="100%" as |F|>
<F.Label>Select the primary reason</F.Label>
<F.Options>
Expand All @@ -269,7 +282,9 @@
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button type="submit" @text="Leave Beta" />
<Hds::Button type="submit" @text="Leave Beta"
\{{on "click" (fn this.saveFormAndClose "formModalActive")}}
/>
<Hds::Button type="button" @text="Cancel" @color="secondary" \{{on "click" F.close}} />
</Hds::ButtonSet>
</M.Footer>
Expand All @@ -284,7 +299,12 @@

{{! template-lint-disable no-autofocus-attribute }}
{{#if this.formModalActive}}
<Hds::Modal id="form-modal" @onClose={{fn this.checkBeforeDeactivate "formModalActive"}} as |M|>
<Hds::Modal
id="form-modal"
@isDismissDisabled={{this.isModalDismissDisabled}}
@onClose={{fn this.checkBeforeDeactivate "formModalActive"}}
as |M|
>
<M.Header>
Why do you want to leave the beta?
</M.Header>
Expand All @@ -303,7 +323,7 @@
</M.Body>
<M.Footer as |F|>
<Hds::ButtonSet>
<Hds::Button type="submit" @text="Leave Beta" />
<Hds::Button type="submit" @text="Leave Beta" {{on "click" (fn this.saveFormAndClose "formModalActive")}} />
<Hds::Button type="button" @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
</M.Footer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { module, test, skip } from 'qunit';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render, resetOnerror, setupOnerror } from '@ember/test-helpers';
import {
click,
render,
resetOnerror,
setupOnerror,
settled,
} from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Component | hds/modal/index', function (hooks) {
Expand Down Expand Up @@ -123,6 +129,19 @@ module('Integration | Component | hds/modal/index', function (hooks) {
await click('#cancel-button');
assert.dom('#test-modal').isNotVisible();
});
test('it should not close the modal when `@isDismissDisabled` is `true`', async function (assert) {
await render(
hbs`<Hds::Modal @isDismissDisabled={{true}} id="test-modal-dismiss-disabled" as |M|>
<M.Footer as |F|>
<Hds::Button id="cancel-button" type="button" @text="Cancel" @color="secondary" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>`
);
assert.dom('#test-modal-dismiss-disabled').isVisible();
await settled();
await click('#cancel-button');
assert.dom('#test-modal-dismiss-disabled').isVisible();
});

// ACCESSIBILITY

Expand Down Expand Up @@ -186,10 +205,12 @@ module('Integration | Component | hds/modal/index', function (hooks) {
<M.Header>Title</M.Header>
</Hds::Modal>`
);
assert.dom('#test-modal-onopen-callback').isVisible();
await settled();
assert.ok(opened);
});
// the following test is flakey so we're going to skip it until we find a more reliable way
skip('it should call `onClose` function if provided', async function (assert) {
test('it should call `onClose` function if provided', async function (assert) {
let closed = false;
this.set('onClose', () => (closed = true));
await render(
Expand All @@ -198,6 +219,8 @@ module('Integration | Component | hds/modal/index', function (hooks) {
</Hds::Modal>`
);
await click('#test-modal-onclose-callback button.hds-modal__dismiss');
assert.dom('#test-modal-onclose-callback').isNotVisible();
await settled();
assert.ok(closed);
});

Expand Down

0 comments on commit e6d88d5

Please sign in to comment.