AMP

amp-bind

Añade interactividad personalizada utilizando data bindings y expresiones.

Secuencia de comandos obligatoria
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
Ejemplos
Tutoriales Crear páginas de AMP interactivas

Descripción general

El componente amp-bind te permite añadir interactividad personalizada con reconocimiento de estado a tus páginas de AMP mediante el uso de data binding y expresiones similares a JS.

Echa un vistazo a este vídeo para conocer los aspectos básicos de amp-bind.

Un ejemplo muy sencillo

En el siguiente ejemplo, si tocas el botón, el texto del elemento <p> pasará de ser "Hello World" a "Hello amp-bind".

<p [text]="'Hello ' + foo">Hello World</p>

<button on="tap:AMP.setState({foo: 'amp-bind'})">Say "Hello amp-bind"</button>

Por cuestiones de rendimiento y para evitar que el contenido se desplace de forma inesperada, amp-bind no evalúa las expresiones al cargar la página. Esto quiere decir que los elementos visuales deben tener un estado predeterminado y no depender de amp-bind para el renderizado inicial.

¿Cómo funciona?

amp-bind tiene tres componentes principales:

  1. Estado: un estado JSON mutable que afecta al documento. En el ejemplo que aparece más arriba, el estado está vacío antes de tocar el botón. Después de tocar el botón, el estado es {foo: 'amp-bind'}.
  2. Expresiones: son expresiones similares a JavaScript que pueden hacer referencia al estado. El ejemplo que aparece más arriba tiene una única expresión, 'Hello ' + foo, que concatena el literal de cadena 'Hello ' y la variable de estado foo. Una expresión puede contener un máximo de 100 operandos.
  3. Bindings: son atributos especiales de la forma [property] que enlazan la propiedad de un elemento con una expresión. El ejemplo anterior tiene un único binding, [text], que actualiza el texto del elemento <p> cada vez que cambia el valor de la expresión.

amp-bind pone un especial énfasis en garantizar la velocidad, la seguridad y el rendimiento de las páginas de AMP.

Veamos un ejemplo ligeramente más complejo:

<!-- Se pueden almacenar datos JSON complejos anidados en elementos <amp-state> -->
 <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 + '.'">Esto es un perro.</p>

<!-- También se pueden añadir o eliminar clases de CSS mediante [class]. -->
<p class="greenBackground" [class]="myAnimals[currentAnimal].style">
  Cada animal tiene un color de fondo diferente.
</p>

<!-- O bien cambia el src de una imagen por el binding [src]. -->
<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>

Cuando se pulsa el botón:

  1. El estado se actualiza con currentAnimal, que se ha definido como 'cat'.
  2. Se evalúan las expresiones que dependen de currentAnimal:

    • 'This is a ' + currentAnimal + '.' => 'This is a cat.'
    • myAnimals[currentAnimal].style => 'redBackground'
    • myAnimals[currentAnimal].imageUrl => /img/cat.jpg
  3. Se actualizan los bindings que dependen de las expresiones modificadas:

    • El texto del primer elemento <p> será "This is a cat".
    • El atributo class del segundo elemento <p> será" "redBackground".
    • El elemento amp-img hará que se muestre la imagen de un gato.

Prueba la demostración de este ejemplo con anotaciones de código.

Información detallada

País

Cada documento AMP que utiliza amp-bind contiene datos JSON mutables que afectan a dicho documento, a los que llamamos estado.

Inicializar el estado mediante amp-state

El estado de amp-bind se puede inicializar mediante el componente amp-state:

<amp-state id="myState">
  <script type="application/json">
    {
      "foo": "bar"
    }
  </script>
</amp-state>

Las expresiones pueden hacer referencia a variables de estado mediante la sintaxis de puntos. En este ejemplo, myState.foo dará como resultado "bar".

  • El JSON secundario de un elemento <amp-state> puede tener un tamaño máximo de 100 KB.
  • También se puede especificar una URL de CORS para un elemento <amp-state> en lugar de una secuencia de comandos JSON secundaria. Para obtener más información, consulta el Anexo.

Actualizar el estado

La acción refresh es compatible con este componente y se puede utilizar para actualizar el contenido del estado.

<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>

Actualizar el estado mediante AMP.setState()

La acción AMP.setState() combina una literal de objeto con el estado. Por ejemplo, cuando se pulsa el botón que aparece más abajo, AMP.setState() combinará mediante deepmerge la literal de objeto con el estado.

<!-- 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>

En general, los objetos anidados se combinarán con hasta 10 objetos por debajo. Se pueden omitir todas las variables, incluidas las que introduce amp-state.

Cuando se activa debido a determinados eventos, AMP.setState() también puede acceder a datos relacionados con los eventos de la propiedad event.

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

Modificar el historial mediante AMP.pushState()

La acción AMP.pushState() es similar a AMP.setState(), con la diferencia de que también añade una entrada a la pila del historial de navegación. Al deshacer esta entrada del historial (por ejemplo, volviendo a la página anterior), se restaura el valor anterior de las variables que define AMP.pushState().

Por ejemplo:

<button on="tap:AMP.pushState({foo: '123'})">Set 'foo' to 123</button>
  • Al tocar el botón, se define la variable foo en 123 y se envía una nueva entrada al historial.
  • Al volver a la página anterior, se restaurará el valor previo de foo, es decir, "bar" (equivale a hacer una llamada a AMP.setState({foo: 'bar'}).

Expresiones

Las expresiones son similares a JavaScript, con algunas diferencias importantes.

Diferencias con respecto a JavaScript

  • Las expresiones solo pueden acceder al estado del documento al que pertenecen.
  • Las expresiones ****no tienen acceso a variables globales como window o document.
  • Solo se pueden utilizar los operadores y las funciones incluidos en la lista blanca.
  • Por lo general, no se admiten las funciones, las clases ni los bucles personalizados. Se admiten las funciones de flecha como parámetros; por ejemplo, Array.prototype.map.
  • Las variables no definidas y los índices de matriz fuera de límites devuelven null en lugar de undefined o de generar errores.
  • Actualmente, una expresión puede tener un máximo de 50 operandos por cuestiones de rendimiento. Ponte en contacto con nosotros si esta cantidad no te resulta suficiente.

Encontrarás la expresión gramatical completa y la implementación en bind-expr-impl.jison y bind-expression.js.

Ejemplos

Las expresiones que aparecen a continuación son válidas:

1 + '1'           // 11
1 + (+'1')        // 2
!0                // true
null || 'default' // 'default'

Funciones incluidas en la lista blanca

Tipo de objeto Funciones Ejemplo
Matriz1 concat
filter
includes
indexOf
join
lastIndexOf
map
reduce
slice
some
sort (no es in-place)
splice (no es 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)
Número toExponential
toFixed
toPrecision
toString
// Returns 3.
                (3.14).toFixed()
// Returns '3.14'.
                  (3.14).toString()
Cadena charAt
charCodeAt
concat
indexOf
lastIndexOf
slice
split
substr
substring
toLowerCase
toUpperCase
// Returns 'abcdef'.
                      abc'.concat('def')
Operación matemática2 abs
ceil
floor
max
min
random
round
sign
// Returns 1.
                          abs(-1)
Objeto2 keys
values
// Returns ['a', 'b'].
                            keys({a: 1, b: 2})
// Returns [1, 2].
                              values({a: 1, b: 2}
Global2 encodeURI
encodeURIComponent
// Returns 'Hello%20world'.
                                encodeURIComponent('Hello world')

1 Las funciones de flecha de un solo parámetro no pueden tener paréntesis. Por ejemplo, utiliza x => x + 1 en lugar de (x) => x + 1. Además, sort() y splice() devuelven copias modificadas en lugar de funcionar in situ. 2 Las funciones estáticas no llevan espacios de nombre. Por ejemplo, utiliza abs(-1) en lugar de Math.abs(-1).

Definir macros mediante amp-bind-macro

Los fragmentos de expresión de amp-bind se pueden reutilizar definiendo un amp-bind-macro. Este elemento` permite definir una expresión que utiliza cero o más argumentos y hace referencia al estado actual. Se puede invocar una macro como si fuera una función haciendo referencia en cualquier parte del documento al valor de su atributoid`.

<amp-bind-macro id="circleArea" arguments="radius" expression="3.14 * radius * radius"></amp-bind-macro>

<div>
  El círculo tiene un área de <span [text]="circleArea(myCircle.radius)">0</span>.
</div>

Una macro también puede llamar a otras macros que se han definido antes que a sí misma. Una macro no puede hacerse llamadas a sí misma de forma recursiva.

Bindings

Un binding es un atributo especial del formulario [property] que vincula la propiedad de un elemento con una expresión. También se puede utilizar una sintaxis alternativa compatible con XML mediante data-amp-bind-property.

Cuando el estado cambia, las expresiones se vuelven a evaluar y las propiedades de los elementos vinculados se actualizan con los resultados de la nueva expresión.

amp-bind admite data bindings para cuatro tipos de estado de elemento:

Tipo Atributos Información detallada
Node.textContent [text] Compatible con la mayoría de los elementos de texto.
Clases de CSS [class] El resultado de la expresión debe ser una cadena delimitada por espacios.
Atributo hidden [hidden] Debe ser una expresión booleana.
Tamaño de los elementos AMP [width]
[height]
Cambia la anchura o la altura del elemento AMP.
Atributos específicos de elementos Varios

Notas sobre los bindings:

  • Por motivos de seguridad, no se permite hacer bindings con innerHTML.
  • Todos los bindings de atributo se depuran para eliminar los valores que no son seguros (p. ej., javascript:).
  • Los resultados de las expresiones booleanas habilitan o inhabilitan los atributos booleanos. Por ejemplo: <amp-video [controls]="expr"...>. Cuando expr da como resultado true, el elemento <amp-video> tiene el atributo controls. Cuando expr da como resultado false, se elimina el atributo controls.
  • Incluir caracteres de corchetes [ y ] en nombres de atributos puede dar problemas al escribir XML (p. ej., XHTML o JSX) o atributos a través de las API de DOM. En estos casos, utiliza la sintaxis alternativa data-amp-bind-x="foo" en lugar de [x]="foo".

Atributos específicos de los elementos

Solo se admiten los bindings a los siguientes componentes y atributos:

Componente Atributos Comportamiento
<amp-brightcove> [data-account]
[data-embed]
[data-player]
[data-player-id]
[data-playlist-id]
[data-video-id]
Cambia el vídeo de Brightcove que se muestra.
<amp-carousel type=slides> [slide]* Cambia el índice de diapositiva que se muestra actualmente. Ver un ejemplo
<amp-date-picker> [min]
[max]
Define la fecha más temprana que se puede seleccionar.
Define la fecha más tardía que se puede seleccionar.
<amp-google-document-embed> [src]
[title]
Muestra el documento en la URL actualizada.
Cambia el título del documento.
<amp-iframe> [src] Cambia la URL de origen del iframe.
<amp-img> [alt]
[attribution]
[src]
[srcset]
Al hacer un binding a [src], asegúrate de hacerlo también a [srcset] para que el funcione en caché.
Consulta los atributos de "amp-img" correspondientes.
<amp-lightbox> [open]* Muestra u oculta el lightbox. Consejo: Utiliza on="lightboxClose: AMP.setState(...)" para actualizar las variables cuando el lightbox esté cerrado.
<amp-list> [src] Si la expresión es una cadena, recupera y renderiza un JSON de la URL de la cadena; si es un objeto o una matriz, renderiza los datos de la expresión.
<amp-selector> [selected]*
[disabled]
Cambia los elementos secundarios seleccionados actualmente
identificados por sus valores de atributo option. Admite una lista de valores separados por comas si hay varios elementos seleccionados. Ver un ejemplo
<amp-state> [src] Recupera un JSON de la nueva URL y lo combina con el estado que ya existe. Ten en cuenta que la siguiente actualización ignorará los elementos <amp-state> para evitar los ciclos.
<amp-video> [alt]
[attribution]
[controls]
[loop]
[poster]
[preload]
[src]
Consulta los atributos de "amp-video" correspondientes.
<amp-youtube> [data-videoid] Cambia el vídeo de YouTube que se muestra.
<a> [href] Cambia el enlace.
<button> [disabled]
[type]
[value]
Consulta los atributos de "button" correspondientes.
<details> [open] Consulta los atributos de "details" correspondientes.
<fieldset> [disabled] Habilita o inhabilita el elemento "fieldset".
<image> [xlink:href]
Consulta los atributos de "image" correspondientes.
<input> [accept]
[accessKey]
[autocomplete]
[checked]
[disabled]
[height]
[inputmode]
[max]
[maxlength]
[min]
[minlength]
[multiple]
[pattern]
[placeholder]
[readonly]
[required]
[selectiondirection]
[size]
[spellcheck]
[step]
[type]
[value]
[width]
Consulta los atributos de "input" correspondientes.
<option> [disabled]
[label]
[selected]
[value]
Consulta los atributos de "options" correspondientes.
<optgroup> [disabled]
[label]
Consulta los atributos de "optgroup" correspondientes
<select> [autofocus]
[disabled]
[multiple]
[required]
[size]
Consulta los atributos de "select" correspondientes.
<source> [src]
[type]
Consulta los atributos de "source" correspondientes.
<track> [label]
[src]
[srclang]
Consulta los atributos de "track" correspondientes.
<textarea> [autocomplete]
[autofocus]
[cols]
[disabled]
[maxlength]
[minlength]
[placeholder]
[readonly]
[required]
[rows]
[selectiondirection]
[selectionend]
[selectionstart]
[spellcheck]
[wrap]
Consulta los atributos de "textarea" correspondientes.

* Indica atributos vinculables que no tienen contrapartida que no se pueda vincular.

Depuración

Haz las pruebas en modo de desarrollo (con el fragmento de URL #development=1) para hacer que se resalten los mensajes de advertencia y error durante el desarrollo y para acceder a funciones de depuración especiales.

Advertencias

En el modo de desarrollo, amp-bind enviará una advertencia cuando el valor predeterminado de un atributo vinculado no coincida con el resultado inicial de su expresión correspondiente. Esto puede evitar mutaciones no deseadas causadas por cambios en otras variables de estado. Por ejemplo:

<!-- El valor de clase predeterminado del elemento ('def') no coincide con el resultado de la expresión [class] ('abc'), por lo que se emitirá una advertencia en el modo de desarrollo. -->

<p class="def" [class]="'abc'"></p>

En el modo de desarrollo, amp-bind también enviará una advertencia cuando se eliminen referencias a variables o propiedades no definidas. Esto también puede evitar mutaciones no deseadas debidas a resultados de expresión null. Por ejemplo:

<amp-state id="myAmpState">
  <script type="application/json">
    { "foo": 123 }
</script>
</amp-state></p>

<!-- amp-state#myAmpState no tiene una variable `bar`, por lo que se emitirá una advertencia en el modo de desarrollo. -->

<p [text]="myAmpState.bar">Un texto de marcador de posición.</p>

Errores

Hay varios tipos de errores de tiempo de ejecución que se pueden encontrar al trabajar con amp-bind.

Tipo SMS Sugerencia
Binding no válido No se permite el binding a [someBogusAttribute] en <P>. Utiliza solo bindings incluidos en la lista blanca.
Error de sintaxis Se ha producido un error de compilación de expresiones en... Comprueba que la expresión no tiene erratas.
Funciones no incluidas en la lista blanca La función alert no es compatible. Utiliza solo funciones incluidas en la lista blanca.
Resultado depurado "javascript:alert(1)" no es un resultado válido para [href]. Evita los protocolos de URL prohibidos o las expresiones que no admita AMP Validator.
Infracción de la política de seguridad de contenido No se ha podido crear un worker mediante 'blob:...' porque infringe la siguiente directiva de la política de seguridad de contenido... Añade default-src blob: a la política de seguridad de contenido de tu origen. amp-bind delega trabajo costoso a un Web Worker dedicado para garantizar un buen rendimiento.

Estado de depuración

Utiliza AMP.printState() para imprimir el estado actual en la consola.

Apéndice

Especificación de <amp-state>

Un elemento amp-state puede contener un elemento secundario <script> ****o bien un atributo src que contenga una URL CORS de un punto final remoto JSON, pero no ambos.

<amp-state id="myLocalState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state></p>

<p><amp-state id="myRemoteState" src="https://data.com/articles.json">
</amp-state>

Procesamiento por lotes de XHR

AMP envía XMLHttpRequests (XHR) por lotes a puntos de conexión JSON. Es decir, puedes utilizar una sola solicitud de datos JSON como fuente de datos para varios consumidores (p. ej., varios elementos amp-state) en una página AMP. Por ejemplo, si tu elemento amp-state hace un XHR a un punto de conexión, y mientras esté procesándose, los XHR posteriores que se hagan al mismo punto de conexión no se activarán, y devolverán en su lugar los resultados del primer XHR.

Atributos

src URL del punto de conexión remoto que devolverá el JSON que actualizará este amp-state. Debe ser un servicio CORS HTTP. El atributo src admite todas las sustituciones estándar de variables de URL. Para obtener más información, consulta la guía de sustituciones.
El punto de conexión debe implementar los requisitos que se detallan en la especificación de las solicitudes CORS de AMP.
credentials (opcional) Define una opción credentials tal y como especifica la API de Fetch.
  • Valores admitidos: `omit` e `include`
  • Valores predeterminados: `omit`
Para enviar credenciales, transfiere el valor de include. Si se define este valor, la respuesta debe seguir las directrices de seguridad de AMP CORS.

Combinar con deepmerge mediante AMP.setState()

Cuando se hace una llamada a AMP.setState(), amp-bind lleva a cabo una combinación con deepmerge y fusiona la literal del objeto con el estado actual. Todas las variables de la literal del objeto se añaden directamente al estado, excepto los objetos anidados, que se fusionan de forma recursiva. Los primitivos y las matrices que se encuentran en el estado siempre se sobrescriben en la literal del objeto con variables del mismo nombre.

Observa el siguiente ejemplo:

{
  <!-- 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>

Cuando se pulsa el primer botón, el estado cambia a:

{
  employee: {
    name: 'John Smith',
    age: 47,
    vehicle: 'Car',
    }
  }

Cuando se pulse el segundo botón, amp-bind combinará de forma recursiva el argumento de la literal del objeto, {employee: {age: 64}}, con el estado existente.

{
  employee: {
    name: 'John Smith',
    age: 64,
    vehicle: 'Car',
    }
  }

Se ha actualizado employee.age, pero las claves employee.name y employee.vehicle no han cambiado.

Ten en cuenta que amp-bind generará un error si haces una llamada a AMP.setState() utilizando una literal de objeto que contiene referencias circulares.

Eliminar una variable

Puedes eliminar una variable de estado existente definiendo su valor como null en AMP.setState(). Empezando por el estado del ejemplo anterior, si se pulsa:

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

El estado cambiará a:

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

Veamos otro ejemplo parecido:

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

Pulsando el botón, el estado cambiará a:

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

Gramática de las expresiones

Esta es la gramática para las expresiones de amp-bind, similar a BNF:

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
¿Necesita ayuda adicional?

¿Ha leído este documento una docena de veces pero realmente no responde todas sus preguntas? Quizás otras personas piensen lo mismo: póngase en contacto con ellas en Stack Overflow.

Ir a Stack Overflow
¿Encontró un error o considera que falta una función?

¡El proyecto AMP alienta profundamente su participación y contribuciones! Esperamos que se convierta en un miembro permanente de nuestra comunidad de código abierto, pero también agradecemos las contribuciones esporádicas sobre los temas que le apasionan especialmente.

Ir a GitHub