Skip to content

Commit

Permalink
Chore: Refactoring Controls for mobile (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy Press authored Jun 7, 2017
1 parent a0fc5ae commit 98ad9cc
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 27 deletions.
46 changes: 30 additions & 16 deletions src/lib/Controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ class Controls {
this.controlsEl = this.controlsWrapperEl.appendChild(document.createElement('div'));
this.controlsEl.className = 'bp-controls';

this.containerEl.addEventListener('mousemove', this.mousemoveHandler);
this.controlsEl.addEventListener('mouseenter', this.mouseenterHandler);
this.controlsEl.addEventListener('mouseleave', this.mouseleaveHandler);
this.controlsEl.addEventListener('focusin', this.focusinHandler);
this.controlsEl.addEventListener('focusout', this.focusoutHandler);
// Bind event handlers
this.mousemoveHandler = throttle(this.mousemoveHandler.bind(this), CONTROLS_AUTO_HIDE_TIMEOUT_IN_MILLIS - 500);
this.moouseenterHandler = this.mouseenterHandler.bind(this);
this.mouseleaveHandler = this.mouseleaveHandler.bind(this);
this.focusinHandler = this.focusinHandler.bind(this);
this.focusoutHandler = this.focusoutHandler.bind(this);

this.bindControlListeners();
}

/**
Expand All @@ -47,6 +50,14 @@ class Controls {
});
}

bindControlListeners() {
this.containerEl.addEventListener('mousemove', this.mousemoveHandler);
this.controlsEl.addEventListener('mouseenter', this.mouseenterHandler);
this.controlsEl.addEventListener('mouseleave', this.mouseleaveHandler);
this.controlsEl.addEventListener('focusin', this.focusinHandler);
this.controlsEl.addEventListener('focusout', this.focusoutHandler);
}

/**
* Checks if the button is a preview controls button
*
Expand All @@ -55,7 +66,10 @@ class Controls {
* @return {boolean} true if element is a preview control button
*/
isPreviewControlButton(element) {
return !!element && element.classList.contains('bp-controls-btn');
return (
!!element &&
(element.classList.contains('bp-controls-btn') || element.parentNode.classList.contains('bp-controls-btn'))
);
}

/**
Expand Down Expand Up @@ -85,56 +99,56 @@ class Controls {
* @private
* @return {void}
*/
mousemoveHandler = throttle(() => {
mousemoveHandler() {
this.containerEl.classList.add(SHOW_PREVIEW_CONTROLS_CLASS);
this.resetTimeout();
}, CONTROLS_AUTO_HIDE_TIMEOUT_IN_MILLIS - 500);
}

/**
* Mouse enter handler
*
* @private
* @return {void}
*/
mouseenterHandler = () => {
mouseenterHandler() {
this.blockHiding = true;
};
}

/**
* Mouse leave handler
*
* @private
* @return {void}
*/
mouseleaveHandler = () => {
mouseleaveHandler() {
this.blockHiding = false;
};
}

/**
* Handles all focusin events for the module.
*
* @param {Event} event - A DOM-normalized event object.
* @return {void}
*/
focusinHandler = (event) => {
focusinHandler(event) {
// When we focus onto a preview control button, show controls
if (this.isPreviewControlButton(event.target)) {
this.containerEl.classList.add(SHOW_PREVIEW_CONTROLS_CLASS);
}
};
}

/**
* Handles all focusout events for the module.
*
* @param {Event} event - A DOM-normalized event object.
* @return {void}
*/
focusoutHandler = (event) => {
focusoutHandler(event) {
// When we focus out of a control button and aren't focusing onto another control button, hide the controls
if (this.isPreviewControlButton(event.target) && !this.isPreviewControlButton(event.relatedTarget)) {
this.containerEl.classList.remove(SHOW_PREVIEW_CONTROLS_CLASS);
}
};
}

/**
* Adds buttons to controls
Expand Down
4 changes: 1 addition & 3 deletions src/lib/Controls.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
transition: opacity .5s;
}

.box-show-preview-controls .bp-controls,
.bp-controls:hover,
.bp-controls:focus {
.box-show-preview-controls .bp-controls {
opacity: 1;
}

Expand Down
70 changes: 70 additions & 0 deletions src/lib/MobileControls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Controls from './Controls';

const SHOW_PREVIEW_CONTROLS_CLASS = 'box-show-preview-controls';

class MobileControls extends Controls {
/**
* [destructor]
* @return {void}
*/
destroy() {
this.containerEl.removeEventListener('touchstart', this.mousemoveHandler);
this.containerEl.removeEventListener('touchmove', this.mousemoveHandler);
this.controlsEl.removeEventListener('touchmove', this.mousemoveHandler);
this.controlsEl.addEventListener('focusin', this.focusinHandler);
this.controlsEl.addEventListener('focusout', this.focusoutHandler);
this.controlsEl.removeEventListener('click', this.clickHandler);

super.destroy();
}

/** Bind event listeners for mobile controls.
* @return {void}
*/
bindControlListeners() {
this.containerEl.addEventListener('touchstart', this.mousemoveHandler);
this.containerEl.addEventListener('touchmove', this.mousemoveHandler);
this.controlsEl.addEventListener('focusin', this.focusinHandler);
this.controlsEl.addEventListener('focusout', this.focusoutHandler);
this.controlsEl.addEventListener('click', this.clickHandler);
}

/**
* Handles all focusin events for the module.
*
* @param {Event} event - A DOM-normalized event object.
* @return {void}
*/
focusinHandler(event) {
// When we focus onto a preview control button, show controls
if (this.isPreviewControlButton(event.target)) {
this.containerEl.classList.add(SHOW_PREVIEW_CONTROLS_CLASS);
this.blockHiding = true;
}
}

/**
* Handles all focusout events for the module.
*
* @param {Event} event - A DOM-normalized event object.
* @return {void}
*/
focusoutHandler(event) {
// When we focus out of a control button and aren't focusing onto another control button, hide the controls
if (this.isPreviewControlButton(event.target) && !this.isPreviewControlButton(event.relatedTarget)) {
this.containerEl.classList.remove(SHOW_PREVIEW_CONTROLS_CLASS);
this.blockHiding = false;
}
}

/** Prevent zooming on the control bar.
* @param {Event} event - A DOM-normalized event object.
* @return {void}
*/
clickHandler(event) {
event.preventDefault();
event.stopPropagation();
}
}

export default MobileControls;
39 changes: 31 additions & 8 deletions src/lib/__tests__/Controls-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ let controls;
let clock;

const sandbox = sinon.sandbox.create();
let stubs;

const SHOW_PREVIEW_CONTROLS_CLASS = 'box-show-preview-controls';

Expand All @@ -17,6 +18,7 @@ describe('lib/Controls', () => {
beforeEach(() => {
fixture.load('__tests__/Controls-test.html');
controls = new Controls(document.getElementById('test-controls-container'));
stubs = {};
});

afterEach(() => {
Expand All @@ -28,6 +30,7 @@ describe('lib/Controls', () => {
}

controls = null;
stubs = null;
});

describe('constructor()', () => {
Expand All @@ -44,15 +47,15 @@ describe('lib/Controls', () => {

describe('destroy()', () => {
it('should remove the correct event listeners', () => {
const containerElEventListener = sandbox.stub(controls.containerEl, 'removeEventListener');
const controlsElEventListener = sandbox.stub(controls.controlsEl, 'removeEventListener');
stubs.containerElEventListener = sandbox.stub(controls.containerEl, 'removeEventListener');
stubs.controlsElEventListener = sandbox.stub(controls.controlsEl, 'removeEventListener');

controls.destroy();
expect(containerElEventListener).to.be.calledWith('mousemove', controls.mousemoveHandler);
expect(controlsElEventListener).to.be.calledWith('mouseenter', controls.mouseenterHandler);
expect(controlsElEventListener).to.be.calledWith('mouseleave', controls.mouseleaveHandler);
expect(controlsElEventListener).to.be.calledWith('focusin', controls.focusinHandler);
expect(controlsElEventListener).to.be.calledWith('focusout', controls.focusoutHandler);
expect(stubs.containerElEventListener).to.be.calledWith('mousemove', controls.mousemoveHandler);
expect(stubs.controlsElEventListener).to.be.calledWith('mouseenter', controls.mouseenterHandler);
expect(stubs.controlsElEventListener).to.be.calledWith('mouseleave', controls.mouseleaveHandler);
expect(stubs.controlsElEventListener).to.be.calledWith('focusin', controls.focusinHandler);
expect(stubs.controlsElEventListener).to.be.calledWith('focusout', controls.focusoutHandler);
});

it('should remove click listeners for any button references', () => {
Expand All @@ -72,15 +75,35 @@ describe('lib/Controls', () => {
});
});

describe('bindControlListeners()', () => {
it('should add the correct event listeners', () => {
stubs.addContainerElListener = sandbox.stub(controls.containerEl, 'addEventListener');
stubs.addControlsElListener = sandbox.stub(controls.controlsEl, 'addEventListener');

controls.bindControlListeners();
expect(stubs.addContainerElListener).to.be.calledWith('mousemove', controls.mousemoveHandler);
expect(stubs.addControlsElListener).to.be.calledWith('mouseenter', controls.mouseenterHandler);
expect(stubs.addControlsElListener).to.be.calledWith('mouseleave', controls.mouseleaveHandler);
expect(stubs.addControlsElListener).to.be.calledWith('focusin');
expect(stubs.addControlsElListener).to.be.calledWith('focusout');
});
});

describe('isPreviewControlButton()', () => {
it('should determine whether the element is a preview control button', () => {
let element = null;
let parent = null;
expect(controls.isPreviewControlButton(element)).to.be.false;

element = document.createElement('div');
parent = document.createElement('div');
element = parent.appendChild(document.createElement('div'));
element.className = 'bp-controls-btn';
expect(controls.isPreviewControlButton(element)).to.be.true;

parent.className = 'bp-controls-btn';
expect(controls.isPreviewControlButton(element)).to.be.true;

parent.className = '';
element.className = '';
expect(controls.isPreviewControlButton(element)).to.be.false;
});
Expand Down
1 change: 1 addition & 0 deletions src/lib/__tests__/MobileControls-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="test-controls-container"> </div>
Loading

0 comments on commit 98ad9cc

Please sign in to comment.