Description Adds custom interactivity with data binding and expressions.
Availability In development
Required Script
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
Examples
Codelabs Advanced Interactivity in AMP highlights a sophisticated e-commerce use case.
Origin Trials Register here to enable amp-bind for your origin.

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'})">

How does it work?

amp-bind has three main components:

  1. 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'}.
  2. 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 variable state foo.
  3. 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.

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:

  1. State is updated with currentAnimal defined as 'cat'.

  2. 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
  3. 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's class attribute will be "redBackground".
    • The amp-img element will show the image of a cat.

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.

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})">

See Actions and Events in AMP for more details.

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 globals like window or document.
  • Only white-listed functions are allowed.
  • Custom functions, classes and some control flow statements (e.g. for) are disallowed.
  • Undefined variables and array-index-out-of-bounds return null instead of undefined or throwing errors.
  • A single expression is currently capped at 50 operands for performance reasons. 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 concat
includes
indexOf
join
lastIndexOf
slice
// Returns true.
[1, 2, 3].includes(1)
String charAt
charCodeAt
concat
indexOf
lastIndexOf
slice
split
substr
substring
toLowerCase
toUpperCase
// Returns 'abcdef'.
'abc'.concat('def')
Math2 abs
ceil
floor
max
min
random
round
sign
// Returns 1.
abs(-1)
Global2 encodeURI
encodeURIComponent
// Returns 'hello%20world'
encodeURIComponent('hello world')
Custom built-ins2 copyAndSplice
// Returns [1, 47 ,3].
copyAndSplice([1, 2, 3], 1, 1, 47)

2Functions are not namespaced, e.g. use abs(-1) instead of Math.abs(-1).

Bindings

A binding is a special attribute of the form [property] that links an element's property to an expression.

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 four 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.
Size of AMP elements [width]
[height]
Changes the width and/or height of the AMP element.
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"...>. When expr evaluates to true, the <amp-video> element has the controls attribute. When expr evaluates to false, the controls attribute is removed.

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]1 Changes the currently displayed slide index. See an example.
<amp-iframe> [src] Changes the iframe's source URL.
<amp-img> [alt]
[attribution]
[src]
[srcset]
See corresponding amp-img attributes.
<amp-list> [src] Fetches JSON from the new URL and re-renders, replacing old content.
<amp-selector> [selected]1 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-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.
<fieldset> [disabled] Enables or disables the fieldset.
<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
<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]
[maxlength]
[minlength]
[placeholder]
[readonly]
[required]
[rows]
[selectiondirection]
[selectionend]
[selectionstart]
[spellcheck]
[wrap]
See corresponding textarea attributes.

1Denotes 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 [someBogusAttribute] 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

In development mode, 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>

Attributes

src

The URL of the remote endpoint that will return the JSON that will update this amp-state. This must be a CORS HTTP service.

The src attribute allows all standard URL variable substitutions. See the Substitutions Guide for more info.

credentials (optional)

Defines a credentials option as specified by the Fetch API. To send credentials, pass the value of "include". If this is set, the response must follow the AMP CORS security guidelines.

The support values are "omit" and "include". Default is "omit".

Non-standard built-in functions

amp-bind supports the following non-standard functions:

Name Details
copyAndSplice Similar to Array#splice() except a copy of the spliced array is returned.

Arguments:
  • array: An array.
  • start: Index at which to start changing the array.
  • deleteCount: The number of items to delete, starting at index start.
  • items...: Items to add to the array, beginning at index start


Examples:
// Deleting an element. Returns [1, 3]
copyAndSplice([1, 2, 3], 1, 1)

// Replacing an item. Returns ['Pizza', 'Cake', 'Ice Cream']
copyAndSplice(['Pizza', 'Cake', 'Soda'], 2, 1, 'Ice Cream')

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'}})"...></button>
<button on="tap:AMP.setState({employee: {age: 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.

Please note that amp-bind will throw an error if you call AMP.setState() with an object literal that contains circular references.

Removing a variable

Remove an existing state variable by setting its value to null in AMP.setState(). Starting with the state from the previous example, pressing:

<button on="tap:AMP.setState({employee: {vehicle: null}})"...></button>

Will change the state to:

{
  employee: {
    name: 'John Smith',
    age: 48,
  }
}

Similarly:

<button on="tap:AMP.setState({employee: null})"...></button>

Will change the state to:

{
<!-- State is empty -->
}

Expression grammar

The BNF-like grammar for amp-bind expressions:

expr:
    operation
  | invocation
  | member_access
  | '(' expr ')'
  | variable
  | literal

operation:
    '!' 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
  | expr '?' expr ':' expr

invocation:
    expr '.' NAME args

args:
    '(' ')'
  | '(' array ')'
  ;

member_access:
    expr member
  ;

member:
    '.' NAME
  | '[' expr ']'

variable:
    NAME
  ;

literal:
    STRING
  | NUMBER
  | TRUE
  | FALSE
  | NULL
  | object_literal
  | array_literal

array_literal:
    '[' ']'
  | '[' array ']'

array:
    expr
  | array ',' expr

object_literal:
    '{' '}'
  | '{' object '}'

object:
    key_value
  | object ',' key_value

key_value:
  expr ':' expr