diff --git a/build-system/tasks/compile-bind-expr.js b/build-system/tasks/compile-bind-expr.js index 5d68d1af1827..c2cabf1fd115 100644 --- a/build-system/tasks/compile-bind-expr.js +++ b/build-system/tasks/compile-bind-expr.js @@ -28,8 +28,9 @@ gulp.task('compile-bind-expr', function() { var license = fs.readFileSync( 'build-system/tasks/js-license.txt', 'utf8'); + var suppressCheckTypes = '/** @fileoverview @suppress {checkTypes, suspiciousCode, uselessCode} */'; var jsExports = 'exports.parser = parser;'; - var out = license + '\n\n' + jsModule + '\n\n' + jsExports + '\n'; + var out = [license, suppressCheckTypes, jsModule, jsExports].join('\n\n') + '\n'; fs.writeFileSync(path + 'bind-expr-impl.js', out); }); diff --git a/builtins/amp-img.js b/builtins/amp-img.js index 5a1a12e24eb2..06691263ee0b 100644 --- a/builtins/amp-img.js +++ b/builtins/amp-img.js @@ -20,6 +20,12 @@ import {registerElement} from '../src/custom-element'; import {srcsetFromElement} from '../src/srcset'; import {user} from '../src/log'; +/** + * Attributes to propagate to internal image when changed externally. + * @type {!Array} + */ +const ATTRIBUTES_TO_PROPAGATE = ['alt', 'referrerpolicy', 'aria-label', + 'aria-describedby', 'aria-labelledby']; export class AmpImg extends BaseElement { @@ -40,6 +46,17 @@ export class AmpImg extends BaseElement { this.srcset_ = null; } + /** @override */ + attributeChangedCallback(name, unusedOldValue, unusedNewValue) { + if (name === 'src') { + this.srcset_ = srcsetFromElement(this.element); + this.updateImageSrc_(); + } else if (this.img_ && ATTRIBUTES_TO_PROPAGATE.indexOf(name) >= 0) { + this.propagateAttributes(name, this.img_, + /* opt_removeMissingAttrs */ true); + } + } + /** @override */ buildCallback() { this.isPrerenderAllowed_ = !this.element.hasAttribute('noprerender'); @@ -81,8 +98,7 @@ export class AmpImg extends BaseElement { 'be correctly propagated for the underlying element.'); } - this.propagateAttributes(['alt', 'referrerpolicy', 'aria-label', - 'aria-describedby', 'aria-labelledby'], this.img_); + this.propagateAttributes(ATTRIBUTES_TO_PROPAGATE, this.img_); this.applyFillContent(this.img_, true); this.element.appendChild(this.img_); diff --git a/examples/bind.amp.html b/examples/bind.amp.html new file mode 100644 index 000000000000..8178f875c1f3 --- /dev/null +++ b/examples/bind.amp.html @@ -0,0 +1,44 @@ + + + + + amp-bind + + + + + + + + + + + + + + + + + +

After clicking the button below, this will read 'foo'

+

And this will read 'foobar'

+

This will read 'myStateValue1'

+ +

This text will have have a red background color

+ +

The image above will increase in size and change its src

+ +
+ + + diff --git a/extensions/amp-bind/0.1/bind-expr.js b/extensions/amp-bind/0.1/amp-bind.js similarity index 76% rename from extensions/amp-bind/0.1/bind-expr.js rename to extensions/amp-bind/0.1/amp-bind.js index 4337705581ec..12f344ab1f4e 100644 --- a/extensions/amp-bind/0.1/bind-expr.js +++ b/extensions/amp-bind/0.1/amp-bind.js @@ -14,13 +14,8 @@ * limitations under the License. */ -import {parser} from './bind-expr-impl'; +import {AmpState} from './amp-state'; +import {Bind} from './bind-impl'; -export function evaluateBindExpr(expr, data) { - try { - parser.yy = data; - return parser.parse(expr); - } finally { - parser.yy = null; - } -} +AMP.registerServiceForDoc('bind', Bind); +AMP.registerElement('amp-state', AmpState); diff --git a/extensions/amp-bind/0.1/amp-state.js b/extensions/amp-bind/0.1/amp-state.js new file mode 100644 index 000000000000..b804cb812109 --- /dev/null +++ b/extensions/amp-bind/0.1/amp-state.js @@ -0,0 +1,91 @@ +/** + * 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 {bindForDoc} from '../../../src/bind'; +import {isJsonScriptTag} from '../../../src/dom'; +import {toggle} from '../../../src/style'; +import {tryParseJson} from '../../../src/json'; +import {user} from '../../../src/log'; + +export class AmpState extends AMP.BaseElement { + /** @override */ + getPriority() { + // Loads after other content. + return 1; + } + + /** @override */ + isAlwaysFixed() { + return true; + } + + /** @override */ + isLayoutSupported(unusedLayout) { + return true; + } + + /** @override */ + buildCallback() { + const TAG = this.getName_(); + + toggle(this.element, false); + this.element.setAttribute('aria-hidden', 'true'); + + const id = user().assert(this.element.id, + '%s element must have an id.', TAG); + + let json; + const children = this.element.children; + if (children.length == 1) { + const child = children[0]; + if (isJsonScriptTag(child)) { + json = tryParseJson(children[0].textContent, e => { + user().error(TAG, 'Failed to parse state. Is it valid JSON?', e); + }); + } else { + user().error(TAG, + 'State should be in a