diff --git a/CHANGELOG.md b/CHANGELOG.md index 975786c8..438d5b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.6 +- [DEPRECATION] Deprecate onOpen and onClose hooks. Suggest to use onBeforeOpen and onBeforeClose +- [ENHANCEMENT] Add onAfterOpen and onAfterClose hooks that are called after changes in the dropdown state + # 2.0.5 - [CHORE] Update npm packages to tests pass in beta and canary - [BUGFIX] Ensure Ember doesn't complain about not using `set` to update values. diff --git a/addon/components/basic-dropdown.js b/addon/components/basic-dropdown.js index b30dc503..f60a7d46 100644 --- a/addon/components/basic-dropdown.js +++ b/addon/components/basic-dropdown.js @@ -10,6 +10,7 @@ import templateLayout from '../templates/components/basic-dropdown'; import calculatePosition from '../utils/calculate-position'; import { assign } from '@ember/polyfills'; import requirejs from 'require'; +import { deprecate } from '@ember/application/deprecations'; const ignoredStyleAttrs = [ 'top', @@ -89,10 +90,23 @@ export default @layout(templateLayout) @tagName('') class BasicDropdown extends if (this.publicAPI.disabled || this.publicAPI.isOpen) { return; } - if (this.onOpen && this.onOpen(this.publicAPI, e) === false) { + if (this.onOpen) { + deprecate("onOpen is deprecated and will be removed in the future. Please use onBeforeOpen instead", false, { + id: 'ember-basic-dropdown.onOpen', + until: '3.0.0', + url: 'https://ember-basic-dropdown.com/docs/dropdown-events', + }); + if (this.onOpen(this.publicAPI, e) === false) { + return; + } + } + if (this.onBeforeOpen && this.onBeforeOpen(this.publicAPI, e) === false) { return; } this.updateState({ isOpen: true }); + if (this.onAfterOpen) { + this.onAfterOpen(this.publicAPI, e); + } } close(e, skipFocus) { @@ -102,7 +116,17 @@ export default @layout(templateLayout) @tagName('') class BasicDropdown extends if (this.publicAPI.disabled || !this.publicAPI.isOpen) { return; } - if (this.onClose && this.onClose(this.publicAPI, e) === false) { + if (this.onClose) { + deprecate("onClose is deprecated and will be removed in the future. Please use onBeforeClose instead", false, { + id: 'ember-basic-dropdown.onClose', + until: '3.0.0', + url: 'https://ember-basic-dropdown.com/docs/dropdown-events', + }); + if (this.onClose(this.publicAPI, e) === false) { + return; + } + } + if (this.onBeforeClose && this.onBeforeClose(this.publicAPI, e) === false) { return; } if (this.isDestroyed) { @@ -111,6 +135,10 @@ export default @layout(templateLayout) @tagName('') class BasicDropdown extends this.setProperties({ hPosition: null, vPosition: null, top: null, left: null, right: null, width: null, height: null }); this.previousVerticalPosition = this.previousHorizontalPosition = null; this.updateState({ isOpen: false }); + // NOTE: onAfterClose isn't going to be called if the dropdown was destroyed + if (this.onAfterClose) { + this.onAfterClose(this.publicAPI, e); + } if (skipFocus) { return; } diff --git a/package.json b/package.json index 2f0f8845..29785494 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ember-basic-dropdown", - "version": "2.0.5", - "description": "The default blueprint for ember-cli addons.", + "version": "2.0.6", + "description": "The basic dropdown that your ember needs", "homepage": "http://ember-basic-dropdown.com", "scripts": { "build": "ember build", diff --git a/tests/dummy/app/templates/public-pages/docs/api-reference.hbs b/tests/dummy/app/templates/public-pages/docs/api-reference.hbs index cdda9e89..ad2b2e14 100644 --- a/tests/dummy/app/templates/public-pages/docs/api-reference.hbs +++ b/tests/dummy/app/templates/public-pages/docs/api-reference.hbs @@ -89,14 +89,34 @@ An action that will be invoked with the new public API of the component every time there is a change in the state of the component. - onOpen + onBeforeOpen Function Action that will be called when the component is about to open. Returning false from this function will prevent the component from being opened. - onClose + onAfterOpen + Function + Action that will be called when the component has finished opening + + + onBeforeClose + Function + Action that will be called when the component is about to close. Returning false from this function will prevent the component from being opened. + + + onAfterClose + Function + Action that will be called when the component has finished closing only if the dropdown hasn't been destroyed. + + + onOpen + Function + [DEPRECATED] Action that will be called when the component is about to open. Returning false from this function will prevent the component from being opened. + + + onClose Function - Action that will be called when the component is about to close. Returning false from this function will prevent the component from being closed. + [DEPRECATED] Action that will be called when the component is about to close. Returning false from this function will prevent the component from being closed. @@ -219,4 +239,4 @@
< Test helpers -
\ No newline at end of file + diff --git a/tests/dummy/app/templates/public-pages/docs/dropdown-events.hbs b/tests/dummy/app/templates/public-pages/docs/dropdown-events.hbs index 99971d09..138e4394 100644 --- a/tests/dummy/app/templates/public-pages/docs/dropdown-events.hbs +++ b/tests/dummy/app/templates/public-pages/docs/dropdown-events.hbs @@ -9,7 +9,7 @@ This has no mystery.

-

onOpen(dropdown, event?)

+

onBeforeOpen(dropdown, event?)

Pretty self-explanatory. The dropdown argument in the signature @@ -46,7 +46,7 @@

For the record, I don't think this is good UX

-

onClose(dropdown, event?)

+

onBeforeClose(dropdown, event?)

Symmetrically, you can perform on action when the dropdown is closed. For example, save @@ -65,6 +65,19 @@

Cruel, isn't it?

+

onAfterOpen(dropdown, event?)

+ +

+ In addition to the before hooks mentioned above, there are accompanying hooks that get called immediately after state has been changed in the dropdown. The dropdown argument is the updated public API of the component that reflects these changes in state. +

+ +

onAfterClose(dropdown, event?)

+ +

+ There is something you should know about this hook: If the dropdown has been destroyed, + this will not get called. +

+

Those were the events fired by the top-level component, now let's go deep on the events that are fired by the trigger. diff --git a/tests/dummy/app/templates/snippets/custom-position-2.hbs b/tests/dummy/app/templates/snippets/custom-position-2.hbs index af89f59a..8c9b53ff 100644 --- a/tests/dummy/app/templates/snippets/custom-position-2.hbs +++ b/tests/dummy/app/templates/snippets/custom-position-2.hbs @@ -1,4 +1,4 @@ - + Click me @@ -6,4 +6,4 @@ {{name}}
{{/each}}
-
\ No newline at end of file +
diff --git a/tests/dummy/app/templates/snippets/dropdown-events-1.hbs b/tests/dummy/app/templates/snippets/dropdown-events-1.hbs index 89a50556..c530c7e9 100644 --- a/tests/dummy/app/templates/snippets/dropdown-events-1.hbs +++ b/tests/dummy/app/templates/snippets/dropdown-events-1.hbs @@ -1,4 +1,4 @@ - + Click me @@ -10,4 +10,4 @@ {{/each}} {{/if}} - \ No newline at end of file + diff --git a/tests/dummy/app/templates/snippets/dropdown-events-2.hbs b/tests/dummy/app/templates/snippets/dropdown-events-2.hbs index b9cb4448..edfc9e8c 100644 --- a/tests/dummy/app/templates/snippets/dropdown-events-2.hbs +++ b/tests/dummy/app/templates/snippets/dropdown-events-2.hbs @@ -1,4 +1,4 @@ - + {{#if this.loadUsersAndOpen.isRunning}} Loading... @@ -12,4 +12,4 @@

{{user.name}} - {{user.assignment}}

{{/each}} - \ No newline at end of file + diff --git a/tests/dummy/app/templates/snippets/dropdown-events-3.hbs b/tests/dummy/app/templates/snippets/dropdown-events-3.hbs index f036c906..9d01c6b2 100644 --- a/tests/dummy/app/templates/snippets/dropdown-events-3.hbs +++ b/tests/dummy/app/templates/snippets/dropdown-events-3.hbs @@ -1,4 +1,4 @@ - + Click me @@ -14,4 +14,4 @@

-
\ No newline at end of file +
diff --git a/tests/integration/components/basic-dropdown-test.js b/tests/integration/components/basic-dropdown-test.js index 42032bfc..773f9c0d 100644 --- a/tests/integration/components/basic-dropdown-test.js +++ b/tests/integration/components/basic-dropdown-test.js @@ -93,17 +93,17 @@ module('Integration | Component | basic-dropdown', function(hooks) { assert.dom('#dropdown-is-opened').doesNotExist('The dropdown is still closed'); }); - test('It can receive an onOpen action that is fired just before the component opens', async function(assert) { + test('It can receive an onBeforeOpen action that is fired just before the component opens', async function(assert) { assert.expect(4); this.willOpen = function(dropdown, e) { assert.equal(dropdown.isOpen, false, 'The received dropdown has a `isOpen` property that is still false'); assert.ok(Object.prototype.hasOwnProperty.call(dropdown, 'actions'), 'The received dropdown has a `actions` property'); assert.ok(!!e, 'Receives an argument as second argument'); - assert.ok(true, 'onOpen action was invoked'); + assert.ok(true, 'onBeforeOpen action was invoked'); }; await render(hbs` - + {{#if dropdown.isOpen}} @@ -114,7 +114,28 @@ module('Integration | Component | basic-dropdown', function(hooks) { await click('.ember-basic-dropdown-trigger'); }); - test('returning false from the `onOpen` action prevents the dropdown from opening', async function(assert) { + test('It can receive an onAfterOpen action that is fired just after the component opens', async function(assert) { + assert.expect(4); + + this.willOpen = function(dropdown, e) { + assert.equal(dropdown.isOpen, true, 'The received dropdown has a `isOpen` property that now true'); + assert.ok(Object.prototype.hasOwnProperty.call(dropdown, 'actions'), 'The received dropdown has a `actions` property'); + assert.ok(!!e, 'Receives an argument as second argument'); + assert.ok(true, 'onAfterOpen action was invoked'); + }; + await render(hbs` + + + {{#if dropdown.isOpen}} + + {{/if}} + + `); + + await click('.ember-basic-dropdown-trigger'); + }); + + test('returning false from the `onBeforeOpen` action prevents the dropdown from opening', async function(assert) { assert.expect(2); this.willOpen = function() { @@ -122,7 +143,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { return false; }; await render(hbs` - + {{#if dropdown.isOpen}} @@ -134,17 +155,17 @@ module('Integration | Component | basic-dropdown', function(hooks) { assert.dom('#dropdown-is-opened').doesNotExist('The dropdown is still closed'); }); - test('It can receive an onClose action that is fired when the component closes', async function(assert) { + test('It can receive an onBeforeClose action that is fired just before the component closes', async function(assert) { assert.expect(7); this.willClose = function(dropdown, e) { assert.equal(dropdown.isOpen, true, 'The received dropdown has a `isOpen` property and its value is `true`'); assert.ok(Object.prototype.hasOwnProperty.call(dropdown, 'actions'), 'The received dropdown has a `actions` property'); assert.ok(!!e, 'Receives an argument as second argument'); - assert.ok(true, 'onClose action was invoked'); + assert.ok(true, 'onBeforeClose action was invoked'); }; await render(hbs` - + {{#if dropdown.isOpen}} @@ -156,10 +177,35 @@ module('Integration | Component | basic-dropdown', function(hooks) { await click('.ember-basic-dropdown-trigger'); assert.dom('#dropdown-is-opened').exists('The dropdown is opened'); await click('.ember-basic-dropdown-trigger'); - assert.dom('#dropdown-is-opened').doesNotExist('The dropdown is now opened'); + assert.dom('#dropdown-is-opened').doesNotExist('The dropdown is now closed'); }); - test('returning false from the `onClose` action prevents the dropdown from closing', async function(assert) { + test('It can receive an onAfterClose action that is fired just after the component closes', async function(assert) { + assert.expect(7); + + this.willClose = function(dropdown, e) { + assert.equal(dropdown.isOpen, false, 'The received dropdown has a `isOpen` property and its value is `false`'); + assert.ok(Object.prototype.hasOwnProperty.call(dropdown, 'actions'), 'The received dropdown has a `actions` property'); + assert.ok(!!e, 'Receives an argument as second argument'); + assert.ok(true, 'onAfterClose action was invoked'); + }; + await render(hbs` + + + {{#if dropdown.isOpen}} + + {{/if}} + + `); + + assert.dom('#dropdown-is-opened').doesNotExist('The dropdown is closed'); + await click('.ember-basic-dropdown-trigger'); + assert.dom('#dropdown-is-opened').exists('The dropdown is opened'); + await click('.ember-basic-dropdown-trigger'); + assert.dom('#dropdown-is-opened').doesNotExist('The dropdown is now closed'); + }); + + test('returning false from the `onBeforeClose` action prevents the dropdown from closing', async function(assert) { assert.expect(4); this.willClose = function() { @@ -167,7 +213,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { return false; }; await render(hbs` - + {{#if dropdown.isOpen}} @@ -196,7 +242,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { assert.dom('#dropdown-is-opened').exists('The dropdown is opened'); }); - test('Calling the `open` method while the dropdown is already opened does not call `onOpen` action', async function(assert) { + test('Calling the `open` method while the dropdown is already opened does not call `onBeforeOpen` action', async function(assert) { assert.expect(1); let onOpenCalls = 0; this.onOpen = () => { @@ -204,7 +250,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { }; await render(hbs` - + {{#if dropdown.isOpen}} @@ -217,7 +263,52 @@ module('Integration | Component | basic-dropdown', function(hooks) { assert.equal(onOpenCalls, 1, 'onOpen has been called only once'); }); - test('Calling the `close` method while the dropdown is already opened does not call `onOpen` action', async function(assert) { + test('Calling the `open` method while the dropdown is already opened does not call `onAfterOpen` action', async function(assert) { + assert.expect(1); + let onOpenCalls = 0; + this.onOpen = () => { + onOpenCalls++; + }; + + await render(hbs` + + + {{#if dropdown.isOpen}} + + {{/if}} + + `); + await click('.ember-basic-dropdown-trigger'); + await click('.ember-basic-dropdown-trigger'); + await click('.ember-basic-dropdown-trigger'); + assert.equal(onOpenCalls, 1, 'onOpen has been called only once'); + }); + + test('Calling the `close` method while the dropdown is already closed does not call `onBeforeClose` action', async function(assert) { + assert.expect(1); + let onCloseCalls = 0; + this.onFocus = (dropdown) => { + dropdown.actions.close(); + }; + this.onClose = () => { + onCloseCalls++; + }; + + await render(hbs` + + + {{#if dropdown.isOpen}} + + {{/if}} + + `); + await click('.ember-basic-dropdown-trigger'); + await click('.ember-basic-dropdown-trigger'); + await click('.ember-basic-dropdown-trigger'); + assert.equal(onCloseCalls, 0, 'onBeforeClose was never called'); + }); + + test('Calling the `close` method while the dropdown is already closed does not call `onAfterClose` action', async function(assert) { assert.expect(1); let onCloseCalls = 0; this.onFocus = (dropdown) => { @@ -228,7 +319,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { }; await render(hbs` - + {{#if dropdown.isOpen}} @@ -238,7 +329,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { await click('.ember-basic-dropdown-trigger'); await click('.ember-basic-dropdown-trigger'); await click('.ember-basic-dropdown-trigger'); - assert.equal(onCloseCalls, 0, 'onClose was never called'); + assert.equal(onCloseCalls, 0, 'onAfterClose was never called'); }); test('It adds the proper class to trigger and content when it receives `horizontalPosition="right"`', async function(assert) { @@ -726,7 +817,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { assert.equal(apis[3].disabled, true, 'and it became disabled'); }); - test('removing the dropdown in response to onClose does not error', async function(assert) { + test('removing the dropdown in response to onBeforeClose does not error', async function(assert) { assert.expect(2); this.isOpen = true; @@ -737,7 +828,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { await render(hbs` {{#if isOpen}} - + Open me

Content of the dropdown

@@ -824,7 +915,7 @@ module('Integration | Component | basic-dropdown', function(hooks) { } await render(hbs` - + Open me