amp-bind
Adds custom interactivity with data binding and expressions.
Required Script |
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
|
Examples | |
Tutorials | Create interactive AMP pages |
Overview
The amp-bind
component allows you to add custom stateful interactivity to your AMP pages via data binding and JS-like expressions.
A simple example
In the following example, tapping the button changes the <p>
element's text from "Hello World" to "Hello amp-bind".
<p [text]="'Hello ' + foo">Hello World</p> <button on="tap:AMP.setState({foo: 'amp-bind'})">Say "Hello amp-bind"</button>
For performance and to avoid the risk of unexpected content jumping, amp-bind
does not evaluate expressions on page load. This means that the visual elements should be given a default state and not rely amp-bind
for initial render.
How does it work?
amp-bind
has three main components:
- State: A document-scope, mutable JSON state. In the example above, the state is empty before tapping the button. After tapping the button, the state is
{foo: 'amp-bind'}
. - Expressions: These are JavaScript-like expressions that can reference the state. The example above has a single expression,
'Hello ' + foo
, which concatenates the string literal'Hello '
and the state variablefoo
. There is a limit of 100 operands what can be used in an expression. - Bindings: These are special attributes of the form
[property]
that link an element's property to an expression. The example above has a single binding,[text]
, which updates the<p>
element's text every time the expression's value changes.
amp-bind
takes special care to ensure speed, security and performance on AMP pages.
A slightly more complex example
<!-- Store complex nested JSON data in <amp-state> elements. --> <amp-state id="myAnimals"> <script type="application/json"> { "dog": { "imageUrl": "/img/dog.jpg", "style": "greenBackground" }, "cat": { "imageUrl": "/img/cat.jpg", "style": "redBackground" } } </script> </amp-state> <p [text]="'This is a ' + currentAnimal + '.'">This is a dog.</p> <!-- CSS classes can also be added or removed with [class]. --> <p class="greenBackground" [class]="myAnimals[currentAnimal].style"> Each animal has a different background color. </p> <!-- Or change an image's src with the [src] binding. --> <amp-img width="300" height="200" src="/img/dog.jpg" [src]="myAnimals[currentAnimal].imageUrl" > </amp-img> <button on="tap:AMP.setState({currentAnimal: 'cat'})">Set to Cat</button>
When the button is pressed:
-
State is updated with
currentAnimal
defined as'cat'
. -
Expressions that depend on
currentAnimal
are evaluated:'This is a ' + currentAnimal + '.'
=>'This is a cat.'
myAnimals[currentAnimal].style
=>'redBackground'
myAnimals[currentAnimal].imageUrl
=>/img/cat.jpg
-
Bindings that depend on the changed expressions are updated:
- The first
<p>
element's text will read "This is a cat." - The second
<p>
element'sclass
attribute will be "redBackground". - The
amp-img
element will show the image of a cat.
- The first
Try out the live demo for this example with code annotations!
Details
State
Each AMP document that uses amp-bind
has document-scope mutable JSON data, or state.
Initializing state with amp-state
amp-bind
's state can be initialized with the amp-state
component:
<amp-state id="myState"> <script type="application/json"> { "foo": "bar" } </script> </amp-state>
Expressions can reference state variables via dot syntax. In this example, myState.foo
will evaluate to "bar"
.
- An
<amp-state>
element's child JSON has a maximum size of 100KB. - An
<amp-state>
element can also specify a CORS URL instead of a child JSON script. See the Appendix for details.
Refreshing state
The refresh
action is supported by this component and can be used to refresh the
state's contents.
<amp-state id="amp-state" ...></amp-state> <!-- Clicking the button will refresh and refetch the json in amp-state. --> <button on="tap:amp-state.refresh"></button>
Updating state with AMP.setState()
The AMP.setState()
action merges an object literal into the state. For example, when the below button is pressed, AMP.setState()
will deep-merge the object literal with the state.
<!-- Like JavaScript, you can reference existing variables in the values of the object literal. --> <button on="tap:AMP.setState({foo: 'bar', baz: myAmpState.someVariable})" ></button>
In general, nested objects will be merged up to a maximum depth of 10. All variables, including those introduced by amp-state
, can be overidden.
When triggered by certain events, AMP.setState()
also can access event-related data on the event
property.
<!-- The "change" event of this <input> element contains a "value" variable that can be referenced via "event.value". --> <input type="range" on="change:AMP.setState({myRangeValue: event.value})" />
Modifying history with AMP.pushState()
The AMP.pushState()
action is similar to AMP.setState()
except it also pushes a new entry
onto the browser history stack. Popping this history entry (e.g. by navigating back) restores
the previous value of variables set by AMP.pushState()
.
For example:
<button on="tap:AMP.pushState({foo: '123'})">Set 'foo' to 123</button>
- Tapping the button will set variable
foo
to 123 and push a new history entry. - Navigating back will restore
foo
to its previous value, "bar" (equivalent to callingAMP.setState({foo: 'bar'})
.
Expressions
Expressions are similar to JavaScript with some important differences.
Differences from JavaScript
- Expressions may only access the containing document's state.
- Expressions do not have access to
window
ordocument
.global
references the top-level state. - Only white-listed functions and operators may be used. Custom functions, classes and loops are disallowed. Arrow functions are allowed as function parameters e.g.
[1, 2, 3].map(x => x + 1)
. - Undefined variables and array-index-out-of-bounds return
null
instead ofundefined
or throwing errors. - A single expression is currently capped at 50 operands for performance. Please contact us if this is insufficient for your use case.
The full expression grammar and implementation can be found in bind-expr-impl.jison and bind-expression.js.
Examples
The following are all valid expressions:
1 + '1'; // 11 1 + +'1'; // 2 !0; // true null || 'default'; // 'default'
White-listed functions
Object type | Function(s) | Example |
---|---|---|
Array 1 |
concat filter includes indexOf join lastIndexOf map reduce slice some sort (not-in-place)splice (not-in-place) |
// Returns [1, 2, 3]. [3, 2, 1].sort() // Returns [1, 3, 5]. [1, 2, 3].map((x, i) => x + i) // Returns 6. [1, 2, 3].reduce((x, y) => x + y) |
Number |
toExponential toFixed toPrecision toString
|
// Returns 3. (3.14).toFixed() // Returns '3.14'. (3.14).toString() |
String |
charAt charCodeAt concat indexOf lastIndexOf replace slice split substr substring toLowerCase toUpperCase |
// Returns 'abcdef'. 'abc'.concat('def') |
Math 2 |
abs ceil floor max min pow random round sign |
// Returns 1. abs(-1) |
Object 2 |
keys values
|
// Returns ['a', 'b']. keys({a: 1, b: 2}) // Returns [1, 2]. values({a: 1, b: 2} |
Global 2
|
encodeURI encodeURIComponent
|
// Returns 'Hello%20world'. encodeURIComponent('Hello world') |
1Single-parameter arrow functions can't have parentheses, e.g. use x => x + 1
instead of (x) => x + 1
. Also, sort()
and splice()
return modified copies instead of operating in-place.
2Static functions are not namespaced, e.g. use abs(-1)
instead of Math.abs(-1)
.
Defining macros with amp-bind-macro
amp-bind
expression fragments can be reused by defining an amp-bind-macro
. The amp-bind-macro
element allows you to define an expression that takes zero or more arguments and references the current state. A macro can be invoked like a function by referencing its id
attribute value from anywhere in your doc.
<amp-bind-macro id="circleArea" arguments="radius" expression="3.14 * radius * radius" ></amp-bind-macro> <div> The circle has an area of <span [text]="circleArea(myCircle.radius)">0</span>. </div>
A macro can also call other macros defined before itself. A macro cannot call itself recursively.
Bindings
A binding is a special attribute of the form [property]
that links an element's property to an expression. An alternative, XML-compatible syntax can also be used in the form of data-amp-bind-property
.
When the state changes, expressions are re-evaluated and the bound elements' properties are updated with the new expression results.
amp-bind
supports data bindings on five types of element state:
Type | Attribute(s) | Details |
---|---|---|
Node.textContent |
[text] |
Supported on most text elements. |
CSS classes | [class] |
Expression result must be a space-delimited string. |
The hidden attribute |
[hidden] |
Should be a boolean expression. |
Size of AMP elements | [width] [height] |
Changes the width and/or height of the AMP element. |
Accessibility states and properties | [aria-hidden] [aria-label] etc. |
Used for dynamically updating information available to assistive technologies like screen readers. |
Element-specific attributes | Various |
Notes on bindings:
- For security reasons, binding to
innerHTML
is disallowed. - All attribute bindings are sanitized for unsafe values (e.g.,
javascript:
). - Boolean expression results toggle boolean attributes. For example:
<amp-video [controls]="expr"...>
. Whenexpr
evaluates totrue
, the<amp-video>
element has thecontrols
attribute. Whenexpr
evaluates tofalse
, thecontrols
attribute is removed. - Bracket characters
[
and]
in attribute names can be problematic when writing XML (e.g. XHTML, JSX) or writing attributes via DOM APIs. In these cases, use the alternative syntaxdata-amp-bind-x="foo"
instead of[x]="foo"
.
Element-specific attributes
Only binding to the following components and attributes are allowed:
Component | Attribute(s) | Behavior |
---|---|---|
<amp-brightcove> |
[data-account] [data-embed] [data-player] [data-player-id] [data-playlist-id] [data-video-id] |
Changes the displayed Brightcove video. |
<amp-carousel type=slides> |
[slide] |
Changes the currently displayed slide index. See an example. |
<amp-date-picker> |
[min] [max]
|
Sets the earliest selectable date Sets the latest selectable date |
<amp-google-document-embed> |
[src] [title] |
Displays the document at the updated URL. Changes the document's title. |
<amp-iframe> |
[src] |
Changes the iframe's source URL. |
<amp-img> |
[alt] [attribution] [src] [srcset] |
Recommend binding to [srcset] instead of [src] to support responsive images.See corresponding amp-img attributes. |
<amp-lightbox> |
[open] |
Toggles display of the lightbox. Tip: Use on="lightboxClose: AMP.setState(...)" to update variables when the lightbox is closed.
|
<amp-list> |
[src] |
If expression is a string, fetches and renders JSON from the string URL. If expression is an object or array, renders the expression data. |
<amp-selector> |
[selected] *[disabled] |
Changes the currently selected children element(s) identified by their option attribute values. Supports a comma-separated list of values for multiple selection. See an example. |
<amp-state> |
[src] |
Fetches JSON from the new URL and merges it into the existing state. Note the following update will ignore <amp-state> elements to prevent cycles. |
<amp-twitter> |
[data-tweetid] |
Changes the displayed Tweet. |
<amp-video> |
[alt] [attribution] [controls] [loop] [poster] [preload] [src] |
See corresponding amp-video attributes. |
<amp-youtube> |
[data-videoid] |
Changes the displayed YouTube video. |
<a> |
[href] |
Changes the link. |
<button> |
[disabled] [type] [value] |
See corresponding button attributes. |
<details> |
[open] |
See corresponding details attributes. |
<fieldset> |
[disabled] |
Enables or disables the fieldset. |
<image> |
[xlink:href] | See corresponding image attributes. |
<input> |
[accept] [accessKey] [autocomplete] [checked] [disabled] [height] [inputmode] [max] [maxlength] [min] [minlength] [multiple] [pattern] [placeholder] [readonly] [required] [selectiondirection] [size] [spellcheck] [step] [type] [value] [width] |
See corresponding input attributes. |
<option> |
[disabled] [label] [selected] [value] |
See corresponding option attributes. |
<optgroup> |
[disabled] [label] |
See corresponding optgroup attributes |
<section> |
[data-expand] |
Changes the expansion of a section in an amp-accordion. |
<select> |
[autofocus] [disabled] [multiple] [required] [size] |
See corresponding select attributes. |
<source> |
[src] [type] |
See corresponding source attributes. |
<track> |
[label] [src] [srclang] |
See corresponding track attributes. |
<textarea> |
[autocomplete] [autofocus] [cols] [disabled] [defaultText] [maxlength] [minlength] [placeholder] [readonly] [required] [rows] [selectiondirection] [selectionend] [selectionstart] [spellcheck] [wrap] |
Use [defaultText] to update initial text, and [text] to update current text.See corresponding textarea attributes. |
*Denotes bindable attributes that don't have a non-bindable counterpart.
Debugging
Test in development mode (with the URL fragment #development=1
) to highlight warnings and errors during development and to access special debugging functions.
Warnings
In development mode, amp-bind
will issue a warning when the default value of a bound attribute doesn't match its corresponding expression's initial result. This can help prevent unintended mutations caused by changes in other state variables. For example:
<!-- The element's default class value ('def') doesn't match the expression result for [class] ('abc'), so a warning will be issued in development mode. --> <p [class]="'abc'" class="def"></p>
In development mode, amp-bind
will also issue a warning when dereferencing undefined variables or properties. This can also help prevent unintended mutations due to null
expression results. For example:
<amp-state id="myAmpState"> <script type="application/json"> { "foo": 123 } </script> </amp-state> <!-- The amp-state#myAmpState does not have a `bar` variable, so a warning will be issued in development mode. --> <p [text]="myAmpState.bar">Some placeholder text.</p>
Errors
There are several types of runtime errors that may be encountered when working with amp-bind
.
Type | Message | Suggestion |
---|---|---|
Invalid binding | Binding to [foo] on <P> is not allowed. | Use only white-listed bindings. |
Syntax error | Expression compilation error in... | Verify the expression for typos. |
Non-whitelisted functions | alert is not a supported function. | Use only white-listed functions. |
Sanitized result | "javascript:alert(1)" is not a valid result for [href]. | Avoid banned URL protocols or expressions that would fail the AMP Validator. |
CSP violation | Refused to create a worker from 'blob:...' because it violates the following Content Security Policy directive... | Add default-src blob: to your origin's Content Security Policy. amp-bind delegates expensive work to a dedicated Web Worker to ensure good performance. |
Debugging State
Use AMP.printState()
to print the current state to the console.
Appendix
<amp-state>
specification
An amp-state
element may contain either a child <script>
element OR a src
attribute containing a CORS URL to a remote JSON endpoint, but not both.
<amp-state id="myLocalState"> <script type="application/json"> { "foo": "bar" } </script> </amp-state> <amp-state id="myRemoteState" src="https://data.com/articles.json"> </amp-state>
XHR batching
AMP batches XMLHttpRequests (XHRs) to JSON endpoints, that is, you can use a single JSON data request as a data source for multiple consumers (e.g., multiple amp-state
elements) on an AMP page.
For example, if your amp-state
element makes an XHR to an endpoint, while the XHR is in flight, all subsequent XHRs to the same endpoint won't trigger and will instead return the results from the first XHR.
Attributes
src | The URL of the remote endpoint that will return the JSON that will update this The
The endpoint must implement the requirements specified in the CORS Requests in AMP spec. |
credentials (optional) | Defines a
To send credentials, pass the value of |
Deep-merge with AMP.setState()
When AMP.setState()
is called amp-bind
deep-merges the provided object literal with the current state. All variables from the object literal are written to the state directly except for nested objects, which are recursively merged. Primitives and arrays are in the state are always overwritten by variables of the same name in the object literal.
Consider the following example:
// State is empty. { }
<button on="tap:AMP.setState({ employee: { name: 'John Smith', age: 47, vehicle: 'Car' } })" > Set employee to John Smith </button> <button on="tap:AMP.setState({ employee: { age: 64 } })" > Set employee age to 64 </button>
When the first button is pressed, the state changes to:
{ employee: { name: 'John Smith', age: 47, vehicle: 'Car', } }
When the second button is pressed, amp-bind
will recursively merge the object literal argument, {employee: {age: 64}}
, into the existing state.
{ employee: { name: 'John Smith', age: 64, vehicle: 'Car', } }
employee.age
has been updated, however employee.name
and employee.vehicle
keys have not changed.
Circular references
AMP.setState(object)
will throw a runtime error if object
contains a circular reference.
Removing a variable
Remove an existing state variable by setting its value to null
in AMP.setState()
.
For example:
<button on="tap:AMP.setState({removeMe: null})"></button>
Expression grammar
The BNF-like grammar for amp-bind
expressions:
expr: operation | invocation | member_access | '(' expr ')' | variable | literal ; operation: '!' expr | '-' expr %prec UMINUS | '+' expr %prec UPLUS | expr '+' expr | expr '-' expr | expr '*' expr | expr '/' expr | expr '%' expr | expr '&&' expr | expr '||' expr | expr '<=' expr | expr '<' expr | expr '>=' expr | expr '>' expr | expr '!=' expr | expr '==' expr | expr '?' expr ':' expr ; invocation: NAME args | expr '.' NAME args | expr '.' NAME '(' arrow_function ')' | expr '.' NAME '(' arrow_function ',' expr ')' ; arrow_function: '(' ')' '=>' expr | NAME '=>' expr | '(' params ')' '=>' expr ; params: NAME ',' NAME | params ',' NAME ; args: '(' ')' | '(' array ')' ; member_access: expr member ; member: '.' NAME | '[' expr ']' ; variable: NAME ; literal: primitive | object_literal | array_literal ; primitive: STRING | NUMBER | TRUE | FALSE | NULL ; array_literal: '[' ']' | '[' array ']' | '[' array ',' ']' ; array: expr | array ',' expr ; object_literal: '{' '}' | '{' object '}' | '{' object ',' '}' ; object: key_value | object ',' key_value ; key_value: key ':' expr ; key: NAME | primitive | '[' expr ']' ;
You've read this document a dozen times but it doesn't really cover all of your questions? Maybe other people felt the same: reach out to them on Stack Overflow.
Go to Stack Overflow Found a bug or missing a feature?The AMP project strongly encourages your participation and contributions! We hope you'll become an ongoing participant in our open source community but we also welcome one-off contributions for the issues you're particularly passionate about.
Go to GitHub