Skip to content

Commit

Permalink
Update edit-save documentation (#13578)
Browse files Browse the repository at this point in the history
* Update edit examples with ES5 code

* Add extra explanation and links to attributes and block tutorial

* Add two examples for attributes, edit, save

* Update docs/designers-developers/developers/block-api/block-edit-save.md

Co-Authored-By: mkaz <[email protected]>

* Remove spaces.

* Update docs/designers-developers/developers/block-api/block-edit-save.md

Co-Authored-By: mkaz <[email protected]>

* Update docs/designers-developers/developers/block-api/block-edit-save.md

Co-Authored-By: mkaz <[email protected]>

* Update docs/designers-developers/developers/block-api/block-edit-save.md

Co-Authored-By: mkaz <[email protected]>

* Make function defns consistent across ES5/ESNext

* Add clone example for ES5

* Update docs/designers-developers/developers/block-api/block-edit-save.md

Co-Authored-By: mkaz <[email protected]>

* Simplify ES5 example, props @nosolosw
  • Loading branch information
mkaz authored and youknowriad committed Mar 6, 2019
1 parent 3a2d9f2 commit 901fa2c
Showing 1 changed file with 251 additions and 22 deletions.
273 changes: 251 additions & 22 deletions docs/designers-developers/developers/block-api/block-edit-save.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,146 @@ When registering a block, the `edit` and `save` functions provide the interface

The `edit` function describes the structure of your block in the context of the editor. This represents what the editor will render when the block is used.

{% codetabs %}
{% ES5 %}
```js
// Defining the edit interface
edit() {
return <hr />;
// A static div
edit: function() {
return wp.element.createElement(
'div',
null,
'Your block.'
);
}
```
{% ESNext %}
```jsx
edit: () => {
return <div>Your block.</div>;
}
```
{% end %}

The function receives the following properties through an object argument:

### attributes

This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function:
This property surfaces all the available attributes and their corresponding values, as described by the `attributes` property when the block type was registered. See [attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for how to specify attribute sources.

In this case, assuming we had defined an attribute of `content` during block registration, we would receive and use that value in our edit function:

{% codetabs %}
{% ES5 %}
```js
// Defining the edit interface
edit( { attributes } ) {
edit: function( props ) {
return wp.element.createElement(
'div',
null,
props.attributes.content
);
}
```
{% ESNext %}
```js
edit: ( { attributes } ) => {
return <div>{ attributes.content }</div>;
}
```
{% end %}

The value of `attributes.content` will be displayed inside the `div` when inserting the block in the editor.

### className

This property returns the class name for the wrapper element. This is automatically added in the `save` method, but not on `edit`, as the root element may not correspond to what is _visually_ the main element of the block. You can request it to add it to the correct element in your function.

{% codetabs %}
{% ES5 %}
```js
edit: function( props ) {
return wp.element.createElement(
'div',
{ className: props.className },
props.attributes.content
);
}
```
{% ESNext %}
```js
// Defining the edit interface
edit( { attributes, className } ) {
edit: ( { attributes, className } ) => {
return <div className={ className }>{ attributes.content }</div>;
}
```
{% end %}

### isSelected

The isSelected property is an object that communicates whether the block is currently selected.

{% codetabs %}
{% ES5 %}
```js
// Defining the edit interface
edit( { attributes, className, isSelected } ) {
edit: function( props ) {
return wp.element.createElement(
'div',
{ className: props.className },
[
'Your block.',
props.isSelected ? wp.element.createElement(
'span',
null,
'Shows only when the block is selected.'
)
]
);
}
```
{% ESNext %}
```jsx
edit: ( { attributes, className, isSelected } ) => {
return (
<div className={ className }>
{ attributes.content }
Your block.
{ isSelected &&
<span>Shows only when the block is selected.</span>
}
</div>
);
}
```
{% end %}
### setAttributes
This function allows the block to update individual attributes based on user interactions.
{% codetabs %}
{% ES5 %}
```js
// Defining the edit interface
edit( { attributes, setAttributes, className, isSelected } ) {
edit: function( props ) {
// Simplify access to attributes
let content = props.attributes.content;
let mySetting = props.attributes.mySetting;

// Toggle a setting when the user clicks the button
let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } );
return wp.element.createElement(
'div',
{ className: props.className },
[
content,
props.isSelected ? wp.element.createElement(
'button',
{ onClick: toggleSetting },
'Toggle setting'
) : null
]
);
},
```
{% ESNext %}
```jsx
edit: ( { attributes, setAttributes, className, isSelected } ) => {
// Simplify access to attributes
const { content, mySetting } = attributes;

Expand All @@ -79,22 +161,41 @@ edit( { attributes, setAttributes, className, isSelected } ) {
);
}
```
{% end %}
When using attributes that are objects or arrays it's a good idea to copy or clone the attribute prior to updating it:
{% codetabs %}
{% ES5 %}
```js
// Good - cloning the old list
var newList = attributes.list.slice();

var addListItem = function( newListItem ) {
setAttributes( { list: newList.concat( [ newListItem ] ) } );
};

// Bad - the list from the existing attribute is modified directly to add the new list item:
var list = attributes.list;
var addListItem = function( newListItem ) {
list.push( newListItem );
setAttributes( { list: list } );
};
```
{% ESNext %}
```js
// Good - here a new array is created from the old list attribute and a new list item:
// Good - a new array is created from the old list attribute and a new list item:
const { list } = attributes;
const addListItem = ( newListItem ) => setAttributes( { list: [ ...list, newListItem ] } );

// Bad - here the list from the existing attribute is modified directly to add the new list item:
// Bad - the list from the existing attribute is modified directly to add the new list item:
const { list } = attributes;
const addListItem = ( newListItem ) => {
list.push( newListItem );
setAttributes( { list } );
};

```
{% end %}
Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes.
Expand All @@ -105,14 +206,18 @@ The `save` function defines the way in which the different attributes should be
{% codetabs %}
{% ES5 %}
```js
save() {
return wp.element.createElement( 'hr' );
save: function() {
return wp.element.createElement(
'div',
null,
'Your block.'
);
}
```
{% ESNext %}
```jsx
save() {
return <hr />;
save: () => {
return <div> Your block. </div>;
}
```
{% end %}
Expand All @@ -130,7 +235,7 @@ As with `edit`, the `save` function also receives an object argument including a
{% codetabs %}
{% ES5 %}
```js
save( props ) {
save: function( props ) {
return wp.element.createElement(
'div',
null,
Expand All @@ -140,12 +245,136 @@ save( props ) {
```
{% ESNext %}
```jsx
save( { attributes } ) {
save: ( { attributes } ) => {
return <div>{ attributes.content }</div>;
}
```
{% end %}
When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details.
## Examples
Here are a couple examples of using attributes, edit, and save all together. For a full working example, see the [Introducing Attributes and Editable Fields](/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md) section of the Block Tutorial.
### Saving Attributes to Child Elements
{% codetabs %}
{% ES5 %}
```js
attributes: {
content: {
type: 'string',
source: 'html',
selector: 'p'
}
},

edit: function( props ) {
var updateFieldValue = function( val ) {
props.setAttributes( { content: val } );
}
return wp.element.createElement(
wp.components.TextControl,
{
label: 'My Text Field',
value: props.attributes.content,
onChange: updateFieldValue,

}
);
},

save: function( props ) {
return el( 'p', {}, props.attributes.content );
},
```
{% ESNext %}
```jsx
attributes: {
content: {
type: 'string',
source: 'html',
selector: 'p'
}
},

edit: ( { attributes, setAttributes } ) => {
const updateFieldValue = ( val ) => {
setAttributes( { content: val } );
}
return <TextControl
label='My Text Field'
value={ attributes.content }
onChange={ updateFieldValue }
/>;
},

save: ( { attributes } ) => {
return <p> { attributes.content } </p>;
},
```
{% end %}
### Saving Attributes via Serialization
Ideally, the attributes saved should be included in the markup. However, there are times when this is not practical, so if no attribute source is specified the attribute is serialized and saved to the block's comment delimiter.
This example could be for a dynamic block, such as the [Latest Posts block](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/latest-posts/index.js), which renders the markup server-side. The save function is still required, however in this case it simply returns null since the block is not saving content from the editor.
{% codetabs %}
{% ES5 %}
```js
attributes: {
postsToShow: {
type: 'number',
}
},

edit: function( props ) {
return wp.element.createElement(
wp.components.TextControl,
{
label: 'Number Posts to Show',
value: props.attributes.postsToShow,
onChange: function( val ) {
props.setAttributes( { postsToShow: parseInt( val ) } );
},
}
);
},

save: function() {
return null;
}
```
{% ESNext %}
```jsx
attributes: {
postsToShow: {
type: 'number',
}
},

edit: ( { attributes, setAttributes } ) => {
return <TextControl
label='Number Posts to Show'
value={ attributes.postsToShow }
onChange={ ( val ) => {
setAttributes( { postsToShow: parseInt( val ) } );
}},
}
);
},

save: () => {
return null;
}
```
{% end %}


## Validation

When the editor loads, all blocks within post content are validated to determine their accuracy in order to protect against content loss. This is closely related to the saving implementation of a block, as a user may unintentionally remove or modify their content if the editor is unable to restore a block correctly. During editor initialization, the saved markup for each block is regenerated using the attributes that were parsed from the post's content. If the newly-generated markup does not match what was already stored in post content, the block is marked as invalid. This is because we assume that unless the user makes edits, the markup should remain identical to the saved content.
Expand Down

0 comments on commit 901fa2c

Please sign in to comment.