Skip to content

Commit

Permalink
amp-accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
camelburrito committed Feb 12, 2016
1 parent f5b0517 commit 1aae4ee
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 1 deletion.
67 changes: 67 additions & 0 deletions extensions/amp-accordion/0.1/amp-accordion.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Copyright 2016 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* Non-overridable properties */
amp-accordion {
display: block !important;
}

/* Make sections non-floatable */
amp-accordion > section {
float: none !important;
}

/* Hide all children and make them non-floatable */
amp-accordion > section > *:nth-child(n) {
display: none !important;
float: none !important;
}

/* Display the first 2 elements (heading and content) */
amp-accordion > section > .-amp-accordion-header,
amp-accordion > section > .-amp-accordion-content {
display: block !important;
overflow: hidden !important; /* clearfix */
position: relative !important;
}

amp-accordion,
amp-accordion > section,
.-amp-accordion-header,
.-amp-accordion-content {
margin: 0;
}



/* heading element*/
.-amp-accordion-header {
cursor: pointer;
background-color: #efefef;
padding-right: 20px;
border: solid 1px #dfdfdf;
}

/* Collapse content by default. */
amp-accordion > section > .-amp-accordion-content {
display: none !important;
}

/* Expand content when needed. */
amp-accordion > section[expanded] > .-amp-accordion-content {
display: block !important;
}

76 changes: 76 additions & 0 deletions extensions/amp-accordion/0.1/amp-accordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright 2016 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {Layout} from '../../../src/layout';
import {assert} from '../../../src/asserts';
import {isExperimentOn} from '../../../src/experiments';
import {log} from '../../../src/log';

/** @const */
const EXPERIMENT = 'amp-accordion';

/** @const */
const TAG = 'AmpAccordion';

class AmpAccordion extends AMP.BaseElement {

/** @override */
isLayoutSupported(layout) {
return layout == Layout.CONTAINER;
}

/** @override */
buildCallback() {
/** @const @private {!NodeList} */
this.sections_ = this.getRealChildren();

/** @const @private {boolean} */
this.isExperimentOn_ = isExperimentOn(this.getWin(), EXPERIMENT);
if (!this.isExperimentOn_) {
log.warn(TAG, `Experiment ${EXPERIMENT} disabled`);
return;
}
this.sections_.forEach(section => {
assert(
section.tagName.toLowerCase() == 'section',
'Sections should be enclosed in a <section> tag, ' +
'See https://github.com/ampproject/amphtml/blob/master/extensions/' +
'amp-accordion/amp-accordion.md. Found in: %s', this.element);
const sectionComponents_ = section.children;
assert(
sectionComponents_.length == 2,
'Each section must have exactly two children. ' +
'See https://github.com/ampproject/amphtml/blob/master/extensions/' +
'amp-accordion/amp-accordion.md. Found in: %s', this.element);
const header = sectionComponents_[0];
const content = sectionComponents_[1];
header.classList.add('-amp-accordion-header');
content.classList.add('-amp-accordion-content');
header.addEventListener('click', event => {
event.preventDefault();
this.mutateElement(() => {
if (section.hasAttribute('expanded')) {
section.removeAttribute('expanded');
} else {
section.setAttribute('expanded', '');
}
}, content);
});
});
}
}

AMP.registerElement('amp-accordion', AmpAccordion, $CSS$);
90 changes: 90 additions & 0 deletions extensions/amp-accordion/0.1/test/test-amp-accordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copyright 2016 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {Timer} from '../../../../src/timer';
import {adopt} from '../../../../src/runtime';
import {createIframePromise} from '../../../../testing/iframe';
import {toggleExperiment} from '../../../../src/experiments';
require('../../../../build/all/v0/amp-accordion-0.1.max');

adopt(window);

describe('amp-accordion', () => {
const timer = new Timer(window);
function getAmpAccordion() {
return createIframePromise().then(iframe => {
toggleExperiment(iframe.win, 'amp-accordion', true);
const ampAccordion = iframe.doc.createElement('amp-accordion');
for (let i = 0; i < 3; i++) {
const section = iframe.doc.createElement('section');
section.innerHTML = "<h2>Section " + i + "</h2><div>Loreum ipsum</div>";
ampAccordion.appendChild(section);
if (i == 1) {
section.setAttribute('expanded', '');
}
}
return iframe.addElement(ampAccordion).then(() => {
return Promise.resolve({
iframe: iframe,
ampAccordion: ampAccordion
});
});
});
}

it('should expand when header of a collapsed section is clicked', () => {
return getAmpAccordion().then(obj => {
const iframe = obj.iframe;
let clickEvent;
if (iframe.doc.createEvent) {
clickEvent = iframe.doc.createEvent('MouseEvent');
clickEvent.initMouseEvent('click', true, true, iframe.win, 1);
} else {
clickEvent = iframe.doc.createEventObject();
clickEvent.type = 'click';
}
const headerElements =
iframe.doc.querySelectorAll('section > *:first-child');
expect(headerElements[0].parentNode.hasAttribute('expanded')).to.be.false;
headerElements[0].dispatchEvent(clickEvent);
return timer.promise(50).then(() => {
expect(headerElements[0].parentNode.hasAttribute('expanded'))
.to.be.true;
});
});
});
it('should collapse when header of an expanded section is clicked', () => {
return getAmpAccordion().then(obj => {
const iframe = obj.iframe;
let clickEvent;
if (iframe.doc.createEvent) {
clickEvent = iframe.doc.createEvent('MouseEvent');
clickEvent.initMouseEvent('click', true, true, iframe.win, 1);
} else {
clickEvent = iframe.doc.createEventObject();
clickEvent.type = 'click';
}
const headerElements =
iframe.doc.querySelectorAll('section > *:first-child');
expect(headerElements[1].parentNode.hasAttribute('expanded')).to.be.true;
headerElements[1].dispatchEvent(clickEvent);
return timer.promise(50).then(() => {
expect(headerElements[1].parentNode.hasAttribute('expanded'))
.to.be.false;
});
});
});
});
64 changes: 64 additions & 0 deletions extensions/amp-accordion/amp-accordion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!---
Copyright 2016 The AMP HTML Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS-IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

### <a name="amp-accordion"></a> `amp-accordion`

An accordion provides a way for viewers to have a glance at the outline of the content and jump to a section or their choice at their will. This would be extremely helpful for handheld mobile devices where even a couple of sentences in a section would lead to the viewer needing to scroll.

#### Behavior

Each of the `amp-accordion` component’s immediate children is considered a section in the accordion. Each of these nodes must be a `<section>` tag.

- An `amp-accordion` can contain one or more `<section>`s as its direct children.
- Each `<section>` must contain only two direct children.
- The first child (of the section) will be considered as the heading of the section. Clicking/tapping on this section will trigger the expand/collapse behaviour.
- The second child (of the section) will be the content or the section
- There is no restriction on the type of tags that could be used for the `<section>`’s children.
- Any additional children of the `<section>` would be ignored not be displayed. (This should just be a safety backup and should be enforced in the validator)
- Clicking/tapping on the heading of a section expands/ or collapses the section.

```html
<amp-accordion>
<section expanded>
<h2>Section 1</h2>
<p>Bunch of awesome content</p>
</section>
<section>
<h2>Section 2</h2>
<div>Bunch of awesome content</div>
</section>
<section>
<h2>Section 3</h2>
<amp-img src="/awesome.png" width="300" height="300"></amp-img>
</section>
</amp-accordion>
```

#### Attributes

**expanded**

The `expanded` attribute can be set on any `<section>` that needs to be expanded on page load.

#### Styling

- You may use the `amp-accordion` element selector to style it freely.
- `amp-accordion` elements are always `display: block`.
- `<section>` and the heading and content element are not float-able.
- `<section>`s will have an `expanded` attribute when they are expanded.
- The content element is clear-fixed with `overflow: hidden` and hence cannot have scrollbars.
- margins of the `amp-accordion`, `<section>` and the heading and content elements are set to 0 and can be overridden in custom styles.
- Both the header and content elements are `position: relative`.
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function buildExtensions(options) {
// and update it if any of its required deps changed.
// Each extension and version must be listed individually here.
buildExtension('amp-access', '0.1', true, options);
buildExtension('amp-accordion', '0.1', true, options);
buildExtension('amp-analytics', '0.1', false, options);
buildExtension('amp-anim', '0.1', false, options);
buildExtension('amp-audio', '0.1', false, options);
Expand Down
19 changes: 18 additions & 1 deletion src/base-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ export class BaseElement {
* specified. Resource manager will perform the actual layout based on the
* priority of this element and its children.
* @param {!Element|!Array<!Element>} elements
* @param {boolean} inLocalViewport
* @protected
*/
scheduleLayout(elements) {
Expand Down Expand Up @@ -554,6 +553,24 @@ export class BaseElement {
this.resources_.attemptChangeHeight(this.element, newHeight, opt_callback);
}

/**
* Runs the specified mutation on the element and ensures that measures
* and layouts performed for the affected elements.
*
* This method should be called whenever a significant mutations are done
* on the DOM that could affect layout of elements inside this subtree or
* its siblings. The top-most affected element should be specified as the
* first argument to this method and all the mutation work should be done
* in the mutator callback which is called in the "mutation" vsync phase.
*
* @param {function()} mutator
* @param {Element=} opt_element
* @return {!Promise}
*/
mutateElement(mutator, opt_element) {
this.resources_.mutateElement(opt_element || this.element, mutator);
}

/**
* Schedules callback to be complete within the next batch. This call is
* intended for heavy DOM mutations that typically cause re-layouts.
Expand Down
1 change: 1 addition & 0 deletions src/render-delaying-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {timer} from './timer';
* @const {!Array<string>}
*/
const EXTENSIONS = [
'amp-accordion',
'amp-dynamic-css-classes'
];

Expand Down
36 changes: 36 additions & 0 deletions test/manual/amp-accordion.amp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!doctype html>
<html >
<head>
<meta charset="utf-8">
<title>AMP #0</title>
<link rel="canonical" href="amps.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<link href='https://fonts.googleapis.com/css?family=Questrial' rel='stylesheet' type='text/css'>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="../../dist/amp.js"></script>
<script async custom-element="amp-youtube" src="../../dist/v0/amp-accordion-0.1.max.js"></script>
</head>
<body>
<h1>AMP #0</h1>
<amp-accordion>
<section>
<h2>Section 1</h2>
<div>
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
</div>
</section>
<section>
<h2>Section 2</h2>
<div>
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
</div>
</section>
<section>
<h2>Section 3</h2>
<div>
<amp-img id="img1" srcset="https://lh4.googleusercontent.com/kDqWeHQ0Zt-UFeHAlc2ydE9AwK7W-kbMXBqyuNvPNy0mNSGAP7FOVB3_1iAOdbQSsbZByErbehvvSKtnTb1L5GoYreijfKwgYwpP1eLCHyl9Am7BSpwRuBABOAO12PtyPBTirdAadnxOmfq9dH_rsLSTaYGmNz1D5QIwXeWd8UeDNUQ3f-cMgvkq4ePqmKoe9t5ySqqLMZs-v3wTi2pd4jV_CurzcMB76k_b4lyD5w77NUowkSaQfscYVQpkDjo6OgG2nBiKJ2TITWVDu2rvt6NuEoOD9xaHgXuv81OnOjXCokxZd5K6TZiAH-Qm1jTKGMANklXiXt6hbwrN3QPA1mq2FardxGNLj1_oqOpXaqfUuj8LvejFRY6zJMGq0r6S_TEtPvbyulIg4PkKPIaVzi5nVdGrAWWoesWh-ORqmxZ4FIhdbd_Igsdh5AcMETBcZz3l5-IX0hmnyUeT5IOPSGw-p3Esgp_abwWB9-kElEiiHPD4QuQ_swsRu0NSFwfRi_QefnJQJ5UATng6iVP3K0g7uumHcwLtFId0vCeHp4A=w1024-h768-no" width=512 height=344></amp-img>
</div>
</section>
</amp-accordion>
</body>
</html>
Loading

0 comments on commit 1aae4ee

Please sign in to comment.