Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API documentation for blocks and element #302

Merged
merged 7 commits into from
Mar 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions blocks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Blocks

Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage. The idea combines concepts of what in WordPress today we achieve with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience.

For more context, refer to [_What Are Little Blocks Made Of?_](https://make.wordpress.org/design/2017/01/25/what-are-little-blocks-made-of/) from the [Make WordPress Design](https://make.wordpress.org/design/) blog.

The following documentation outlines steps you as a developer will need to follow to add your own custom blocks to WordPress's editor interfaces.

## Getting Started

If you're not already accustomed to working with JavaScript in your WordPress plugins, you may first want to reference the guide on [_Including CSS & JavaScript_](https://developer.wordpress.org/themes/basics/including-css-javascript/) in the Theme Handbook.

At a minimum, you will need to enqueue scripts for your block as part of a `block_enqueue_scripts` action callback, with a dependency on the `wp-blocks` script handle:

```php
<?php
// plugin.php

function myplugin_block_enqueue_scripts() {
wp_enqueue_script( 'myplugin-block', plugins_url( 'block.js', __FILE__ ), array( 'wp-blocks' ) );
}
add_action( 'block_enqueue_scripts', 'myplugin_block_enqueue_scripts' );
```

The following sections will describe what you'll need to include in `block.js` to describe the behavior of your custom block.

## Example

Let's imagine you wanted to define a block to show a randomly generated image in a post's content using [lorempixel.com](http://lorempixel.com/). The service provides a choice of category and you'd like to offer this as an option when editing the post.

Take a step back and consider the ideal workflow for adding a new random image. After inserting the block, as a user I'd expect it to be shown in some empty state, with an option to choose a category in a select dropdown. Upon confirming my selection, a preview of the image should be shown next to the dropdown. At this point, you might realize that while you'd want some controls to be shown when editing content, the markup included in the published post might not appear the same (your visitors should not see a dropdown field when reading your content). This leads to the first requirement of describing a block: __you will need to provide implementations both for what's to be shown in an editor and what's to be saved with the published content__. You needn't worry about redundant effort here, as concepts of [Elements](../elements/README.md) and componentization provide an avenue for sharing common behaviors.

Now that we've considered user interaction, you should think about the underlying values that determine the markup generated by your block. In our example, the output is affected only when the category changes. Put another way: __the output of a block is a function of its attributes__. The category, a simple string, is the only thing we require to be able to generate the image we want to include in the published content. We call these underlying values of a block instance its _attributes_.

With these concepts in mind, let's explore an implementation of our random image block:

```php
<?php
// plugin.php

function random_image_block_enqueue_scripts() {
wp_enqueue_script( 'random-image-block', plugins_url( 'block.js', __FILE__ ), array( 'wp-blocks', 'wp-element' ) );
}
add_action( 'block_enqueue_scripts', 'random_image_block_enqueue_scripts' );
```

```js
// block.js
var el = wp.element.createElement,
query = wp.blocks.query;

function RandomImage( props ) {
var src = 'http://lorempixel.com/400/200/' + props.category;

return el( 'img', {
src: src,
alt: props.category
} );
}

wp.blocks.registerBlock( 'myplugin/random-image', {
title: 'Random Image',

icon: 'format-image',

attributes: {
category: query.attr( 'img', 'alt' )
},

edit: function( attributes, setAttributes ) {
var category = attributes.category,
children;

function setCategory( event ) {
var selected = event.target.querySelector( 'option:checked' );
setAttributes( { category: selected.value } );
event.preventDefault();
}

children = [];
if ( category ) {
children.push( RandomImage( { category: category } ) );
}

children.push(
el( 'select', { value: category, onChange: setCategory },
el( 'option', null, '- Select -' ),
el( 'option', { value: 'sports' }, 'Sports' ),
el( 'option', { value: 'animals' }, 'Animals' ),
el( 'option', { value: 'nature' }, 'Nature' )
)
);

return el( 'form', { onSubmit: setCategory }, children );
},

save: function( attributes ) {
return RandomImage( { category: attributes.category } );
}
} );
```

_[(Example in ES2015+, JSX)](https://gist.github.com/aduth/fb1cc9a2296110a62b96383e4b2e8a7c)_

Let's briefly review a few items you might observe in the implementation:

- When registering a new block, you must prefix its slug with a namespace for your plugin. This helps prevent conflicts when more than one plugin registers a block with the same slug.
- You will use `createElement` to describe the structure of your block's markup. See the [Element documentation](../element/README.md) for more information.
- Extracting `RandomImage` to a separate function allows us to reuse it in both the editor-specific interface and the published content.
- The `edit` function should handle any case where an attribute is unset, as in the case of the block being newly inserted.
- We only change the attributes of a block by calling the `setAttributes` helper. Never assign a value on the attributes object directly.
- React provides conveniences for working with `select` element with [`value` and `onChange` props](https://facebook.github.io/react/docs/forms.html#the-select-tag).

By concerning yourself only with describing the markup of a block given its attributes, you need not worry about maintaining the state of the page, or how your block interacts in the context of the surrounding editor.

But how does the markup become an object of attributes? We need a pattern for encoding the values into the published post's markup, and then retrieving them the next time the post is edited. This is the motivation for the block's `attributes` property. The shape of this object matches that of the attributes object we'd like to receive, where each value is a [__matcher__](http://github.com/aduth/hpq) which tries to find the desired value from the markup of the block.

In the random image block above, we've given the `alt` attribute of the image a secondary responsibility of tracking the selected category. There are a few other ways we could have achieved this, but the category value happens to work well as an `alt` descriptor. In the `attributes` property, we define an object with a key of `category` whose value tries to find this `alt` attribute of the markup. If it's successful, the category's value in our `edit` and `save` functions will be assigned. In the case of a new block or invalid markup, this value would be `undefined`, so we adjust our return value accordingly.

## API

### `wp.blocks.registerBlock( slug: string, settings: Object )`

Registers a new block provided a unique slug and an object defining its behavior. Once registered, the block is made available as an option to any editor interface where blocks are implemented.

- `title: string` - A human-readable [localized](https://codex.wordpress.org/I18n_for_WordPress_Developers#Handling_JavaScript_files) label for the block. Shown in the block picker.
- `icon: string | WPElement` - Slug of the [Dashicon](https://developer.wordpress.org/resource/dashicons/#awards) to be shown in the control's button, or an element if you choose to render your own SVG.
- `edit( attributes: Object, setAttributes: Function ): WPElement` - Returns an element describing the markup of a block to be shown in the editor. A block can update its own state in response to events using the `setAttributes` function, passing an object of properties to be applied as a partial update.
- `save( attributes: Object ): WPElement` - Returns an element describing the markup of a block to be saved in the published content. This function is called before save and when switching to an editor's HTML view.
- `controls: string[]` - Slugs for controls to be made available to block. See also: [`wp.blocks.registerControl`](#wpblocksregistercontrol-slug-string-settings-object-)
- `encodeAttributes( attributes: Object ): Object` - Called when save markup is generated, this function allows you to control which attributes are to be encoded in the block comment metadata. By default, all attribute values not defined in the block's `attributes` property are serialized to the comment metadata. If defined, this function should return the subset of attributes to encode, or `null` to bypass default behavior.

### `wp.blocks.registerControl( slug: string, settings: Object )`

Registers a new block-level control. Controls appear in a block's toolbar when it receives focus if it is included in the block's `controls` option.

- `title: string` - A human-readable [localized](https://codex.wordpress.org/I18n_for_WordPress_Developers#Handling_JavaScript_files) label for the control. Shown in help tooltips.
- `icon: string | WPElement` - Slug of the [Dashicon](https://developer.wordpress.org/resource/dashicons/#awards) to be shown in the control's button, or an element if you choose to render your own SVG.
- `onClick( attributes: Object, setAttributes: Function )` - Click behavior for control. Use this to change or toggle an attribute of the block.
- `isVisible( attributes: Object ): boolean` - Called when a block receives focus or changes. Return `false` to prevent the control's button from being shown. If this option is not defined for a control, the button will always be shown.
- `isActive( attributes: Object ): boolean` - Called when a block receives focus or changes. Return `true` to apply an active effect to the control's button, in the case that the control's behavior is a toggle.

Inline controls for [`Editable`](#editable) elements are identical for every block and cannot be modified.

### `wp.blocks.getBlockSettings( slug: string )`

Returns settings associated with a registered block.

### `wp.blocks.getControlSettings( slug: string )`

Returns settings associated with a registered control.

## Components

Because many blocks share the same complex behaviors, the following components are made available to simplify implementations of your block's `edit` function.

### `Editable`

Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content), providing users the option to add emphasis to content or links to content. It behaves similar to a [controlled component](https://facebook.github.io/react/docs/forms.html#controlled-components), except that `onChange` is triggered less frequently than would be expected from a traditional `input` field, usually when the user exits the field.

The following props are made available:

- `inline: boolean` - If true, only inline elements are allowed to be used in inserted into the text, effectively disabling the behavior of the "Enter" key.
- `value: string` - Markup value of the editable field. Only valid markup is allowed, as determined by `inline` value and available controls.
- `onChange: Function` - Callback handler when the value of the field changes, passing the new value as its only argument.

Example:

```js
var el = wp.element.createElement,
Editable = wp.blocks.Editable;

function edit( attributes, setAttributes ) {
function onChange( value ) {
setAttributes( { text: value } );
}

return el( Editable, {
value: attributes.text,
onChange: onChange
} );
}
```
6 changes: 4 additions & 2 deletions blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export { default as parse } from './parser';
const blocks = {};

/**
* Registers a block.
* Registers a new block provided a unique slug and an object defining its
* behavior. Once registered, the block is made available as an option to any
* editor interface where blocks are implemented.
*
* @param {string} slug Block slug
* @param {Object} settings Block settings
Expand Down Expand Up @@ -68,7 +70,7 @@ export function unregisterBlock( slug ) {
}

/**
* Returns settings associated with a block.
* Returns settings associated with a registered block.
*
* @param {string} slug Block slug
* @return {?Object} Block settings
Expand Down
64 changes: 64 additions & 0 deletions element/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Element
=======

Element is, quite simply, an abstraction layer atop [React](https://facebook.github.io/react/).

You may find yourself asking, "Why an abstraction layer?". For a few reasons:

- In many applications, especially those extended by a rich plugin ecosystem as is the case with WordPress, it's wise to create interfaces to underlying third-party code. The thinking is that if ever a need arises to change or even replace the underlying implementation, it can be done without catestrophic rippling effects to dependent code, so long as the interface stays the same.
- It provides a mechanism to shield implementers by omitting features with uncertain futures (`createClass`, `PropTypes`).
- It helps avoid incompatibilities between versions by ensuring that every plugin operates on a single centralized version of the code.

On the `wp.element` global object, you will find the following, ordered roughly be likelihood you'll encounter it in your code:

- [`createElement`](https://facebook.github.io/react/docs/react-api.html#createelement)
- [`render`](https://facebook.github.io/react/docs/react-dom.html#render)

## Example

Let's render a customized greeting into an empty element:

```html
<div id="greeting"></div>
<script>
function Greeting( props ) {
return wp.element.createElement( 'span', null,
'Hello ' + props.toWhom + '!'
);
}

wp.element.render(
wp.element.createElement( Greeting, { toWhom: 'World' } ),
document.getElementById( 'app' )
);
</script>
```

Refer to the [official React Quick Start guide](https://facebook.github.io/react/docs/hello-world.html) for a more thorough walkthrough, in most cases substituting `React` and `ReactDOM` with `wp.element` in code examples.

## Why React?

At the risk of igniting debate surrounding any single "best" front-end framework, the choice to use any tool should be motivated specifically to serve the requirements of the system. In modeling the concept of a [block](../blocks/README.md), we observe the following technical requirements:

- An understanding of a block in terms of its underlying values (in the [random image example](../blocks/README.md#example), a category)
- A means to describe the UI of a block given these values

At its most basic, React provides a simple input / output mechanism. __Given a set of inputs ("props"), a developer describes the output to be shown on the page.__ This is most elegantly observed in its [function components](https://facebook.github.io/react/docs/components-and-props.html#functional-and-class-components). React serves the role of reconciling the desired output with the current state of the page.

The offerings of any framework necessarily become more complex as these requirements increase; many front-end frameworks prescribe ideas around page routing, retrieving and updating data, and managing layout. React is not immune to this, but the introduced complexity is rarely caused by React itself, but instead managing an arrangement of supporting tools. By moving these concerns out of sight to the internals of the system (WordPress core code), we can minimize the responsibilities of plugin authors to a small, clear set of touch points.

## JSX

While not at all a requirement to use React, [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) is a recommended syntax extension to compose elements more expressively. Through a build process, JSX is converted back to the `createElement` syntax you see earlier in this document.

If you've configured [Babel](http://babeljs.io/) for your project, you can opt in to JSX syntax by specifying the `pragma` option of the [`transform-react-jsx` plugin](https://www.npmjs.com/package/babel-plugin-transform-react-jsx) in your [`.babelrc` configuration](http://babeljs.io/docs/usage/babelrc/).

```json
{
"plugins": [
[ "transform-react-jsx", {
"pragma": "wp.element.createElement"
} ]
]
}
```