Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #271 from ckeditor/t/261
Browse files Browse the repository at this point in the history
Other: Add support for multiple context elements in the `clickOutsideHandler` helper. Closes #261.

BREAKING CHANGE: The `clickOutsideHandler` helper's `contextElement` config option is now an `Array` named `contextElements`.
  • Loading branch information
oleq authored Jul 11, 2017
2 parents 403b64a + 183e1d0 commit 9da5bf7
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 16 deletions.
20 changes: 14 additions & 6 deletions src/bindings/clickoutsidehandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,29 @@
/* global document */

/**
* Handles a DOM `click` event outside of specified element and fires an action.
* Handles a DOM `click` event outside of specified elements and fires an action.
*
* Note that it is not handled by a `click` event, this is to avoid situation when click on some trigger
* opens and closes element at the same time.
*
* @param {Object} options Configuration options.
* @param {module:utils/dom/emittermixin~Emitter} options.emitter The emitter to which this behavior should be added.
* @param {Function} options.activator Function returning a `Boolean`, to determine whether handler is active.
* @param {HTMLElement} options.contextElement `HTMLElement` that clicking inside of which will not fire the callback.
* @param {Function} options.callback Function fired after clicking outside of a specified element.
* @param {Array.<HTMLElement>} options.contextElements `HTMLElement`s that clicking inside of any of them will not fire the callback.
* @param {Function} options.callback Function fired after clicking outside of specified elements.
*/
export default function clickOutsideHandler( { emitter, activator, callback, contextElement } ) {
export default function clickOutsideHandler( { emitter, activator, callback, contextElements } ) {
emitter.listenTo( document, 'mouseup', ( evt, { target } ) => {
if ( activator() && !contextElement.contains( target ) ) {
callback();
if ( !activator() ) {
return;
}

for ( const contextElement of contextElements ) {
if ( contextElement.contains( target ) ) {
return;
}
}

callback();
} );
}
25 changes: 16 additions & 9 deletions tests/bindings/clickoutsidehandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,28 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
testUtils.createSinonSandbox();

describe( 'clickOutsideHandler', () => {
let activator, actionSpy, contextElement;
let activator, actionSpy, contextElement1, contextElement2;

beforeEach( () => {
activator = testUtils.sinon.stub().returns( false );
contextElement = document.createElement( 'div' );
contextElement1 = document.createElement( 'div' );
contextElement2 = document.createElement( 'div' );
actionSpy = testUtils.sinon.spy();

document.body.appendChild( contextElement );
document.body.appendChild( contextElement1 );
document.body.appendChild( contextElement2 );

clickOutsideHandler( {
emitter: Object.create( DomEmitterMixin ),
activator,
contextElement,
contextElements: [ contextElement1, contextElement2 ],
callback: actionSpy
} );
} );

afterEach( () => {
document.body.removeChild( contextElement );
document.body.removeChild( contextElement1 );
document.body.removeChild( contextElement2 );
} );

it( 'should fired callback after clicking out of context element when listener is active', () => {
Expand All @@ -54,16 +57,20 @@ describe( 'clickOutsideHandler', () => {
it( 'should not fired callback after clicking on context element when listener is active', () => {
activator.returns( true );

contextElement.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) );
contextElement1.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) );
sinon.assert.notCalled( actionSpy );

contextElement2.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) );
sinon.assert.notCalled( actionSpy );
} );

it( 'should not fired callback after clicking on context element when listener is not active', () => {
activator.returns( false );

contextElement.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) );
contextElement1.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) );
sinon.assert.notCalled( actionSpy );

contextElement2.dispatchEvent( new Event( 'mouseup', { bubbles: true } ) );
sinon.assert.notCalled( actionSpy );
} );

Expand All @@ -75,7 +82,7 @@ describe( 'clickOutsideHandler', () => {
clickOutsideHandler( {
emitter: Object.create( DomEmitterMixin ),
activator,
contextElement,
contextElements: [ contextElement1 ],
callback: spy
} );

Expand All @@ -92,7 +99,7 @@ describe( 'clickOutsideHandler', () => {
clickOutsideHandler( {
emitter: Object.create( DomEmitterMixin ),
activator,
contextElement,
contextElements: [ contextElement1 ],
callback: spy
} );

Expand Down
2 changes: 1 addition & 1 deletion tests/manual/contextualballoon/contextualballoon.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class PluginGeneric extends Plugin {
clickOutsideHandler( {
emitter: this.view,
activator: () => this._isVisible,
contextElement: this.view.element,
contextElements: [ this.view.element ],
callback: () => this._hidePanel()
} );
}
Expand Down

0 comments on commit 9da5bf7

Please sign in to comment.