From 9067a5305258e698e68ff969e03fbacd7f20e4cb Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 22 Mar 2019 09:32:22 -0700 Subject: [PATCH 01/17] Add link to JS Build documentation --- .../developers/tutorials/block-tutorial/readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/readme.md b/docs/designers-developers/developers/tutorials/block-tutorial/readme.md index 0ff9418d01743a..a3ee5191dbca79 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/readme.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/readme.md @@ -4,4 +4,6 @@ The purpose of this tutorial is to step through the fundamentals of creating a n To follow along with this tutorial, you can [download the accompanying WordPress plugin](https://github.com/WordPress/gutenberg-examples) which includes all of the examples for you to try on your own site. At each step along the way, you should feel free to experiment by modifying the examples with your own ideas and observing the effects they have on the block's behavior. -Code snippets are provided both for "classic" JavaScript (ECMAScript 5, or "ES5"), as well as newer versions of the language standard (ES2015 and newer, or "ESNext"). You can change between them using tabs found above each code example. When choosing to author your blocks with ESNext, you will need a build step in order to support older browsers. Note that it is not required to use ESNext to create a new block, and you are welcome to use classic JavaScript if you so choose. +Code snippets are provided both for "classic" JavaScript (ECMAScript 5, or "ES5"), as well as newer versions of the language standard (ES2015 and newer, or "ESNext"). You can change between them using tabs found above each code example. When choosing to author your blocks with ESNext, you need to run [the JavaScript build step](/docs/designers-developers/developers/tutorials/javascript/js-build-setup/) in order to support older browsers. + +Note that it is not required to use ESNext to create a new block, and you are welcome to use classic JavaScript if you so choose. From 6725f86f7fc445ee4ba124d47b952224c4b59927 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 22 Mar 2019 10:58:01 -0700 Subject: [PATCH 02/17] Update step 01 to match examples repo --- .../writing-your-first-block-type.md | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index ea9a18bbf23684..50a7d025afbc07 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -11,18 +11,19 @@ While the block's editor behaviors are implemented in JavaScript, you'll need to ```php 'gutenberg-boilerplate-es5-step01', + register_block_type( 'gutenberg-examples/example-01-basic', array( + 'editor_script' => 'gutenberg-examples-01', ) ); + } -add_action( 'init', 'gutenberg_boilerplate_block' ); +add_action( 'init', 'gutenberg_examples_01_register_block() ' ); ``` Note the two script dependencies: @@ -39,44 +40,58 @@ With the script enqueued, let's look at the implementation of the block itself: {% codetabs %} {% ES5 %} ```js -var el = wp.element.createElement, - registerBlockType = wp.blocks.registerBlockType, - blockStyle = { backgroundColor: '#900', color: '#fff', padding: '20px' }; - -registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-01', { - title: 'Hello World (Step 1)', - - icon: 'universal-access-alt', - - category: 'layout', - - edit: function() { - return el( 'p', { style: blockStyle }, 'Hello editor.' ); - }, - - save: function() { - return el( 'p', { style: blockStyle }, 'Hello saved content.' ); - }, -} ); +( function( blocks, element ) { + var el = element.createElement; + + var blockStyle = { + backgroundColor: '#900', + color: '#fff', + padding: '20px', + }; + + blocks.registerBlockType( 'gutenberg-examples/example-01-basic', { + title: 'Example: Basic', + icon: 'universal-access-alt', + category: 'layout', + edit: function() { + return el( + 'p', + { style: blockStyle }, + 'Hello World, step 1 (from the editor).' + ); + }, + save: function() { + return el( + 'p', + { style: blockStyle }, + 'Hello World, step 1 (from the frontend).' + ); + }, + } ); +}( + window.wp.blocks, + window.wp.element +) ); ``` {% ESNext %} ```js const { registerBlockType } = wp.blocks; -const blockStyle = { backgroundColor: '#900', color: '#fff', padding: '20px' }; -registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', { - title: 'Hello World (Step 1)', +const blockStyle = { + backgroundColor: '#900', + color: '#fff', + padding: '20px', +}; +registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { + title: 'Example: Basic (esnext)', icon: 'universal-access-alt', - category: 'layout', - edit() { - return

Hello editor.

; + return
Basic example with JSX! (editor)
; }, - save() { - return

Hello saved content.

; + return
Basic example with JSX! (front)
; }, } ); ``` @@ -84,6 +99,6 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-01', { Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](/docs/designers-developers/developers/block-api/block-registration.md#icon-optional). -A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. +A block name must be prefixed with a namespace specific to your plugin. This helps prevent conflicts when more than one plugin registers a block with the same name. In this example, the namespace is `gutenberg-examples`. The `edit` and `save` functions describe the structure of your block in the context of the editor and the saved content respectively. While the difference is not obvious in this simple example, in the following sections we'll explore how these are used to enable customization of the block in the editor preview. From 75e53582a6694c04e19a4e5326aada0448ae619b Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 22 Mar 2019 10:58:10 -0700 Subject: [PATCH 03/17] Update step 02 to match examples repo --- .../applying-styles-with-stylesheets.md | 91 +++++++++---------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index 47d2f70876143e..4ac205695a01a8 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -1,8 +1,8 @@ # Applying Styles From a Stylesheet -In the previous section, the block had applied its own styles by an inline `style` attribute. While this might be adequate for very simple components, you will quickly find that it becomes easier to write your styles by extracting them to a separate stylesheet file. +In the previous step, the block had applied its own styles by an inline `style` attribute. While this might be adequate for very simple components, you will quickly find that it becomes easier to write your styles by extracting them to a separate stylesheet file. -The editor will automatically generate a class name for each block type to simplify styling. It can be accessed from the object argument passed to the edit and save functions: +The editor will automatically generate a class name for each block type to simplify styling. It can be accessed from the object argument passed to the edit and save functions. In step 2, we will create a stylesheet to use that class name. {% codetabs %} {% ES5 %} @@ -10,19 +10,27 @@ The editor will automatically generate a class name for each block type to simpl var el = wp.element.createElement, registerBlockType = wp.blocks.registerBlockType; -registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-02', { - title: 'Hello World (Step 2)', +registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + title: 'Example: Stylesheets', icon: 'universal-access-alt', category: 'layout', edit: function( props ) { - return el( 'p', { className: props.className }, 'Hello editor.' ); + return el( + 'p', + { className: props.className }, + 'Hello World, step 2 (from the editor, in green).' + ); }, save: function() { - return el( 'p', {}, 'Hello saved content.' ); + return el( + 'p', + {}, + 'Hello World, step 2 (from the frontend, in red).' + ); } } ); ``` @@ -30,19 +38,19 @@ registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-02', { ```js const { registerBlockType } = wp.blocks; -registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', { - title: 'Hello World (Step 2)', +registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + title: 'Example: Stylesheets', icon: 'universal-access-alt', category: 'layout', edit( { className } ) { - return

Hello editor.

; + return

Hello World, step 2 (from the editor, in green).

; }, save() { - return

Hello saved content.

; + return

Hello World, step 2 (from the frontend, in red)./p>; } } ); ``` @@ -51,7 +59,7 @@ registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-02', { The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`. ```css -.wp-block-gutenberg-boilerplate-es5-hello-world-step-02 { +.wp-block-gutenberg-examples-example-02-stylesheets { color: green; background: #cfc; border: 2px solid #9c9; @@ -59,56 +67,47 @@ The class name is generated using the block's name prefixed with `wp-block-`, re } ``` -## Enqueueing Editor-only Block Assets +## Enqueueing Editor and Front end Assets + +Like scripts, your block's editor-specific styles should be enqueued by assigning the `editor_style` setting of the registered block type. + +To enqueue a style that shows on both the front of your site and the editor, use `style` setting. + +When registering a block, you can assign one or both of `style` and `editor_style` to respectively assign styles always loaded for a block, or styles only loaded in the editor. -Like scripts, your block's editor-specific styles should be enqueued by assigning the `editor_styles` setting of the registered block type: +Example 2 shows having a distinct style for each context. Your block is likely to share some styles in both contexts, so in this example you can consider `style.css` as the base stylesheet, placing editor-specific styles in `editor.css`. ```php 'gutenberg-boilerplate-es5-step02-editor', - 'editor_style' => 'gutenberg-boilerplate-es5-step02-editor', - ) ); -} -add_action( 'init', 'gutenberg_boilerplate_block' ); -``` - -## Enqueueing Editor and Front end Assets - -While a block's scripts are usually only necessary to load in the editor, you'll want to load styles both on the front of your site and in the editor. You may even want distinct styles in each context. - -When registering a block, you can assign one or both of `style` and `editor_style` to respectively assign styles always loaded for a block or styles only loaded in the editor. - -```php - 'gutenberg-boilerplate-es5-step02', + register_block_type( 'gutenberg-examples/example-02-stylesheets', array( + 'style' => 'gutenberg-examples-02', + 'editor_style' => 'gutenberg-examples-02-editor', + 'editor_script' => 'gutenberg-examples-02', ) ); } -add_action( 'init', 'gutenberg_boilerplate_block' ); +add_action( 'init', 'gutenberg_examples_02_register_block' ); ``` -Since your block is likely to share some styles in both contexts, you can consider `style.css` as the base stylesheet, placing editor-specific styles in `editor.css`. From 13643612990662d9019081bbc97504d3a190cd90 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 22 Mar 2019 11:21:40 -0700 Subject: [PATCH 04/17] Update step 03 to match examples repo --- ...roducing-attributes-and-editable-fields.md | 182 +++++++++--------- 1 file changed, 90 insertions(+), 92 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index c51bd505bdd2e2..c5bc685ced36e6 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -1,6 +1,6 @@ # Introducing Attributes and Editable Fields -Our example block is still not very interesting because it lacks options to customize the appearance of the message. In this section, we will implement a RichText field allowing the user to specify their own message. Before doing so, it's important to understand how the state of a block (its "attributes") is maintained and changed over time. +The example blocks so far are still not very interesting because they lack options to customize the appearance of the message. In this section, we will implement a RichText field allowing the user to specify their own message. Before doing so, it's important to understand how the state of a block (its "attributes") is maintained and changed over time. ## Attributes @@ -8,84 +8,118 @@ Until now, the `edit` and `save` functions have returned a simple representation One challenge of maintaining the representation of a block as a JavaScript object is that we must be able to extract this object again from the saved content of a post. This is achieved with the block type's `attributes` property: -{% codetabs %} -{% ES5 %} ```js -var el = wp.element.createElement, - registerBlockType = wp.blocks.registerBlockType, - RichText = wp.editor.RichText; - -registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-03', { - title: 'Hello World (Step 3)', - - icon: 'universal-access-alt', - - category: 'layout', - attributes: { content: { - type: 'string', - source: 'html', + type: 'array', + source: 'children', selector: 'p', - } + }, }, +``` + + +When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](/docs/designers-developers/developers/block-api/block-attributes.md) to find the desired value from the markup of the block. + +In the code snippet above, when loading the editor, the `content` value will be extracted from the HTML of the paragraph element in the saved post's markup. + +## Components and the `RichText` Component + +Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into "components". This abstraction helps as a pattern to share common behaviors and to hide complexity into self-contained units. There are a number of [components available](/docs/designers-developers/developers/packages/packages-editor.md#components) to use in implementing your blocks. You can see one such component in the code below: the [`RichText` component](/docs/designers-developers/developers/packages/packages-editor.md#richtext). + +The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. - edit: function( props ) { - var content = props.attributes.content; +To use the `RichText` component, add `wp-editor` to the dependency array of registered script handles when calling `wp_register_script`. - function onChangeContent( newContent ) { - props.setAttributes( { content: newContent } ); - } +```php +wp_register_script( + 'gutenberg-examples-03', + plugins_url( 'block.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-editor' // Note the addition of wp-editor to the dependencies + ), + filemtime( plugin_dir_path( __FILE__ ) . 'block.js' ) +); +``` - return el( - RichText, - { - tagName: 'p', - className: props.className, - onChange: onChangeContent, - value: content, +Implementing this behavior as a component enables you as the block implementer to be much more granular about editable fields. Your block may not need `RichText` at all, or it may need many independent `RichText` elements, each operating on a subset of the overall block state. + +Because `RichText` allows for nested nodes, you'll most often use it in conjunction with the `html` attribute source when extracting the value from saved content. You'll also use `RichText.Content` in the `save` function to output RichText values. + +Here is the complete block definition for Example 03. + +{% codetabs %} +{% ES5 %} +```js +( function( blocks, editor, element ) { + var el = element.createElement; + var RichText = editor.RichText; + + blocks.registerBlockType( 'gutenberg-examples/example-03-editable', { + title: 'Example: Editable', + icon: 'universal-access-alt', + category: 'layout', + + attributes: { + content: { + type: 'array', + source: 'children', + selector: 'p', + }, + }, + + edit: function( props ) { + var content = props.attributes.content; + function onChangeContent( newContent ) { + props.setAttributes( { content: newContent } ); } - ); - }, - save: function( props ) { - var content = props.attributes.content; + return el( + RichText, + { + tagName: 'p', + className: props.className, + onChange: onChangeContent, + value: content, + } + ); + }, - return el( RichText.Content, { - tagName: 'p', - className: props.className, - value: content - } ); - }, -} ); + save: function( props ) { + return el( RichText.Content, { + tagName: 'p', value: props.attributes.content, + } ); + }, + } ); +}( + window.wp.blocks, + window.wp.editor, + window.wp.element +) ); ``` {% ESNext %} ```js const { registerBlockType } = wp.blocks; const { RichText } = wp.editor; -registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-03', { - title: 'Hello World (Step 3)', - +registerBlockType( 'gutenberg-examples/example-03-editable-esnext', { + title: 'Example: Editable (esnext)', icon: 'universal-access-alt', - category: 'layout', - attributes: { content: { - type: 'string', - source: 'html', + type: 'array', + source: 'children', selector: 'p', }, }, - - edit( { attributes, className, setAttributes } ) { - const { content } = attributes; - - function onChangeContent( newContent ) { + edit: ( props ) => { + const { attributes: { content }, setAttributes, className } = props; + const onChangeContent = ( newContent ) => { setAttributes( { content: newContent } ); - } - + }; return ( ); }, - - save( { attributes } ) { - const { content } = attributes; - - return ( - - ); + save: ( props ) => { + return ; }, } ); ``` {% end %} - -When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](/docs/designers-developers/developers/block-api/block-attributes.md) to find the desired value from the markup of the block. - -In the code snippet above, when loading the editor, we will extract the `content` value as the HTML of the paragraph element in the saved post's markup. - -## Components and the `RichText` Component - -Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into ["components"](). This abstraction helps as a pattern to share common behaviors and to hide complexity into self-contained units. There are a number of components available to use in implementing your blocks. You can see one such component in the snippet above: the [`RichText` component](). - -The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. - -To use the `RichText` component, add `wp-editor` to the array of registered script handles when calling `wp_register_script`. - -```php -wp_register_script( - 'gutenberg-boilerplate-es5-step03', - plugins_url( 'step-03/block.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-element', - 'wp-editor', // Note the addition of wp-editor to the dependencies - ) -); -``` - -Implementing this behavior as a component enables you as the block implementer to be much more granular about editable fields. Your block may not need `RichText` at all, or it may need many independent `RichText` elements, each operating on a subset of the overall block state. - -Because `RichText` allows for nested nodes, you'll most often use it in conjunction with the `html` attribute source when extracting the value from saved content. You'll also use `RichText.Content` in the `save` function to output RichText values. From f741011d927bf5b39973c58d2128b1b792cb9e9a Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Fri, 22 Mar 2019 11:32:41 -0700 Subject: [PATCH 05/17] Update step 04 to match examples repo --- .../block-controls-toolbars-and-inspector.md | 178 +++++++++--------- 1 file changed, 87 insertions(+), 91 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md index 1bad64b3d95329..371e3f2a671ebd 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md @@ -6,57 +6,52 @@ To simplify block customization and ensure a consistent experience for users, th ![Screenshot of the rich text toolbar applied to a paragraph block inside the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-text.png) -When the user selects a block, a number of control buttons may be shown in a toolbar above the selected block. Some of these block-level controls are included automatically if the editor is able to transform the block to another type, or if the focused element is an RichText component. +When the user selects a block, a number of control buttons may be shown in a toolbar above the selected block. Some of these block-level controls are included automatically if the editor is able to transform the block to another type, or if the focused element is a RichText component. You can also customize the toolbar to include controls specific to your block type. If the return value of your block type's `edit` function includes a `BlockControls` element, those controls will be shown in the selected block's toolbar. {% codetabs %} {% ES5 %} ```js -var el = wp.element.createElement, - Fragment = wp.element.Fragment - registerBlockType = wp.blocks.registerBlockType, - RichText = wp.editor.RichText, - BlockControls = wp.editor.BlockControls, - AlignmentToolbar = wp.editor.AlignmentToolbar; - -registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-04', { - title: 'Hello World (Step 4)', - - icon: 'universal-access-alt', - - category: 'layout', - - attributes: { - content: { - type: 'string', - source: 'html', - selector: 'p', +( function( blocks, editor, i18n, element ) { + var el = element.createElement; + var RichText = editor.RichText; + var AlignmentToolbar = editor.AlignmentToolbar; + var BlockControls = editor.BlockControls; + + blocks.registerBlockType( 'gutenberg-examples/example-04-controls', { + title: 'Example: Controls', + icon: 'universal-access-alt', + category: 'layout', + + attributes: { + content: { + type: 'array', + source: 'children', + selector: 'p', + }, + alignment: { + type: 'string', + default: 'none', + }, }, - alignment: { - type: 'string', - }, - }, - edit: function( props ) { - var content = props.attributes.content, - alignment = props.attributes.alignment; + edit: function( props ) { + var content = props.attributes.content; + var alignment = props.attributes.alignment; - function onChangeContent( newContent ) { - props.setAttributes( { content: newContent } ); - } + function onChangeContent( newContent ) { + props.setAttributes( { content: newContent } ); + } - function onChangeAlignment( newAlignment ) { - props.setAttributes( { alignment: newAlignment } ); - } + function onChangeAlignment( newAlignment ) { + props.setAttributes( { alignment: newAlignment === undefined ? 'none' : newAlignment } ); + } - return ( - el( - Fragment, - null, + return [ el( BlockControls, - null, + { key: 'controls' }, el( AlignmentToolbar, { @@ -68,98 +63,99 @@ registerBlockType( 'gutenberg-boilerplate-es5/hello-world-step-04', { el( RichText, { - key: 'editable', + key: 'richtext', tagName: 'p', - className: props.className, style: { textAlign: alignment }, + className: props.className, onChange: onChangeContent, value: content, } - ) - ) - ); - }, - - save: function( props ) { - var content = props.attributes.content, - alignment = props.attributes.alignment; + ), + ]; + }, - return el( RichText.Content, { - tagName: 'p', - className: props.className, - style: { textAlign: alignment }, - value: content - } ); - }, -} ); + save: function( props ) { + return el( RichText.Content, { + tagName: 'p', + className: 'gutenberg-examples-align-' + props.attributes.alignment, + value: props.attributes.content, + } ); + }, + } ); +}( + window.wp.blocks, + window.wp.editor, + window.wp.element +) ); ``` {% ESNext %} ```js const { registerBlockType } = wp.blocks; -const { Fragment } = wp.element; + const { RichText, - BlockControls, AlignmentToolbar, + BlockControls, } = wp.editor; -registerBlockType( 'gutenberg-boilerplate-esnext/hello-world-step-04', { - title: 'Hello World (Step 4)', - +registerBlockType( 'gutenberg-examples/example-04-controls-esnext', { + title: 'Example: Controls (esnext)', icon: 'universal-access-alt', - category: 'layout', - attributes: { content: { - type: 'string', - source: 'html', + type: 'array', + source: 'children', selector: 'p', }, alignment: { type: 'string', + default: 'none', }, }, + edit: ( props ) => { + const { + attributes: { + content, + alignment, + }, + className, + } = props; + + const onChangeContent = ( newContent ) => { + props.setAttributes( { content: newContent } ); + }; - edit( { attributes, className, setAttributes } ) { - const { content, alignment } = attributes; - - function onChangeContent( newContent ) { - setAttributes( { content: newContent } ); - } - - function onChangeAlignment( newAlignment ) { - setAttributes( { alignment: newAlignment } ); - } + const onChangeAlignment = ( newAlignment ) => { + props.setAttributes( { alignment: newAlignment === undefined ? 'none' : newAlignment } ); + }; return ( - - - - +

+ { + + + + } - +
); }, - - save( { attributes } ) { - const { content, alignment } = attributes; - + save: ( props ) => { return ( ); }, From f1f68f95aa2f36da9841f83c5e67ed85c6bd98a9 Mon Sep 17 00:00:00 2001 From: Chris Van Patten Date: Fri, 22 Mar 2019 11:58:30 -0700 Subject: [PATCH 06/17] Apply suggestions from code review Co-Authored-By: mkaz --- .../introducing-attributes-and-editable-fields.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index c5bc685ced36e6..0db457b14777e9 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -18,14 +18,15 @@ One challenge of maintaining the representation of a block as a JavaScript objec }, ``` - When registering a new block type, the `attributes` property describes the shape of the attributes object you'd like to receive in the `edit` and `save` functions. Each value is a [source function](/docs/designers-developers/developers/block-api/block-attributes.md) to find the desired value from the markup of the block. In the code snippet above, when loading the editor, the `content` value will be extracted from the HTML of the paragraph element in the saved post's markup. ## Components and the `RichText` Component -Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into "components". This abstraction helps as a pattern to share common behaviors and to hide complexity into self-contained units. There are a number of [components available](/docs/designers-developers/developers/packages/packages-editor.md#components) to use in implementing your blocks. You can see one such component in the code below: the [`RichText` component](/docs/designers-developers/developers/packages/packages-editor.md#richtext). +Earlier examples used the `createElement` function to create DOM nodes, but it's also possible to encapsulate this behavior into "components". This abstraction helps you share common behaviors and hide complexity in self-contained units. + +There are a number of [components available](/docs/designers-developers/developers/packages/packages-editor.md#components) to use in implementing your blocks. You can see one such component in the code below: the [`RichText` component](/docs/designers-developers/developers/packages/packages-editor.md#richtext). The `RichText` component can be considered as a super-powered `textarea` element, enabling rich content editing including bold, italics, hyperlinks, etc. From 5a1e1cbb370143e18551de24816f3ed9c707927b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Mon, 25 Mar 2019 10:33:30 -0700 Subject: [PATCH 07/17] Update docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md Co-Authored-By: mkaz --- .../tutorials/block-tutorial/writing-your-first-block-type.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index 50a7d025afbc07..360f3e879ba08b 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -10,7 +10,9 @@ While the block's editor behaviors are implemented in JavaScript, you'll need to ```php Date: Mon, 25 Mar 2019 10:36:23 -0700 Subject: [PATCH 08/17] Add plugin header --- .../block-tutorial/applying-styles-with-stylesheets.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index 4ac205695a01a8..9effbefa136410 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -79,6 +79,9 @@ Example 2 shows having a distinct style for each context. Your block is likely t ```php Date: Mon, 25 Mar 2019 10:40:12 -0700 Subject: [PATCH 09/17] Per review, wrap in IIFE, matches examples --- .../applying-styles-with-stylesheets.md | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index 9effbefa136410..0ef9cf7ba341a9 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -7,32 +7,32 @@ The editor will automatically generate a class name for each block type to simpl {% codetabs %} {% ES5 %} ```js -var el = wp.element.createElement, - registerBlockType = wp.blocks.registerBlockType; - -registerBlockType( 'gutenberg-examples/example-02-stylesheets', { - title: 'Example: Stylesheets', - - icon: 'universal-access-alt', - - category: 'layout', - - edit: function( props ) { - return el( - 'p', - { className: props.className }, - 'Hello World, step 2 (from the editor, in green).' - ); - }, - - save: function() { - return el( - 'p', - {}, - 'Hello World, step 2 (from the frontend, in red).' - ); - } -} ); +( function( blocks, element ) { + var el = element.createElement; + + blocks.registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + title: 'Example: Stylesheets', + icon: 'universal-access-alt', + category: 'layout', + edit: function( props ) { + return el( + 'p', + { className: props.className }, + 'Hello World, step 2 (from the editor, in green).' + ); + }, + save: function() { + return el( + 'p', + {}, + 'Hello World, step 2 (from the frontend, in red).' + ); + }, + } ); +}( + window.wp.blocks, + window.wp.element +) ); ``` {% ESNext %} ```js From 4ae91f68a44068a3a2f7340fbfe4af3a1832e5e4 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak Date: Mon, 25 Mar 2019 10:42:55 -0700 Subject: [PATCH 10/17] Per review, add description for editor.css file --- .../block-tutorial/applying-styles-with-stylesheets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index 0ef9cf7ba341a9..857651667d26cc 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -56,7 +56,7 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { ``` {% end %} -The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`. +The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`. Create the following CSS in a file called `editor.css`: ```css .wp-block-gutenberg-examples-example-02-stylesheets { From 6d74cc3833a5de1821b83e48377e8c95fad6f561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 26 Mar 2019 19:03:47 +0100 Subject: [PATCH 11/17] Fix action callback --- .../tutorials/block-tutorial/writing-your-first-block-type.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index 360f3e879ba08b..c2ea73cbdd8957 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -13,7 +13,7 @@ While the block's editor behaviors are implemented in JavaScript, you'll need to /* Plugin Name: Gutenberg examples 01 */ -function gutenberg_examples_01_register_block() +function gutenberg_examples_01_register_block() { wp_register_script( 'gutenberg-examples-01', plugins_url( 'block.js', __FILE__ ), @@ -25,7 +25,7 @@ function gutenberg_examples_01_register_block() ) ); } -add_action( 'init', 'gutenberg_examples_01_register_block() ' ); +add_action( 'init', 'gutenberg_examples_01_register_block' ); ``` Note the two script dependencies: From 17acb81a76d6ff5f98d9df8ccd128b82c9a65a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 26 Mar 2019 19:14:08 +0100 Subject: [PATCH 12/17] Add style.css code and reorganize a bit --- .../applying-styles-with-stylesheets.md | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index 857651667d26cc..dff770151e8e9c 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -56,7 +56,15 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { ``` {% end %} -The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`. Create the following CSS in a file called `editor.css`: +The class name is generated using the block's name prefixed with `wp-block-`, replacing the `/` namespace separator with a single `-`. + +## Enqueueing Editor and Front end Assets + +Like scripts, you need to enqueue your block's styles. As explained in the section before, you use the `editor_style` handle for styles only relevant in the editor, and the `style` handle for common styles applied both in the editor and the front of your site. + +The stylesheets enqueued by `style` are the base styles and are loaded first. The `editor` stylesheet will be loaded after it. + +Let's move on into code. Create a file called `editor.css`: ```css .wp-block-gutenberg-examples-example-02-stylesheets { @@ -67,15 +75,18 @@ The class name is generated using the block's name prefixed with `wp-block-`, re } ``` -## Enqueueing Editor and Front end Assets - -Like scripts, your block's editor-specific styles should be enqueued by assigning the `editor_style` setting of the registered block type. +And a new `style.css` file containing: -To enqueue a style that shows on both the front of your site and the editor, use `style` setting. - -When registering a block, you can assign one or both of `style` and `editor_style` to respectively assign styles always loaded for a block, or styles only loaded in the editor. +```css +.wp-block-gutenberg-examples-example-02-stylesheets { + color: darkred; + background: #fcc; + border: 2px solid #c99; + padding: 20px; +} +``` -Example 2 shows having a distinct style for each context. Your block is likely to share some styles in both contexts, so in this example you can consider `style.css` as the base stylesheet, placing editor-specific styles in `editor.css`. +Configure your plugin to use these new styles: ```php Date: Tue, 26 Mar 2019 19:22:30 +0100 Subject: [PATCH 13/17] Add note about changing the handle --- .../introducing-attributes-and-editable-fields.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md index 0db457b14777e9..738ed6640ce55f 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md @@ -45,6 +45,8 @@ wp_register_script( ); ``` +Do not forget to also update the `editor_script` handle in `register_block_type` to `gutenberg-examples-03`. + Implementing this behavior as a component enables you as the block implementer to be much more granular about editable fields. Your block may not need `RichText` at all, or it may need many independent `RichText` elements, each operating on a subset of the overall block state. Because `RichText` allows for nested nodes, you'll most often use it in conjunction with the `html` attribute source when extracting the value from saved content. You'll also use `RichText.Content` in the `save` function to output RichText values. From 4002f27dc69227b485fa9257670869c00336fc01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 26 Mar 2019 19:29:05 +0100 Subject: [PATCH 14/17] Fix example --- .../block-tutorial/block-controls-toolbars-and-inspector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md index 371e3f2a671ebd..bfabb7379b40a6 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md @@ -13,7 +13,7 @@ You can also customize the toolbar to include controls specific to your block ty {% codetabs %} {% ES5 %} ```js -( function( blocks, editor, i18n, element ) { +( function( blocks, editor, element ) { var el = element.createElement; var RichText = editor.RichText; var AlignmentToolbar = editor.AlignmentToolbar; From ee9464a411465f0fc6ff4d01dbf400e93d831edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 26 Mar 2019 21:15:05 +0100 Subject: [PATCH 15/17] Fix dynamic block example --- .../block-tutorial/creating-dynamic-blocks.md | 100 +++++++++++------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index ab7543c392c9cb..f0e66466d70a8b 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -1,51 +1,54 @@ # Creating dynamic blocks -It is possible to create dynamic blocks. These are blocks that can change their content even if the post is not saved. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published. +Dynamic blocks are blocks that can change their content even if the post is not saved. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published. -The following code example shows how to create the latest post block dynamic block. +The following code example shows how to create a dynamic block that shows only the last post as a link. {% codetabs %} {% ES5 %} ```js -// myblock.js - -var el = wp.element.createElement, - registerBlockType = wp.blocks.registerBlockType, - withSelect = wp.data.withSelect; +( function( blocks, element, data ) { -registerBlockType( 'my-plugin/latest-post', { - title: 'Latest Post', - icon: 'megaphone', - category: 'widgets', + var el = element.createElement, + registerBlockType = blocks.registerBlockType, + withSelect = data.withSelect; - edit: withSelect( function( select ) { - return { - posts: select( 'core' ).getEntityRecords( 'postType', 'post' ) - }; - } )( function( props ) { + registerBlockType( 'gutenberg-examples/example-05-dynamic', { + title: 'Example: last post', + icon: 'megaphone', + category: 'widgets', - if ( ! props.posts ) { - return "Loading..."; - } - - if ( props.posts.length === 0 ) { - return "No posts"; - } - var className = props.className; - var post = props.posts[ 0 ]; - - return el( - 'a', - { className: className, href: post.link }, - post.title.rendered - ); - } ), -} ); + edit: withSelect( function( select ) { + return { + posts: select( 'core' ).getEntityRecords( 'postType', 'post' ) + }; + } )( function( props ) { + + if ( ! props.posts ) { + return "Loading..."; + } + + if ( props.posts.length === 0 ) { + return "No posts"; + } + var className = props.className; + var post = props.posts[ 0 ]; + + return el( + 'a', + { className: className, href: post.link }, + post.title.rendered + ); + } ), + } ); +}( + window.wp.blocks, + window.wp.element, + window.wp.data, +) ); ``` {% ESNext %} ```js -// myblock.js - const { registerBlockType } = wp.blocks; const { withSelect } = wp.data; @@ -78,13 +81,16 @@ registerBlockType( 'my-plugin/latest-post', { ``` {% end %} -Because it is a dynamic block it doesn't need to override the default `save` implementation on the client. Instead, it needs a server component. The rendering can be added using the `render_callback` property when using the `register_block_type` function. +Because it is a dynamic block it doesn't need to override the default `save` implementation on the client. Instead, it needs a server component. The contents in the front of your site depend on the function called by the `render_callback` property of `register_block_type`. ```php 1, 'post_status' => 'publish', @@ -101,9 +107,21 @@ function my_plugin_render_block_latest_post( $attributes, $content ) { ); } -register_block_type( 'my-plugin/latest-post', array( - 'render_callback' => 'my_plugin_render_block_latest_post', -) ); +function gutenberg_examples_05_dynamic() { + wp_register_script( + 'gutenberg-examples-05', + plugins_url( 'block.js', __FILE__ ), + array( 'wp-blocks', 'wp-element', 'wp-data' ) + ); + + register_block_type( 'gutenberg-examples/example-05-dynamic', array( + 'editor_script' => 'gutenberg-examples-05', + 'render_callback' => 'gutenberg_examples_05_dynamic_render_callback' + ) ); + +} +add_action( 'init', 'gutenberg_examples_05_dynamic' ); + ``` There are a few things to notice: From bd3d503d106192019c904b0bf7bcc7fbc7a1f5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 26 Mar 2019 21:22:14 +0100 Subject: [PATCH 16/17] Fix up ESNext example --- .../tutorials/block-tutorial/creating-dynamic-blocks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index f0e66466d70a8b..084cd5bd41f4c6 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -52,8 +52,8 @@ The following code example shows how to create a dynamic block that shows only t const { registerBlockType } = wp.blocks; const { withSelect } = wp.data; -registerBlockType( 'my-plugin/latest-post', { - title: 'Latest Post', +registerBlockType( 'gutenberg-examples/example-05-dynamic', { + title: 'Example: last post', icon: 'megaphone', category: 'widgets', From 4827fd686e1d25a4ca22ac8dc796316c7f208425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 26 Mar 2019 21:29:15 +0100 Subject: [PATCH 17/17] Fix live rendering section --- .../block-tutorial/creating-dynamic-blocks.md | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index 084cd5bd41f4c6..8c92d48349b7e8 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -139,45 +139,47 @@ Gutenberg 2.8 added the [``](/packages/components/src/server-s {% codetabs %} {% ES5 %} ```js -// myblock.js +( function( blocks, element, components ) { -var el = wp.element.createElement, - registerBlockType = wp.blocks.registerBlockType, - ServerSideRender = wp.components.ServerSideRender; + var el = element.createElement, + registerBlockType = blocks.registerBlockType, + ServerSideRender = components.ServerSideRender; -registerBlockType( 'my-plugin/latest-post', { - title: 'Latest Post', - icon: 'megaphone', - category: 'widgets', + registerBlockType( 'gutenberg-examples/example-05-dynamic', { + title: 'Example: last post', + icon: 'megaphone', + category: 'widgets', - edit: function( props ) { - // ensure the block attributes matches this plugin's name - return ( - el(ServerSideRender, { - block: "my-plugin/latest-post", - attributes: props.attributes - }) - ); - }, -} ); + edit: function( props ) { + + return ( + el(ServerSideRender, { + block: "gutenberg-examples/example-05-dynamic", + attributes: props.attributes + } ) + ); + }, + } ); +}( + window.wp.blocks, + window.wp.element, + window.wp.components, +) ); ``` {% ESNext %} ```js -// myblock.js - const { registerBlockType } = wp.blocks; const { ServerSideRender } = wp.components; -registerBlockType( 'my-plugin/latest-post', { - title: 'Latest Post', +registerBlockType( 'gutenberg-examples/example-05-dynamic', { + title: 'Example: last post', icon: 'megaphone', category: 'widgets', edit: function( props ) { - // ensure the block attributes matches this plugin's name return ( ); @@ -186,4 +188,4 @@ registerBlockType( 'my-plugin/latest-post', { ``` {% end %} -The PHP code is the same as above and is automatically handled through the WP REST API. +Note that this code uses the `wp.components` utility but not `wp.data`. Make sure to update the `wp-data` dependency to `wp-compononents` in the PHP code.