Skip to content

Commit

Permalink
amp-sidebar - Basic Open and close functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
camelburrito committed Apr 5, 2016
1 parent 2d223c6 commit 99634b1
Show file tree
Hide file tree
Showing 4 changed files with 750 additions and 2 deletions.
42 changes: 41 additions & 1 deletion extensions/amp-sidebar/0.1/amp-sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,44 @@
* limitations under the License.
*/

amp-sidebar {}
amp-sidebar {
position: fixed !important;
top: 0;
max-height: 100vh !important;
height: 100vh;
max-width: 80vw !important;
background-color: #efefef;
min-width: 45px !important;
overflow-x: hidden !important;
overflow-y: auto !important;
z-index: 9999 !important;
-webkit-overflow-scrolling: touch;
}

amp-sidebar[side="left"] {
left: 0 !important;
transform: translateX(-80vw) !important;
}

amp-sidebar[side="right"] {
right: 0 !important;
transform: translateX(80vw) !important;
}

amp-sidebar[side][open] {
transform: translateX(0vw) !important;
}

.-amp-sidebar-mask {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
opacity: 0.2;
/* Prevent someone from making this a full-sceen image */
background-image: none !important;
background-color: #000;
z-index: 9998 !important;
display: none;
}
141 changes: 140 additions & 1 deletion extensions/amp-sidebar/0.1/amp-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
import {CSS} from '../../../build/amp-sidebar-0.1.css';
import {Layout} from '../../../src/layout';
import {isExperimentOn} from '../../../src/experiments';

import {dev} from '../../../src/log';
import {setStyles} from '../../../src/style';

/** @const */
const EXPERIMENT = 'amp-sidebar';
Expand All @@ -41,10 +42,148 @@ export class AmpSidebar extends AMP.BaseElement {
/** @const @private {boolean} */
this.isExperimentOn_ = isExperimentOn(this.getWin(), EXPERIMENT);

/** @private @const {!Window} */
this.win_ = this.getWin();

/** @private @const {!Document} */
this.document_ = this.win_.document;

/** @private @const {!Element} */
this.documentElement_ = this.document_.documentElement;

/** @private @const {string} */
this.side_ = this.element.getAttribute('side');

/** @private @const {!Viewport} */
this.viewport_ = this.getViewport();

/** @private @const {!Element} */
this.maskElement_ = false;

if (!this.isExperimentOn_) {
dev.warn(TAG, `Experiment ${EXPERIMENT} disabled`);
return;
}

if (this.side_ != 'left' && this.side_ != 'right') {
const pageDir =
this.document_.body.getAttribute('dir') ||
this.documentElement_.getAttribute('dir') ||
'ltr';
this.side_ = (pageDir == 'rtl') ? 'right' : 'left';
this.element.setAttribute('side', this.side_);
}

if (this.isOpen_()) {
this.open_();
} else {
this.element.setAttribute('aria-hidden', 'true');
}

this.documentElement_.addEventListener('keydown', event => {
// Close sidebar on ESC.
if (event.keyCode == 27) {
this.close_();
}
});

//TODO (skrish, #2712) Add history support on back button.
this.registerAction('toggle', this.toggle_.bind(this));
this.registerAction('open', this.open_.bind(this));
this.registerAction('close', this.close_.bind(this));
}

/**
* Returns true if the sidebar is opened.
* @returns {boolean}
* @private
*/
isOpen_() {
return this.element.hasAttribute('open');
}

/** @override */
activate() {
this.open_();
}


/**
* Toggles the open/close state of the sidebar.
* @private
*/
toggle_() {
if (this.isOpen_()) {
this.close_();
} else {
this.open_();
}
}

/**
* Reveals the sidebar.
* @private
*/
open_() {
this.viewport_.disableTouchZoom();
this.mutateElement(() => {
this.viewport_.addToFixedLayer(this.element);
setStyles(this.element, {
'display': 'block',
});
this.openMask_();
this.element.setAttribute('open', '');
this.element.setAttribute('aria-hidden', 'false');
});
}

/**
* Hides the sidebar.
* @private
*/
close_() {
this.viewport_.restoreOriginalTouchZoom();
this.mutateElement(() => {
this.closeMask_();
this.element.removeAttribute('open');
this.element.setAttribute('aria-hidden', 'true');
setStyles(this.element, {
'display': 'none',
});
this.viewport_.removeFromFixedLayer(this.element);
});
}

/**
* @private
*/
openMask_() {
if (!this.maskElement_) {
const mask = this.document_.createElement('div');
mask.classList.add('-amp-sidebar-mask');
mask.addEventListener('click', () => {
this.close_();
});
this.element.parentNode.appendChild(mask);
mask.addEventListener('touchmove', e => {
e.preventDefault();
});
this.maskElement_ = mask;
}
setStyles(this.maskElement_, {
'display': 'block',
});
}

/**
* @private
*/
closeMask_() {
if (this.maskElement_) {
setStyles(this.maskElement_, {
'display': 'none',
});
}
}
}

Expand Down
149 changes: 149 additions & 0 deletions extensions/amp-sidebar/0.1/test/test-amp-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,166 @@
*/

import {adopt} from '../../../../src/runtime';
import {createIframePromise} from '../../../../testing/iframe';
import {toggleExperiment} from '../../../../src/experiments';
require('../amp-sidebar');

adopt(window);

describe('amp-sidebar', () => {
let sandbox;

function getAmpSidebar(options) {
options = options || {};
return createIframePromise().then(iframe => {
toggleExperiment(iframe.win, 'amp-sidebar', true);
const ampSidebar = iframe.doc.createElement('amp-sidebar');
const list = iframe.doc.createElement('ul');
for (let i = 0; i < 10; i++) {
const li = iframe.doc.createElement('li');
li.innerHTML = 'Menu item ' + i;
list.appendChild(li);
}
ampSidebar.appendChild(list);
if (options.side) {
ampSidebar.setAttribute('side', options.side);
}
ampSidebar.setAttribute('id', 'sidebar1');
return iframe.addElement(ampSidebar).then(() => {
return Promise.resolve({
iframe: iframe,
ampSidebar: ampSidebar,
});
});
});
}

beforeEach(() => {
sandbox = sinon.sandbox.create();
});

afterEach(() => {
sandbox.restore();
});

it('should open from left is side is not specified', () => {
return getAmpSidebar().then(obj => {
const sidebarElement = obj.ampSidebar;
expect(sidebarElement.getAttribute('side')).to.equal('left');
});
});

it('should open from right is side right is specified', () => {
return getAmpSidebar({'side': 'right'}).then(obj => {
const sidebarElement = obj.ampSidebar;
expect(sidebarElement.getAttribute('side')).to.equal('right');
});
});

it('should create mask element in DOM', () => {
return getAmpSidebar().then(obj => {
const iframe = obj.iframe;
const sidebarElement = obj.ampSidebar;
const impl = sidebarElement.implementation_;
impl.mutateElement = function(callback) {
callback();
};
impl.open_();
expect(iframe.doc.querySelectorAll('.-amp-sidebar-mask').length)
.to.equal(1);
});
});

it('should open sidebar on button click', () => {
return getAmpSidebar().then(obj => {
const sidebarElement = obj.ampSidebar;
const impl = sidebarElement.implementation_;
impl.mutateElement = function(callback) {
callback();
};
impl.open_();
expect(sidebarElement.hasAttribute('open')).to.be.true;
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('false');
expect(sidebarElement.style.display).to.equal('block');
});
});

it('should close sidebar on button click', () => {
return getAmpSidebar().then(obj => {
const sidebarElement = obj.ampSidebar;
const impl = sidebarElement.implementation_;
impl.mutateElement = function(callback) {
callback();
};
impl.close_();
expect(sidebarElement.hasAttribute('open')).to.be.false;
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('true');
expect(sidebarElement.style.display).to.equal('none');
});
});

it('should toggle sidebar on button click', () => {
return getAmpSidebar().then(obj => {
const sidebarElement = obj.ampSidebar;
const impl = sidebarElement.implementation_;
impl.mutateElement = function(callback) {
callback();
};
expect(sidebarElement.hasAttribute('open')).to.be.false;
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('true');
impl.toggle_();
expect(sidebarElement.hasAttribute('open')).to.be.true;
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('false');
expect(sidebarElement.style.display).to.equal('block');
impl.toggle_();
expect(sidebarElement.hasAttribute('open')).to.be.false;
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('true');
expect(sidebarElement.style.display).to.equal('none');
});
});

it('should close sidebar on escape', () => {
return getAmpSidebar().then(obj => {
const iframe = obj.iframe;
const sidebarElement = obj.ampSidebar;
const impl = sidebarElement.implementation_;
impl.mutateElement = function(callback) {
callback();
};
expect(sidebarElement.hasAttribute('open')).to.be.false;
impl.open_();
expect(sidebarElement.hasAttribute('open')).to.be.true;
expect(sidebarElement.style.display).to.equal('block');
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('false');
const eventObj = document.createEventObject ?
document.createEventObject() : document.createEvent('Events');
if (eventObj.initEvent) {
eventObj.initEvent('keydown', true, true);
}

eventObj.keyCode = 27;
eventObj.which = 27;
const el = iframe.doc.documentElement;
el.dispatchEvent ?
el.dispatchEvent(eventObj) : el.fireEvent('onkeydown', eventObj);
expect(sidebarElement.hasAttribute('open')).to.be.false;
expect(sidebarElement.style.display).to.equal('none');
expect(sidebarElement.getAttribute('aria-hidden')).to.equal('true');
});
});

it('should reflect state of the sidebar', () => {
return getAmpSidebar().then(obj => {
const sidebarElement = obj.ampSidebar;
const impl = sidebarElement.implementation_;
impl.mutateElement = function(callback) {
callback();
};
expect(impl.isOpen_()).to.be.false;
impl.toggle_();
expect(impl.isOpen_()).to.be.true;
impl.toggle_();
expect(impl.isOpen_()).to.be.false;
});
});
});
Loading

0 comments on commit 99634b1

Please sign in to comment.