Skip to content

Commit

Permalink
Docs: add more details about block attributes (#33880)
Browse files Browse the repository at this point in the history
The block attributes documentation is a bit sparse on details, and I usually end up looking at core blocks for help.

This adds a lot more detail, with examples that show the block attributes, the HTML, and the attributes parsed from it.

It also includes the `enum` attribute type.
  • Loading branch information
johngodley authored Aug 9, 2021
1 parent 165b92a commit a631bf1
Showing 1 changed file with 246 additions and 38 deletions.
284 changes: 246 additions & 38 deletions docs/reference-guides/block-api/block-attributes.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,143 @@
# Attributes

Block attributes provide information about the data stored by a block. For example, rich content, a list of image URLs, a background colour, or a button title.

A block can contain any number of attributes, and these are specified by the `attributes` field - an object where each key is the name of the attribute, and the value is the attribute definition.

The attribute definition will contain, at a minimum, either a `type` or an `enum`. There may be additional fields.

_Example_: Attributes object defining three attributes - `url`, `title`, and `size`.

```js
{
url: {
type: 'string',
source: 'attribute',
selector: 'img',
attribute: 'src',
},
title: {
type: 'string',
},
size: {
enum: [ 'large', 'small' ],
},
}
```

When a block is parsed this definition will be used to extract data from the block content. Anything that matches will be available to your block through the `attributes` prop.

This parsing process can be summarized as:

1. Extract value from the `source`.
1. Check value matches the `type`, or is one of the `enum` values.

_Example_: Attributes available in the `edit` and function, using the above attributes definition.

```js
function YourBlockEdit( { attributes } ) {
return (
<p>URL is { attributes.url }, title is { attributes.title }, and size is { attributes.size }.</p>
)
}
```

The block is responsible for using the `save` function to ensure that all attributes with a `source` field are saved according to the attributes definition. This is not automatic.

Attributes without a `source` will be automatically saved in the block [comment delimiter](/docs/getting-started/architecture/key-concepts.md#delimiters-and-parsing-expression-grammar).

For example, using the above attributes definition you would need to ensure that your `save` function has a corresponding img tag for the `url` attribute. The `title` and `size` attributes will be saved in the comment delimiter.

_Example_: Example `save` function that contains the `url` attribute

```js
function YourBlockSave( { attributes } ) {
return (
<img src={ attributes.url } />
)
}
```

The saved HTML will contain the `title` and `size` in the comment delimiter, and the `url` in the `img` node.

```html
<!-- block:your-block {"title":"hello world","size":"large"} -->
<img src="/image.jpg" />
<!-- /block:your-block -->
```

If an attributes change over time then a [block deprecation](block-deprecation.md) can help migrate from an older attribute, or remove it entirely.

## Type Validation

The only required field for an attribute is the `type` field. It indicates the type of data that is stored within the attribute.
The `type` indicates the type of data that is stored by the attribute. It does not indicate where the data is stored, which is defined by the `source` field.

A `type` is required, unless an `enum` is provided. A `type` can be used with an `enum`.

Accepted values in the `type` field MUST be one of the following:
The `type` field MUST be one of the following:

- null
- boolean
- object
- array
- number
- string
- integer
- `null`
- `boolean`
- `object`
- `array`
- `string`
- `integer`
- `number` (same as `integer`)

See [WordPress's REST API documentation](https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/) for additional details.
Note that the validity of an `object` is determined by your `source`. For an example, see the `query` details below.

## Common Sources
## Enum Validation

Attribute sources are used to define how the block attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block.
An attribute can be defined as one of a fixed set of values. This is specified by an `enum`, which contains an array of allowed values:

If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](/docs/getting-started/architecture/key-concepts.md#delimiters-and-parsing-expression-grammar).
_Example_: Example `enum`.

The keys specified in the attributes source object are named as you see fit. The result of the attribute source definition is assigned as a value to each key.
```js
{
size: {
enum: [ 'large', 'small', 'tiny' ]
}
}
```

If no selector argument is specified, the source definition runs against the block's root node. If a selector argument is specified, it will run against the specified element(s) contained within the block.
## Value Source

The selector specified can be an HTML tag, or anything queryable such as a class or id attribute, see examples below.
Attribute sources are used to define how the attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block.

The available `source` values are:
- `(no value)` - when no `source` is specified then data is stored in the block's [comment delimiter](/docs/getting-started/architecture/key-concepts.md#delimiters-and-parsing-expression-grammar).
- `attribute` - data is stored in an HTML element attribute.
- `text` - data is stored in HTML text.
- `html` - data is stored in HTML. This is typically used by `RichText`.
- `query` - data is stored as an array of objects.
- `meta` - data is stored in post meta (deprecated).

The `source` field is usually combined with a `selector` field. If no selector argument is specified, the source definition runs against the block's root node. If a selector argument is specified, it will run against the matching element(s) within the block.

The `selector` can be an HTML tag, or anything queryable with [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector), such as a class or id attribute. Examples are given below.

For example, a `selector` of `img` will match an `img` element, and `img.class` will match an `img` element that has a class of `class`.

Under the hood, attribute sources are a superset of the functionality provided by [hpq](https://github.com/aduth/hpq), a small library used to parse and query HTML markup into an object shape.

### `attribute`
To summarize, the `source` determines where data is stored in your content, and the `type` determines what that data is. To reduce the amount of data stored it is usually better to store as much data as possible within HTML rather than as attributes within the comment delimiter.

Use `attribute` to extract the value of an attribute from markup.
### `attribute` source

Use an `attribute` source to extract the value from an attribute in the markup. The attribute is specified by the `attribute` field, which must be supplied.

_Example_: Extract the `src` attribute from an image found in the block's markup.

Saved content:
```html
<div>
Block Content

<img src="https://lorempixel.com/1200/800/" />
</div>
```

Attribute definition:
```js
{
url: {
Expand All @@ -45,7 +147,11 @@ _Example_: Extract the `src` attribute from an image found in the block's markup
attribute: 'src',
}
}
// { "url": "https://lorempixel.com/1200/800/" }
```

Attribute available in the block:
```js
{ "url": "https://lorempixel.com/1200/800/" }
```

Most attributes from markup will be of type `string`. Numeric attributes in HTML are still stored as strings, and are not converted automatically.
Expand All @@ -59,7 +165,10 @@ Most attributes from markup will be of type `string`. Numeric attributes in HTML
attribute: 'width',
}
}
// { "width": "50" }
```

```js
{ "width": "50" }
```

The only exception is when checking for the existence of an attribute (for example, the `disabled` attribute on a `button`). In that case type `boolean` can be used and the stored value will be a boolean.
Expand All @@ -73,13 +182,26 @@ The only exception is when checking for the existence of an attribute (for examp
attribute: 'disabled',
}
}
// { "disabled": true }
```

### `text`
```js
{ "disabled": true }
```

### `text` source

Use `text` to extract the inner text from markup.
Use `text` to extract the inner text from markup. Note that HTML is returned according to the rules of [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent).

Saved content:
```html
<figure>
<img src="/image.jpg" />

<figcaption>The inner text of the figcaption element</figcaption>
</figure>
```

Attribute definition:
```js
{
content: {
Expand All @@ -88,35 +210,67 @@ Use `text` to extract the inner text from markup.
selector: 'figcaption',
}
}
// { "content": "The inner text of the figcaption element" }
```

Attribute available in the block:
```js
{ "content": "The inner text of the figcaption element" }
```

Another example, using `text` as the source, and using `.my-content` class as the selector to extract text:

Saved content:
```html
<div>
<img src="/image.jpg" />

<p class="my-content">The inner text of .my-content class</p>
</div>
```

Attribute definition:
```js
{
attributes {
content: {
type: 'string',
source: 'text',
selector: '.my-content',
}
}
// { "content": "The inner text of .my-content class" }
```

Attribute available in the block:
```js
{ "content": "The inner text of .my-content class" }
```

### `html`

Use `html` to extract the inner HTML from markup.
Use `html` to extract the inner HTML from markup. Note that text is returned according to the rules of [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerHTML).

Saved content:
```html
<figure>
<img src="/image.jpg" />

<figcaption>The inner text of the <strong>figcaption</strong> element</figcaption>
</figure>
```

Attribute definition:
```js
{
attributes {
content: {
type: 'string',
source: 'html',
selector: 'figcaption',
}
}
// { "content": "The inner text of the <strong>figcaption</strong> element" }
```

Attribute available in the block:
```js
{ "content": "The inner text of the <strong>figcaption</strong> element" }
```

Use the `multiline` property to extract the inner HTML of matching tag names for the use in `RichText` with the `multiline` prop.
Expand All @@ -130,15 +284,30 @@ Use the `multiline` property to extract the inner HTML of matching tag names for
selector: 'blockquote',
}
}
// { "content": "<p>First line</p><p>Second line</p>" }
```

Attribute available in the block:
```js
{ "content": "<p>First line</p><p>Second line</p>" }
```

### `query`

Use `query` to extract an array of values from markup. Entries of the array are determined by the selector argument, where each matched element within the block will have an entry structured corresponding to the second argument, an object of attribute sources.
Use `query` to extract an array of values from markup. Entries of the array are determined by the `selector` argument, where each matched element within the block will have an entry structured corresponding to the second argument, an object of attribute sources.

The `query` field is effectively a nested block attributes definition. It is possible (although not necessarily recommended) to nest further.

_Example_: Extract `src` and `alt` from each image element in the block's markup.

Saved content:
```html
<div>
<img src="https://lorempixel.com/1200/800/" alt="large image" />
<img src="https://lorempixel.com/50/50/" alt="small image" />
</div>
```

Attribute definition:
```js
{
images: {
Expand All @@ -159,12 +328,16 @@ _Example_: Extract `src` and `alt` from each image element in the block's markup
}
}
}
// {
// "images": [
// { "url": "https://lorempixel.com/1200/800/", "alt": "large image" },
// { "url": "https://lorempixel.com/50/50/", "alt": "small image" }
// ]
// }
```

Attribute available in the block:
```js
{
"images": [
{ "url": "https://lorempixel.com/1200/800/", "alt": "large image" },
{ "url": "https://lorempixel.com/50/50/", "alt": "small image" }
]
}
```

## Meta (deprecated)
Expand All @@ -176,7 +349,7 @@ Although attributes may be obtained from a post's meta, meta attribute sources a
Attributes may be obtained from a post's meta rather than from the block's representation in saved post content. For this, an attribute is required to specify its corresponding meta key under the `meta` key:

```js
attributes: {
{
author: {
type: 'string',
source: 'meta',
Expand Down Expand Up @@ -257,3 +430,38 @@ function onChange( event ) {
props.setAttributes( { authorCount: Number( event.target.value ) } );
}
```

## Default Value

A block attribute can contain a default value, which will be used if the `type` and `source` do not match anything within the block content.

The value is provided by the `default` field, and the value should match the expected format of the attribute.

_Example_: Example `default` values.

```js
{
type: 'string',
default: 'hello world'
}
```

```js
{
type: 'array',
default: [
{ "url": "https://lorempixel.com/1200/800/", "alt": "large image" },
{ "url": "https://lorempixel.com/50/50/", "alt": "small image" }
]
}
```

```js
{
type: 'object',
default: {
width: 100,
title: 'title'
}
}
```

0 comments on commit a631bf1

Please sign in to comment.