From 1ac5c23c759753f36f5c0f29f177f381cf245eec Mon Sep 17 00:00:00 2001 From: Nick Diego Date: Fri, 15 Dec 2023 15:49:31 -0600 Subject: [PATCH] Docs: Add new "Build your first block" tutorial to the Getting Started section of the BEH (#56931) * Initial draft of the new block tutorial. * Fix link. * Fix grammar and address feedback. * Add reference to Block Development Examples repo. --- docs/getting-started/create-block/README.md | 21 - .../create-block/attributes.md | 87 -- .../create-block/author-experience.md | 147 --- .../create-block/block-anatomy.md | 80 -- .../create-block/block-code.md | 46 - .../getting-started/create-block/finishing.md | 27 - .../submitting-to-block-directory.md | 104 -- .../getting-started/create-block/wp-plugin.md | 146 --- docs/getting-started/tutorial.md | 1003 +++++++++++++++++ docs/manifest.json | 48 +- docs/toc.json | 26 +- 11 files changed, 1007 insertions(+), 728 deletions(-) delete mode 100644 docs/getting-started/create-block/README.md delete mode 100644 docs/getting-started/create-block/attributes.md delete mode 100644 docs/getting-started/create-block/author-experience.md delete mode 100644 docs/getting-started/create-block/block-anatomy.md delete mode 100644 docs/getting-started/create-block/block-code.md delete mode 100644 docs/getting-started/create-block/finishing.md delete mode 100644 docs/getting-started/create-block/submitting-to-block-directory.md delete mode 100644 docs/getting-started/create-block/wp-plugin.md create mode 100644 docs/getting-started/tutorial.md diff --git a/docs/getting-started/create-block/README.md b/docs/getting-started/create-block/README.md deleted file mode 100644 index 22a28560c76a8..0000000000000 --- a/docs/getting-started/create-block/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Create a Block Tutorial - -Let's get you started creating your first block for the WordPress Block Editor. We will create a simple block that allows the user to type a message and style it. - -The tutorial includes setting up your development environment, tools, and getting comfortable with the new development model. If you are already comfortable, try the quick start below, otherwise step through whatever part of the tutorial you need. - -## Prerequisites - -The first thing you need is a development environment and tools. This includes setting up your WordPress environment, Node, NPM, and your code editor. If you need help, see the [setting up your development environment documentation](/docs/getting-started/devenv/README.md). - -## Table of Contents - -The create a block tutorials breaks down to the following sections. - -1. [WordPress Plugin](/docs/getting-started/create-block/wp-plugin.md) -2. [Anatomy of a Gutenberg Block ](/docs/getting-started/create-block/block-anatomy.md) -3. [Block Attributes](/docs/getting-started/create-block/attributes.md) -4. [Code Implementation](/docs/getting-started/create-block/block-code.md) -5. [Authoring Experience](/docs/getting-started/create-block/author-experience.md) -6. [Finishing Touches](/docs/getting-started/create-block/finishing.md) -7. [Share your Block with the World](/docs/getting-started/create-block/submitting-to-block-directory.md) diff --git a/docs/getting-started/create-block/attributes.md b/docs/getting-started/create-block/attributes.md deleted file mode 100644 index 02a55f380dcee..0000000000000 --- a/docs/getting-started/create-block/attributes.md +++ /dev/null @@ -1,87 +0,0 @@ -# Block Attributes - -Attributes are the way a block stores data, they define how a block is parsed to extract data from the saved content. - -For this block tutorial, we want to allow the user to type in a message that we will display stylized in the published post. So, we need to add a **message** attribute that will hold the user message. The following code defines a **message** attribute; the attribute type is a string; the source is the text from the selector which is a `div` tag. - -```json -"attributes": { - "message": { - "type": "string", - "source": "text", - "selector": "div", - "default": "" - } -}, -``` - -Add this to the `src/block.json` file. The `attributes` are at the same level as the _name_ and _title_ fields. - -When the block loads it will look at the saved content for the block, look for the div tag, take the text portion, and store the content in an `attributes.message` variable. - -Note: The text portion is equivalent to `innerText` attribute of a DOM element. For more details and other examples see the [Block Attributes documentation](/docs/reference-guides/block-api/block-attributes.md). - -## Edit and Save - -The **attributes** are passed to both the `edit` and `save` functions. The **setAttributes** function is also passed, but only to the `edit` function. The **setAttributes** function is used to set the values. Additional parameters are also passed in to the `edit` and `save` functions, see [the edit/save documentation](/docs/reference-guides/block-api/block-edit-save.md) for more details. - -The `attributes` is a JavaScript object containing the values of each attribute, or default values if defined. The `setAttributes` is a function to update an attribute. - -```js -export default function Edit( { attributes, setAttributes } ) { - // ... -} -``` - -## TextControl Component - -For our example block, the component we are going to use is the **TextControl** component, it is similar to an HTML text input field. You can see [documentation for TextControl component](/packages/components/src/text-control/README.md). You can browse an [interactive set of components in this Storybook](https://wordpress.github.io/gutenberg/). - -The component is added similar to an HTML tag, setting a label, the `value` is set to the `attributes.message` and the `onChange` function uses the `setAttributes` to update the message attribute value. - -The save function will simply write the `attributes.message` as a div tag since that is how we defined it to be parsed. - -OPTIONAL: For IDE support (code completion and hints), you can install the `@wordpress/components` module which is where the TextControl component is imported from. This install command is optional since the build process automatically detects `@wordpress/*` imports and specifies as dependencies in the assets file. - -```shell -npm install @wordpress/components --save -``` - -Update the edit.js and save.js files to the following, replacing the existing functions. - -**edit.js** file: - -```js -import { __ } from '@wordpress/i18n'; -import { useBlockProps } from '@wordpress/block-editor'; -import { TextControl } from '@wordpress/components'; -import './editor.scss'; - -export default function Edit( { attributes, setAttributes } ) { - return ( -
- setAttributes( { message: val } ) } - /> -
- ); -} -``` - -**save.js** file: - -```jsx -import { useBlockProps } from '@wordpress/block-editor'; - -export default function save( { attributes } ) { - const blockProps = useBlockProps.save(); - return
{ attributes.message }
; -} -``` - -If you have previously run `npm run start`, and the script is still running, you can reload the editor now and add the block to test. -Otherwise rebuild the block using `npm run build`, reload the editor and add the block. Type a message in the editor, save, and view it in the post. - -Next Section: [Code Implementation](/docs/getting-started/create-block/block-code.md) diff --git a/docs/getting-started/create-block/author-experience.md b/docs/getting-started/create-block/author-experience.md deleted file mode 100644 index a52ea808f6965..0000000000000 --- a/docs/getting-started/create-block/author-experience.md +++ /dev/null @@ -1,147 +0,0 @@ -# Authoring Experience - -## Background - -One of the primary tenets of Gutenberg as a WYSIWYG editor is that what you see in the editor should be as close as possible to what you get when published. Keep this in mind when building blocks. - -## Placeholder - -The state when a block has been inserted, but no data has been entered yet, is called a placeholder. There is a `Placeholder` component built that gives us a standard look. You can see example placeholders in use with the image and embed blocks. - -To use the Placeholder, wrap the `` component so it becomes a child element of the `` component. Try it out in your code. After updating, you might have something like: - -```jsx -import { useBlockProps } from '@wordpress/block-editor'; -import { Placeholder, TextControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -export default function Edit( { attributes, className, setAttributes } ) { - return ( -
- - setAttributes( { message: val } ) } - /> - -
- ); -} -``` - -## isSelected Ternary Function - -The placeholder looks ok, for a simple text message it may or may not be what you are looking for. However, the placeholder can be useful if you are replacing the block after what is typed in, similar to the embed blocks. - -For this we can use a ternary function, to display content based on a value being set or not. A ternary function is an inline if-else statement, using the syntax: - -```js -clause ? doIfTrue : doIfFalse; -``` - -This can be used inside a block to control what shows when a parameter is set or not. A simple case that displays a `message` if set, otherwise show the form element: - -```jsx -return ( -
- { attributes.message ? ( -
Message: { attributes.message }
- ) : ( -
-

No Message.

- setAttributes( { message: val } ) } - /> -
- ) } -
-); -``` - -There is a problem with the above, if we only use the `attributes.message` check, as soon as we type in the text field it would disappear since the message would then be set to a value. So we need to pair with an additional `isSelected` parameter. - -The `isSelected` parameter is passed in to the `edit` function and is set to true if the block is selected in the editor (currently editing) otherwise set to false (editing elsewhere). - -Using that parameter, we can use the logic: - -```js -attributes.message && ! isSelected; -``` - -If the message is set and `!isSelected`, meaning we are not editing the block, the focus is elsewhere, then display the message not the text field. - -All of this combined together, here's what the edit function looks like: - -```jsx -import { Placeholder, TextControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useBlockProps } from "@wordpress/block-editor"; - -export default function Edit( { attributes, isSelected, setAttributes } ) { - return ( -
- { attributes.message && ! isSelected ? ( -
{ attributes.message }
- ) : ( - - - setAttributes( { message: val } ) - } - /> - - ) } -
- ); -} -``` - -With that in place, rebuild and reload and when you are not editing the message is displayed as it would be for the view, when you click into the block you see the text field. - -## A Better Solution - -The switching between a Placeholder and input control works well with a visual element like an image or video, but for the text example in this block we can do better. - -The simpler and better solution is to modify the `src/editor.scss` to include the proper stylized text while typing. - -Update `src/editor.scss` to: - -```scss -.wp-block-create-block-gutenpride input[type='text'] { - font-family: Gilbert, sans-serif; - font-size: 64px; - color: inherit; - background: inherit; - border: 0; -} -``` - -The edit function can simply be: - -```jsx -import { TextControl } from '@wordpress/components'; -import { useBlockProps } from '@wordpress/block-editor'; - -import './editor.scss'; - -export default function Edit( { attributes, setAttributes } ) { - const blockProps = useBlockProps(); - return ( - setAttributes( { message: val } ) } - /> - ); -} -``` - -Next Section: [Finishing Touches](/docs/getting-started/create-block/finishing.md) diff --git a/docs/getting-started/create-block/block-anatomy.md b/docs/getting-started/create-block/block-anatomy.md deleted file mode 100644 index edd8b5200ae8f..0000000000000 --- a/docs/getting-started/create-block/block-anatomy.md +++ /dev/null @@ -1,80 +0,0 @@ -# Anatomy of a Block - -At its simplest, a block in the WordPress block editor is a JSON object with a specific set of properties. - -
-Note: Block development uses ESNext syntax, this refers to the latest JavaScript standard. If this is unfamiliar, review the ESNext syntax documentation to familiarize yourself with the newer syntax. -
- -The javascript part is done in the `src/index.js` file. - -```js -import { registerBlockType } from '@wordpress/blocks'; - -import './style.scss'; - -import Edit from './edit'; -import save from './save'; -import metadata from './block.json'; - -registerBlockType( metadata.name, { - /** - * @see ./edit.js - */ - edit: Edit, - /** - * @see ./save.js - */ - save, -} ); -``` - -The first parameter in the **registerBlockType** function is the block name, this should match exactly to the `name` property in the `block.json` file. By importing the metadata from `block.json` and referencing the `name` property in the first parameter we ensure that they will match, and continue to match even if the name is subsequently changed in `block.json`. - -The second parameter to the function is the block object. See the [block registration documentation](/docs/reference-guides/block-api/block-registration.md) for full details. - -Two common object properties are **edit** and **save**, these are the key parts of a block. Both properties are functions that are included via the import above. - -The results of the edit function is what the editor will render to the editor page when the block is inserted. - -The results of the save function is what the editor will insert into the **post_content** field when the post is saved. The post_content field is the field in the **wp_posts** table in the WordPress database that is used to store the content of the post. - -Most of the properties are set in the `src/block.json` file. - -```json -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "create-block/gutenpride", - "version": "0.1.0", - "title": "Gutenpride", - "category": "text", - "icon": "flag", - "description": "A Gutenberg block to show your pride! This block enables you to type text and style it with the color font Gilbert from Type with Pride.", - "supports": { - "html": false - }, - "textdomain": "gutenpride", - "editorScript": "file:./index.js", - "editorStyle": "file:./index.css", - "style": "file:./style-index.css" -} -``` - -The **title** is the title of the block shown in the Inserter and in other areas of the editor. - -The **icon** is the icon shown in the Inserter. The icon property expects any Dashicon name as a string, see [list of available icons](https://developer.wordpress.org/resource/dashicons/). You can also provide an SVG object, but for now it's easiest to just pick a Dashicon name. - -The **category** specified is a string and must be one of: "text, media, design, widgets, theme, or embed". You can create your own custom category name, [see documentation for details](/docs/reference-guides/filters/block-filters.md#managing-block-categories). - -## Internationalization - -If you look at the generated `src/save.js` file, the block title and description are wrapped in a function that looks like this: - -```js -__( 'Gutenpride – hello from the saved content!', 'gutenpride' ); -``` - -This is an internationalization wrapper that allows for the string "Gutenpride" to be translated. The second parameter, "gutenpride" is called the text domain and gives context for where the string is from. The JavaScript internationalization, often abbreviated i18n, matches the core WordPress internationalization process. See the [Internationalization in Plugin Developer Handbook](https://developer.wordpress.org/plugins/internationalization/) for more details. - -Next Section: [Block Attributes](/docs/getting-started/create-block/attributes.md) diff --git a/docs/getting-started/create-block/block-code.md b/docs/getting-started/create-block/block-code.md deleted file mode 100644 index 11f478772c6df..0000000000000 --- a/docs/getting-started/create-block/block-code.md +++ /dev/null @@ -1,46 +0,0 @@ -# Code Implementation - -The basic block is in place, the next step is to add styles to the block. Feel free to style and adjust for your own preference, the main lesson is showing how to create and load external resources. For this example we're going to load the colorized gilbert font from [Type with Pride](https://www.typewithpride.com/). - -Note: The color may not work with all browsers until they support the proper color font properly, but the font itself still loads and styles. See [colorfonts.wtf](https://www.colorfonts.wtf/) for browser support and details on color fonts. - -## Load Font File - -Download and extract the font from the Type with Pride site, and copy it in the `assets` directory of your plugin naming it `gilbert-color.otf`. To load the font file, we need to add CSS using standard WordPress enqueue, [see Including CSS & JavaScript documentation](https://developer.wordpress.org/themes/basics/including-css-javascript/). - -In the `gutenpride.php` file, the enqueue process is already setup from the generated script, so `build/index.css` and `build/style-index.css` files are loaded using: - -```php -function create_block_gutenpride_block_init() { - register_block_type( __DIR__ . '/build' ); -} -add_action( 'init', 'create_block_gutenpride_block_init' ); -``` - -This function checks the `build/block.json` file for JS and CSS files, and will pass them on to [enqueue](https://developer.wordpress.org/themes/basics/including-css-javascript/) these files, so they are loaded on the appropriate pages. - -The `build/index.css` is compiled from `src/editor.scss` and loads only within the editor, and after the `style-index.css`. -The `build/style-index.css` is compiled from `src/style.scss` and loads in both the editor and front-end — published post view. - -## Add CSS Style for Block - -We only need to add the style to `build/style-index.css` since it will show while editing and viewing the post. Edit the `src/style.scss` to add the following. - -Note: the block classname is prefixed with `wp-block`. The `create-block/gutenpride` is converted to the classname `.wp-block-create-block-gutenpride`. - -```scss -@font-face { - font-family: Gilbert; - src: url( ../assets/gilbert-color.otf ); - font-weight: 700; -} - -.wp-block-create-block-gutenpride { - font-family: Gilbert, sans-serif; - font-size: 64px; -} -``` - -After updating, rebuild the block using `npm run build` then reload the post and refresh the browser. If you are using a browser that supports color fonts (Firefox) then you will see it styled. - -Next Section: [Authoring Experience](/docs/getting-started/create-block/author-experience.md) diff --git a/docs/getting-started/create-block/finishing.md b/docs/getting-started/create-block/finishing.md deleted file mode 100644 index cd619a69207ca..0000000000000 --- a/docs/getting-started/create-block/finishing.md +++ /dev/null @@ -1,27 +0,0 @@ -# Finishing Touches - -This tutorial covers general concepts and structure for creating basic blocks. - -## Additional Components - -The block editor provides a [components package](/packages/components/README.md) which contains numerous prebuilt components you can use to build your block. - -You can visually browse the components and what their implementation looks like using the Storybook tool published at [https://wordpress.github.io/gutenberg](https://wordpress.github.io/gutenberg). - -## Additional Tutorials - -The **RichText component** allows for creating a richer input besides plain text, allowing for bold, italic, links, and other inline formatting. See the [RichText Reference](/docs/reference-guides/richtext.md) for documentation using this component. - -The InspectorPanel (the settings on the right for a block) and Block Controls (toolbar controls) have a standard way to be implemented. See the [Block controls tutorial](/docs/how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar.md) for additional information. - -The [Sidebar tutorial](/docs/how-to-guides/plugin-sidebar-0.md) is a good resource on how to create a sidebar for your plugin. - -Nested blocks, a block that contains additional blocks, is a common pattern used by various blocks such as Columns, Cover, and Social Links. The **InnerBlocks component** enables this functionality, see the [Using InnerBlocks documentation](/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md). - -## How did they do that - -One of the best sources for information and reference is the Block Editor itself, all the core blocks are built the same way. A good way to learn how things are done is to find a core block code that does something close to what you are interested in and then using the same approach for your own block. - -All core blocks source are in the [block library package on GitHub](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src). - -Next Section: [Share your Block with the World](/docs/getting-started/create-block/submitting-to-block-directory.md) diff --git a/docs/getting-started/create-block/submitting-to-block-directory.md b/docs/getting-started/create-block/submitting-to-block-directory.md deleted file mode 100644 index f898a9d52caaa..0000000000000 --- a/docs/getting-started/create-block/submitting-to-block-directory.md +++ /dev/null @@ -1,104 +0,0 @@ -# Share your Block with the World - -So you've created an awesome block? Care to share? - -**Contents**: - -1. Help users understand your block -2. Analyze your plugin -3. Zip & Submit - -## Step 1: Help users understand your block - -It is important to the Block Directory and our end users to provide easy to understand information on how your block was created. - -**Guidelines**: - -- Name your block based on what it does -- Clearly describe your block -- Add Keywords for all contexts -- Choose the right category - -### Name your block based on what it does - -Users typically search the Block Directory within the Block Editor and do so in the context of a task. For example, when building their post, a user may search the Block Directory for an “image gallery”. Naming your block accordingly will help the Block Directory surface it when it's needed. - -**Not So Good**: WebTeam5 Image Works -**Good**: Responsive Image Slider by WebTeam5 - -**Question: What happens when there are multiple blocks with similar names?** -Try your best to make your block's name functional and unique to make it stand out. Look for applicable synonyms or include a prefix if necessary. - -### Clearly describe your block - -The description really helps to communicate what your block does.The quicker a user understands how your block will help them, the more likely it is a user will use your block. Users will be reading your block's description within the Block Editor where space can be limited. Try to keep it short and concise. - -**Not So Good**: The best way to show images on your website using jQuery and CSS. -**Good**: A responsive image gallery block. - -**Tip**: It’s not about marketing your block, in fact we want to avoid marketing in blocks. You can read more about it in the [plugin guidelines]. Stick to being as clear as you can. The Block Directory will provide metrics to let users know how awesome your block is! - -### Add Keywords for broader context - -Keywords add extra context to your block and make it more likely to be found in the inserter. - -Examples for an Image Slider block: - -- slider -- carousel -- gallery - -[Read more about keywords.](/docs/reference-guides/block-api/block-metadata.md#keywords) - -### Choose the right category - -The Block Editor allows you to indicate the category your block belongs in, making it easier for users to locate your block in the menu. - -**Possible Values**: - -- text -- media -- design -- widgets -- theme -- embed - -[Read more about categories.](/docs/reference-guides/block-api/block-metadata.md#category) - -Wondering where to input all this information? Read the next section :) - -## Step 2: Analyze your plugin - -Each block in your plugin should have a corresponding `block.json` file with the [block metadata](/docs/reference-guides/block-api/block-metadata.md). This file provides the Block Directory important information about your block. Along with being the place to store contextual information about your block like the: `name`, `description`, `keywords` and `category`, the `block.json` file stores the location of your block’s files. - -Block plugins submitted to the Block Directory can contain multiple blocks only if they are children of a single parent/ancestor. There should only be one main block. For example, a list block can contain list-item blocks. Children blocks must set the `parent` property in their `block.json` file. - -Double check that the following is true for your block: - -- `editorScript` is pointing to the JavaScript bundle that includes all the code used in the **editor**. -- `editorStyle` is pointing to the CSS bundle that includes all the css used in the **editor**. -- `script` is pointing to the JavaScript bundle that includes all the code used on the **website**. -- `style` is pointing to the CSS bundle that includes all the code used on the **website**. - -We encourage the separation of code by using both editorScript/editorStyle and script/style files listed in your block.json to keep the backend and frontend interfaces running smoothly. Even though only one file is required. - -Here is an example of a basic block.json file. - -```json -{ - "name": "plugin-slug/image-slider", - "title": "Responsive Image Slider", - "description": "A responsive and easy to use image gallery block.", - "keywords": [ "slider", "carousel", "gallery" ], - "category": "media", - "editorScript": "file:./dist/editor.js" -} -``` - -The `block.json` file also contains other important properties. Take a look at an [example block.json](/docs/reference-guides/block-api/block-metadata.md) for additional properties to be included in the block.json file. - -## Step 3: Zip & Submit - -The community is thankful for your contribution. It is time to submit your plugin. - -Go through [the block guidelines](https://github.com/WordPress/wporg-plugin-guidelines/blob/block-guidelines/blocks.md). Create a zip file of your block and go to the [block plugin validator](https://wordpress.org/plugins/developers/block-plugin-validator/) and upload your plugin. diff --git a/docs/getting-started/create-block/wp-plugin.md b/docs/getting-started/create-block/wp-plugin.md deleted file mode 100644 index 8d6e618dad40c..0000000000000 --- a/docs/getting-started/create-block/wp-plugin.md +++ /dev/null @@ -1,146 +0,0 @@ -# WordPress Plugin - -A block is added to the block editor using a WordPress plugin. You can create your own plugin, and after installing and activating the plugin use the block. Let's first look at what makes up a WordPress plugin. - -## Plugin Details - -A WordPress plugin is a set of files within the site's `wp-content/plugins` directory. For our tutorial, we will use the `@wordpress/create-block` package to generate the necessary plugin files. - -### Switch to Working Directory - -(1A) If you do not plan to use `wp-env`, change to your local WordPress plugin directory. For example in Local it is: `~\Local Sites\mywp\app\public\wp-content\plugins` - --or- - -(1B) If using `wp-env start`, you can work from any directory for your project; `wp-env` will map it as a plugin directory for your site. - -### Generate Plugin Files - -(2) Once in the right directory for your environment, the next step is to run the following command to generate plugin files: - -```sh -npx @wordpress/create-block gutenpride -cd gutenpride -``` - -A new directory `gutenpride` is created with all the necessary plugin files. This tutorial will walk through and explain the plugin files, please explore and become familiar with them also. - -The main plugin file created is the PHP file `gutenpride.php`, at the top of this file is the Plugin Header comment block that defines the plugin. - -```php -/** - * Plugin Name: Gutenpride - * Description: Example static block scaffolded with Create Block tool. - * Requires at least: 5.8 - * Requires PHP: 7.0 - * Version: 0.1.0 - * Author: The WordPress Contributors - * License: GPL-2.0-or-later - * License URI: https://www.gnu.org/licenses/gpl-2.0.html - * Text Domain: gutenpride - * - * @package create-block - */ -``` - -### Start WordPress - -Let's confirm the plugin is loaded and working. - -(3A) If you are using Local, or other environment confirm your WordPress site is started. - --or- - -(3B) If you are using `wp-env`, see [Development Environment setup](/docs/getting-started/devenv/README.md), then you should now run from inside the `gutenpride` directory: - -```sh -wp-env start -``` - -This will start your local WordPress site and use the current directory as your plugin directory. In your browser, go to http://localhost:8888/wp-admin/ and login, the default username is "admin" and password is "password", no quotes. - -### Confirm Plugin Installed - -The generated plugin should now be listed on the Plugins admin page in your WordPress install. Switch WordPress to the plugins page and activate. - -For more on creating a WordPress plugin see [Plugin Basics](https://developer.wordpress.org/plugins/plugin-basics/), and [Plugin Header requirements](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/) for explanation and additional fields you can include in your plugin header. - -## package.json - -The `package.json` file defines the JavaScript properties for your project. This is a standard file used by NPM for defining properties and scripts it can run, the file and process is not specific to WordPress. - -A `package.json` file was created with the create script, this defines the dependencies and scripts needed. You can install dependencies. The only initial dependency is the `@wordpress/scripts` package that bundles the tools and configurations needed to build blocks. - -In `package.json`, there is a `scripts` property that defines what command to run when using `npm run (cmd)`. In our generated `package.json` file, the two main scripts point to the commands in the `wp-scripts` package: - -```json - "scripts": { - "build": "wp-scripts build", - "start": "wp-scripts start" - }, -``` - -These scripts are run by using: `npm run build` or `npm run start`. - -Use `npm run build` for running once to create a "production" build. This compresses the code down so it downloads faster, but makes it harder to read using browser tools—good for final deployment but not while developing. - -Use `npm run start` for creating a "development" build, this does not compress the code so it is easier to read using browser tools, plus [source maps](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map) that make debugging easier. Additionally, development build will start a watch process that waits and watches for changes to the file and will rebuild each time it is saved; so you don't have to run the command for each change. - -By default, the build scripts looks for `src/index.js` for the JavaScript file to build and will save the built file to `build/index.js`. In the upcoming sections, we will look closer at that script, but first let's make sure it is loaded in WordPress. - -## Plugin to Load Script - -To load the built script, so it is run within the editor, you need to tell WordPress about the script. This is done in the init action in the `gutenpride.php` file. - -```php -function create_block_gutenpride_block_init() { - register_block_type( __DIR__ . '/build' ); -} -add_action( 'init', 'create_block_gutenpride_block_init' ); -``` - -The `register_block_type` function registers the block we are going to create and specifies the editor script handle registered from the metadata provided in `build/block.json` file with the `editorScript` field. So now when the editor loads it will load this script. The source metadata file `src/block.json` is copied during the build process: - -```json -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "create-block/gutenpride", - "version": "0.1.0", - "title": "Gutenpride", - "category": "widgets", - "icon": "smiley", - "description": "Example static block scaffolded with Create Block tool.", - "supports": { - "html": false - }, - "textdomain": "gutenpride", - "editorScript": "file:./index.js", - "editorStyle": "file:./index.css", - "style": "file:./style-index.css" -} -``` - -For the `editorScript` provided in the block metadata, the build process creates a secondary asset file that contains the list of dependencies and a file version based on the timestamp, this is the `build/index.asset.php` file. - -The `wp_register_script` function used internally registers a name, called the handle, and relates that name to the script file. The dependencies are used to specify if the script requires including other libraries. The version is specified so the browser will reload if the file is changed. - -The `wp_set_script_translations` function tells WordPress to load translations for this script, if they exist. See more about [translations & internationalization.](/docs/how-to-guides/internationalization.md) - -With the above in place, create a new post to load the editor and check your plugin is in the inserter. You can use `/` to search, or click the box with the [+] and search for "Gutenpride" to find the block. - -## Troubleshooting - -It is a good skill to learn and get comfortable using the web console. This is where JavaScript errors are shown and a nice way to test out snippets of JavaScript. See [Firefox Developer Tools documentation](https://developer.mozilla.org/en-US/docs/Tools). - -To open the developer tools in Firefox, use the menu selecting Web Developer : Toggle Tools, on Chrome, select More Tools -> Developers Tools. For both browsers, the keyboard shortcut on Windows is Ctrl+Shift+I, or on Mac Cmd+Shift+I. On Windows & Linux, the F12 key also works. You can then click Console to view logs. - -Try running `npm run start` that will start the watch process for automatic rebuilds. If you then make an update to `src/index.js` file, you will see the build run, and if you reload the WordPress editor you'll see the change. - -For more info, see the build section of the [Getting Started with JavaScript tutorial](/docs/how-to-guides/javascript/js-build-setup.md) in the Block Editor Handbook. - -## Summary - -Hopefully, at this point, you have your plugin created and activated. We have the `package.json` with the `@wordpress/scripts` dependency, that defines the build and start scripts. The basic block is in place and can be added to the editor. - -Next Section: [Anatomy of a Block](/docs/getting-started/create-block/block-anatomy.md) diff --git a/docs/getting-started/tutorial.md b/docs/getting-started/tutorial.md new file mode 100644 index 0000000000000..e70b4aba9234e --- /dev/null +++ b/docs/getting-started/tutorial.md @@ -0,0 +1,1003 @@ +# Tutorial: Build your first block + +In this tutorial, you will build a "Copyright Date Block"—a basic yet practical block that displays the copyright symbol (©), the current year, and an optional starting year. This type of content is commonly used in website footers. + +The tutorial will guide you through the complete process, from scaffolding the block plugin using the [`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) package to modifying each file. While previous WordPress development experience is beneficial, it's not a prerequisite for this tutorial. + +By the end of this guide, you will have a clear understanding of block development fundamentals and the necessary skills to create your own WordPress blocks. + +## What you're going to build + +Here's a quick look at what you're going to build. + +![What you're going to build](https://developer.wordpress.org/files/2023/12/block-tutorial-1.png) + +You can also interact with the finished project in [WordPress Playground](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/WordPress/block-development-examples/trunk/plugins/copyright-date-block-09aac3/_playground/blueprint.json) or use the [Quick Start Guide](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/) to install the complete block plugin in your local WordPress environment. + +## Prerequisites + +To complete this tutorial, you will need: + +1. Code editor +2. Node.js development tools +3. Local WordPress environment + +If you don't have one or more of these items, the [Block Development Environment](https://developer.wordpress.org/block-editor/getting-started/devenv/) documentation will help you get started. Come back here once you are all set up. + +
+ This tutorial uses wp-env to create a local WordPress development environment. However, feel free to use alternate local development tools if you already have one that you prefer. +
+ +## Scaffolding the block + +The first step in creating the Copyright Date Block is to scaffold the initial block structure using the [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package. + +
+ Review the Get started with create-block documentation for an introduction to using this package. +
+ +You can use `create-block` from just about any directory on your computer and then use `wp-env` to create a local WordPress development environment with your new block plugin installed and activated. + +Therefore, create a new directory (folder) on your computer called "Block Tutorial". Open your terminal and `cd` to this directory. Then run the following command. + +
+ If you are not using wp-env, instead, navigate to the plugins/ folder in your local WordPress installation using the terminal and run the following command. +
+ +```bash +npx @wordpress/create-block@latest copyright-date-block --variant=dynamic +cd copyright-date-block +``` + +After executing this command, you'll find a new directory named `copyright-date-block` in the plugins folder. This directory contains all the initial files needed to start customizing your block. + +This command also sets up the basic structure of your block, with `copyright-date-block` as its slug. This slug uniquely identifies your block within WordPress. + +
+ You might have noticed that the command uses the --variant=dynamic flag. This tells create-block you want to scaffold a dynamically rendered block. Later in this tutorial, you will learn about dynamic and static rendering and add static rendering to this block. +
+ +Navigate to the Plugins page in the WordPress admin and confirm that the plugin is active. Then, create a new page or post and ensure you can insert the Copyright Date Block. It should look like this once inserted. + +![The scaffolded block in the Editor](https://developer.wordpress.org/files/2023/12/block-tutorial-2.png) + +## Reviewing the files +Before we begin modifying the scaffolded block, it's important to review the plugin's file structure. Open the plugin folder in your code editor. + +![The files that make up the block plugin](https://developer.wordpress.org/files/2023/12/block-tutorial-3.png) + +Next, look at the [File structure of a block](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/) documentation for a thorough overview of what each file does. Don't worry if this is overwhelming right now. You will learn how to use each file throughout this tutorial. + +
+ Since you scaffolded a dynamic block, you will not see a save.js file. Later in the tutorial, you will add this file to the plugin to enable static rendering, so stay tuned. +
+ +## Initial setup + +Let's start by creating the simplest Copyright Date Block possible, which will be a dynamically rendered block that simply displays the copyright symbol (©) and the current year. We'll also add a few controls allowing the user to modify font size and text color. + +Before proceeding to the following steps, run `npm run start` in the terminal from within the plugin directory. This command will watch each file in the `/src` folder for changes. The block's build files will be updated each time you save a file. + +Check out the [Working with JavaScript for the Block Editor](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor/) documentation to learn more. + +### Updating block.json + +Open the `block.json` file in the `/src` folder. + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "create-block/copyright-date-block", + "version": "0.1.0", + "title": "Copyright Date Block", + "category": "widgets", + "icon": "smiley", + "description": "Example block scaffolded with Create Block tool.", + "example": {}, + "supports": { + "html": false + }, + "textdomain": "copyright-date-block", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css", + "render": "file:./render.php", + "viewScript": "file:./view.js" +} +``` + +
+ Review the block.json documentation for an introduction to this file. +
+ +Since this scaffolding process created this file, it requires some updating to suit the needs of the Copyright Date Block. + +#### Modifying the block identity + +Begin by removing the icon and adding a more appropriate description. You will add a custom icon later. + +1. Remove the line for `icon` +2. Update the description to "Display your site's copyright date." +3. Save the file + +After you refresh the Editor, you should now see that the block no longer has the smiley face icon, and its description has been updated. + +![The block in the Editor with updated information](https://developer.wordpress.org/files/2023/12/block-tutorial-4.png) + +#### Adding block supports + +Next, let's add a few [block supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/) so that the user can control the font size and text color of the block. + +
+ You should always try to use native block supports before building custom functionality. This approach provides users with a consistent editing experience across blocks, and your block benefits from Core functionality with only a few lines of code. +
+ +Update the [`supports`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#enable-ui-settings-panels-for-the-block-with-supports) section of the `block.json` file to look like this. + +```json +"supports": { + "color": { + "background": false, + "text": true + }, + "html": false, + "typography": { + "fontSize": true + } +}, +``` + +Note that when you enable text color support with `"text": true`, the background color is also enabled by default. You are welcome to keep it enabled, but it's not required for this tutorial, so you can manually set `"background": false`. + +Save the file and select the block in the Editor. You will now see both Color and Typography panels in the Settings Sidebar. Try modifying the settings and see what happens. + +![The block in the Editor with block supports](https://developer.wordpress.org/files/2023/12/block-tutorial-5.png) + +#### Removing unnecessary code + +For simplicity, the styling for the Copyright Date Block will be controlled entirely by the color and typography block supports. This block also does not have any front-end Javascript. Therefore, you don't need to specify stylesheets or a `viewScript` in the `block.json` file. + +1. Remove the line for `editorStyle` +2. Remove the line for `style` +3. Remove the line for `viewScript` +4. Save the file + +Refresh the Editor, and you will see that the block styling now matches your current theme. + +![The block in the Editor without default styling](https://developer.wordpress.org/files/2023/12/block-tutorial-6.png) + +#### Putting it all together + +Your final `block.json` file should look like this: + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "create-block/copyright-date-block", + "version": "0.1.0", + "title": "Copyright Date Block", + "category": "widgets", + "description": "Display your site's copyright date.", + "example": {}, + "supports": { + "color": { + "background": false, + "text": true + }, + "html": false, + "typography": { + "fontSize": true + } + }, + "textdomain": "copyright-date-block", + "editorScript": "file:./index.js", + "render": "file:./render.php" +} +``` + +### Updating index.js + +Before you start building the functionality of the block itself, let's do a bit more cleanup and add a custom icon to the block. + +Open the [`index.js`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#index-js) file. This is the main JavaScript file of the block and is used to register it on the client. You can learn more about client-side and server-side registration in the [Registration of a block](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/) documentation. + +Start by looking at the [`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/) function. This function accepts the name of the block, which we are getting from the imported `block.js` file, and the block configuration object. + +```js +import Edit from './edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + edit: Edit, +} ); +``` + +By default, the object just includes the `edit` property, but you can add many more, including `icon`. While most of these properties are already defined in `block.json`, you need to specify the icon here to use a custom SVG. + +#### Adding a custom icon + +Using the calendar icon from the [Gutenberg Storybook](https://wordpress.github.io/gutenberg/?path=/story/icons-icon--library), add the SVG to the function like so: + +```js +const calendarIcon = ( + +); + +registerBlockType( metadata.name, { + icon: calendarIcon, + edit: Edit +} ); +``` + +
+ All block icons should be 24 pixels square. Note the viewBox parameter above. +
+ +Save the `index.js` file and refresh the Editor. You will now see the calendar icon instead of the default. + +![The block in the Editor a custom icon](https://developer.wordpress.org/files/2023/12/block-tutorial-7.png) + +At this point, the block's icon and description are correct, and block supports allow you to change the font size and text color. Now, it's time to move on to the actual functionality of the block. + +### Updating edit.js + +The [`edit.js`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#edit-js) file controls how the block functions and appears in the Editor. Right now, the user sees the message " Copyright Date Block – hello from the editor!". Let's change that. + +Open the file and see that the `Edit()` function returns a paragraph tag with the default message. + +```js +export default function Edit() { + return ( +

+ { __( + 'Copyright Date Block – hello from the editor!', + 'copyright-date-block-demo' + ) } +

+ ); +} +``` + +It looks a bit more complicated than it is. + +- [`useBlockProps()`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#block-wrapper-props) outputs all the necessary CSS classes and styles in the [block's wrapper](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-edit-components-markup) needed by the Editor, which includes the style provided by the block supports you added earlier +- [`__()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/) is used for the internationalization of text strings + +
+ Review the block wrapper documentation for an introductory guide on how to ensure the block's markup wrapper has the proper attributes. +
+ +As a reminder, the main purpose of this block is to display the copyright symbol (©) and the current year. So, you first need to get the current year in string form, which can be done with the following code. + +```js +const currentYear = new Date().getFullYear().toString(); +``` + +Next, update the function to display the correct information. + +```js +export default function Edit() { + const currentYear = new Date().getFullYear().toString(); + + return ( +

© { currentYear }

+ ); +} +``` + +Save the `edit.js` file and refresh the Editor. You will now see the copyright symbol (©) followed by the current year. + +![The block in the Editor displays the correct content](https://developer.wordpress.org/files/2023/12/block-tutorial-8.png) + +### Updating render.php + +While the block is working properly in the Editor, the default block message is still being displayed on the front end. To fix this, open the [`render.php`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#render-php) file, and you will see the following. + +```php + +

> + +

+ +``` + +Similar to the `useBlockProps()` function in the Editor, [`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) outputs all the necessary CSS classes and styles in the [block's wrapper](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-server-side-render-markup). Only the content needs to be updated. + +You can use `date( "Y" )` to get the current year in PHP, and your `render.php` should look like this. + +```php + +

+``` + +Save the file and confirm that the block appears correctly in the Editor and on the front end. + +### Cleaning up + +When you use the `create-block` package to scaffold a block, it might include files that you don't need. In the case of this tutorial, the block doesn't use stylesheets or font end JavaScipt. Clean up the plugin's `src/` folder with the following actions. + +1. In the `edit.js` file, remove the lines that import `editor.scss` +2. In the `index.js` file, remove the lines that import `style.scss` +3. Delete the editor.scss, style.scss, and view.js files + +Finally, make sure that there are no unsaved changes and then terminate the `npm run start` command. Run `npm run build` to optimize your code and make it production-ready. + +You have built a fully functional WordPress block, but let's not stop here. In the next sections, we'll add functionality and enable static rendering. + +## Adding block attributes + +The Copyright Date Block you have built shows the current year, but what if you wanted to display a starting year as well? + +![What you're going to build](https://developer.wordpress.org/files/2023/12/block-tutorial-1.png) + +This functionality would require users to enter their starting year somewhere on the block. They should also have the ability to toggle it on or off. + +You could implement this in different ways, but all would require [block attributes](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/). Attributes allow you to store custom data for the block that can then be used to modify the block's markup. + +To enable this starting year functionality, you will need one attribute to store the starting year and another that will be used to tell WordPress whether the starting year should be displayed or not. + +### Updating block.json + +Block attributes are generally specified in the [`block.json`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#data-storage-in-the-block-with-attributes) file. So open up the file and add the following section after the `example` in line 9. + +```json +"attributes": { + "showStartingYear": { + "type": "boolean" + }, + "startingYear": { + "type": "string" + } +}, +``` + +You must indicate the `type` when defining attributes. In this case, the `showStartingYear` should be true or false, so it's set to `boolean`. The `startingYear` is just a string. + +Save the file, and you can now move on to the Editor. + +### Updating edit.js + +Open the `edit.js` file. You will need to accomplish two tasks. + +Add a user interface that allows the user to enter a starting year, toggle the functionality on or off, and store these settings as attributes +Update the block to display the correct content depending on the defined attributes + +#### Adding the user interface + +Earlier in this tutorial, you added block supports that automatically created Color and Typography panels in the Settings Sidebar of the block. You can create your own custom panels using the `InspectorControls` component. + +##### Inspector controls + +The `InspectorControls` belongs to the [`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/) package, so you can import it into the `edit.js` file by adding the component name on line 14. The result should look like this. + +```js +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +``` + +Next, update the Edit function to return the current block content and an `InspectorControls` component that includes the text "Testing." You can wrap everything in a [Fragment](https://react.dev/reference/react/Fragment) (`<>`) to ensure proper JSX syntax. The result should look like this. + +```js +export default function Edit() { +const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + Testing + +

© { currentYear }

+ + ); +} +``` +Save the file and refresh the Editor. When selecting the block, you should see the "Testing" message in the Settings Sidebar. + +![The Setting Sidebar now displays the message](https://developer.wordpress.org/files/2023/12/block-tutorial-9.png) + +##### Components and panels + +Now, let's use a few more Core components to add a custom panel and the user interface for the starting year functionality. You will want to import [`PanelBody`](https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody), [`TextControl`](https://developer.wordpress.org/block-editor/reference-guides/components/text-control/), and [`ToggleControl`](https://developer.wordpress.org/block-editor/reference-guides/components/toggle-control/) from the [`@wordpress/components`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) package. + +Add the following line below the other imports in the `edit.js` file. + +```js +import { PanelBody, TextControl, ToggleControl } from '@wordpress/components'; +``` + +Then wrap the "Testing" message in the `PanelBody` component and set the `title` parameter to "Settings". Refer to the [component documentation](https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody) for additional parameter options. + +```js +export default function Edit() { +const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + + Testing + + +

© { currentYear }

+ + ); +} +``` + +Save the file and refresh the Editor. You should now see the new Settings panel. + +![The Setting Sidebar now displays a custom panel](https://developer.wordpress.org/files/2023/12/block-tutorial-10.png) + +##### Text control + +The next step is to replace the "Testing" message with a `TextControl` component that allows the user to set the `startingYear` attribute. However, you must include two parameters in the `Edit()` function before doing so. + +- `attributes` is an object that contains all the attributes for the block +- `setAttributes` is a function that allows you to update the value of an attribute + +With these parameters included, you can fetch the `showStartingYear` and `startingYear` attributes. + +Update the top of the `Edit()` function to look like this. + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + ... +``` + +
+ To see all the attributes associated with the Copyright Date Block, add console.log( attributes ); at the top of the Edit() function. This can be useful when building and testing a custom block. +
+ +Now, you can remove the "Testing" message and add a `TextControl`. It should include: + +1. A `label` property set to "Starting year" +2. A `value` property set to the attribute `startingYear` +3. An `onChange` property that updates the `startingYear` attribute whenever the value changes + +Putting it all together, the `Edit()` function should look like the following. + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + + + setAttributes( { startingYear: value } ) + } + /> + + +

© { currentYear }

+ + ); +} +``` + +Save the file and refresh the Editor. Confirm that a text field now exists in the Settings panel. Add a starting year and confirm that when you update the page, the value is saved. + +![A live look at editing the new Starting Year field in the Settings Sidebar](https://developer.wordpress.org/files/2023/12/block-tutorial-11.gif) + +##### Toggle control + +Next, let's add a toggle that will turn the starting year functionality on or off. You can do this with a `ToggleControl` component that sets the `showStartingYear` attribute. It should include: + +1. A `label` property set to "Show starting year" +2. A `checked` property set to the attribute `showStartingYear` +3. An `onChange` property that updates the `showStartingYear` attribute whenever the toggle is checked or unchecked + +You can also update the "Starting year" text input so it's only displayed when `showStartingYear` is `true`, which can be done using the `&&` logical operator. + +The `Edit()` function should look like the following. + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + + + setAttributes( { + showStartingYear: ! showStartingYear, + } ) + } + /> + { showStartingYear && ( + + setAttributes( { startingYear: value } ) + } + /> + ) } + + +

© { currentYear }

+ + ); +} +``` + +Save the file and refresh the Editor. Confirm that clicking the toggle displays the text input, and when you update the page, the toggle remains active. + +![A live look at editing the new Show Starting Year toggle in the Settings Sidebar](https://developer.wordpress.org/files/2023/12/block-tutorial-12.gif) + +#### Updating the block content + +So far, you have created the user interface for adding a starting year and updating the associated block attributes. Now you need to actually update the block content in the Editor. + +Let's create a new variable called `displayDate`. When `showStartingYear` is `true` and the user has provided a `startingYear`, the `displayDate` should include the `startingYear` and the `currentYear` separated by an em dash. Otherwise, just display the `currentYear`. + +The code should look something like this. + +```js +let displayDate; + +if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; +} else { + displayDate = currentYear; +} +``` + +
+ When you declare a variable with let, it means that the variable may be reassigned later. Declaring a variable with const means that the variable will never change. You could rewrite this code using const. It's just a matter of personal preference. +
+ +Next, you just need to update the block content to use the `displayDate` instead of the `currentYear` variable. + +The `Edit()` function should look like the following. + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; + } else { + displayDate = currentYear; + } + + return ( + <> + + + + setAttributes( { + showStartingYear: ! showStartingYear, + } ) + } + /> + { showStartingYear && ( + + setAttributes( { startingYear: value } ) + } + /> + ) } + + +

© { displayDate }

+ + ); +} +``` + +Save the file and refresh the Editor. Confirm that the block content updates correctly when you make changes in the Settings panel. + +![A live look at the block content being updated by the new fields in the Setting Sidebar](https://developer.wordpress.org/files/2023/12/block-tutorial-13.gif) + +### Updating render.php + +While the Editor looks great, the starting year functionality has yet to be added to the front end. Let's fix that by updating the `render.php` file. + +Start by adding a variable called `$display_date` and replicate what you did in the `Edit()` function above. + +This variable should display the value of the `startingYear` attribute and the `$current_year` variable separated by an em dash, or just the `$current_year` is the `showStartingYear` attribute is `false`. + +
+

Three variables are exposed in the render.php, which you can use to customize the block's output:

+
    +
  • $attributes (array): The block attributes.
  • +
  • $content (string): The block default content.
  • +
  • $block (WP_Block): The block instance.
  • +
+
+ +The code should look something like this. + +```php +if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) { + $display_date = $attributes['startingYear'] . '–' . $current_year; +} else { + $display_date = $current_year; +} +``` + +Next, you just need to update the block content to use the `$display_date` instead of the `$current_year` variable. + +Your final `render.php` file should look like this. + +```php + +

> + © +

+``` + +Save the file and confirm that the correct block content is now appearing on the front end of your site. + +You have now successfully built a dynamically rendered custom block that utilizes block supports, core WordPress components, and custom attributes. In many situations, this is as far as you would need to go for a block displaying the copyright date with some additional functionality. + +In the next section, however, you will add static rendering to the block. This exercise will illustrate how block data is stored in WordPress and provide a fallback should this plugin ever be inadvertently disabled. + +## Adding static rendering + +A block can be dynamically rendered, statically rendered, or both. The block you have built so far is dynamically rendered. The HTML output of the block is not actually stored in the database, only the block markup and the associated attributes. + +You will see the following if you switch to the Code editor from within the Editor. + +```html + +``` + +Compare this to a statically rendered block like the Paragraph block. + +```html + +

This is a test.

+ +``` + +The HTML of the paragraph is stored in post content and saved in the database. + +You can learn more about dynamic and static rendering in the [Fundamentals documentation](https://developer.wordpress.org/block-editor/getting-started/fundamentals/). While most blocks are either dynamically or statically rendered, you can build a block that utilizes both methods. + +### Why add static rendering? + +When you add static rendering to a dynamically rendered block, the `render.php` file will still control the output on the front end, but the block's HTML content will be saved in the database. This means that the content will remain if the plugin is ever removed from the site. In the case of this Copyright Date Block, the content will revert to a Custom HTML block that you can easily convert to a Paragraph block. + +![An error message in the Editor when a block type no longer exists](https://developer.wordpress.org/files/2023/12/block-tutorial-14.png) + +While not necessary in all situations, adding static rendering to a dynamically rendered block can provide a helpful fallback should the plugin ever be disabled unintentionally. + +Also, consider a situation where the block markup is included in a block pattern or theme template. If a user installs that theme or uses the pattern without the Copyright Date Block installed, they will get a notice that the block is not available, but the content will still be displayed. + +Adding static rendering is also a good exploration of how block content is stored and rendered in WordPress. + +### Adding a save function + +Start by adding a new file named `save.js` to the `src/` folder. In this file, add the following. + +```js +import { useBlockProps } from '@wordpress/block-editor'; + +export default function save() { + return ( +

+ { 'Copyright Date Block – hello from the saved content!' } +

+ ); +} +``` + +This should look similar to the original `edit.js` file, and you can refer to the [block wrapper](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-save-components-markup) documentation for additional information. + +Next, in the `index.js` file, import this `save()` function and add a save property to the `registerBlockType()` function. Here's a simplified view of the updated file. + +```js +import save from './save'; + +... + +registerBlockType( metadata.name, { + icon: calendarIcon, + edit: Edit, + save +} ); +``` + +
+ When defining properties of an object, if the property name and the variable name are the same, you can use shorthand property names. This is why the code above uses save instead of save: save. +
+ +Save both `save.js` and `index.js` files and refresh the Editor. It should look like this. + +![A block validation error message in the Editor](https://developer.wordpress.org/files/2023/12/block-tutorial-15.png) + +Don't worry, the error is expected. If you open the inspector in your browser, you should see the following message. + +![A block validation error message in the console](https://developer.wordpress.org/files/2023/12/block-tutorial-16.png) + +This block validation error occurs because the `save()` function returns block content, but no HTML is stored in the block markup since the previously saved block was dynamic. Remember that this is what the markup currently looks like. + +```html + +``` + +You will see more of these errors as you update the `save()` function in subsequent steps. Just click "Attempt Block Recovery" and update the page. + +After preforming block recovery, open the Code editor and you will see the markup now looks like this. + +```html + + + +``` + +You will often encounter block validation errors when building a block with static rendering, and that's ok. The output of the `save()` function must match the HTML in the post content exactly, which may end up out of sync as you add functionality. So long as there are no validation errors when you're completely finished building the block, you will be all set. + +### Updating save.js + +Next, let's update the output of the `save()` function to display the correct content. Start by copying the same approach used in `edit.js`. + +1. Add the `attributes` parameter to the function +2. Define the `showStartingYear` and `startingYear` variables +3. Define a `currentYear` variable +4. Define a `displayDate` variable depending on the values of `currentYear`, `showStartingYear`, and `startingYear` + +The result should look like this. + +```js +export default function save( { attributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; + } else { + displayDate = currentYear; + } + + return ( +

© { displayDate }

+ ); +} +``` + +Save the file and refresh the Editor. Click "Attempt Block Recovery" and update the page. Check the Code editor, and the block markup should now look something like this. + +```html + + + +``` + +At this point, it might look like you're done. The block content is now saved as HTML in the database and the output on the front end is dynamically rendered. However, there are still a few things that need to be addressed. + +Consider the situation where the user added the block to a page in 2023 and then went back to edit the page in 2024. The front end will update as expected, but in the Editor, there will be a block validation error. The `save()` function knows that it's 2024, but the block content saved in the database still says 2023. + +Let's fix this in the next section. + +### Handling dynamic content in statically rendered blocks + +Generally, you want to avoid dynamic content in statically rendered blocks. This is part of the reason why the term "dynamic" is used when referring to dynamic rendering. + +That said, in this tutorial, you are combining both rendering methods, and you just need a bit more code to avoid any block validation errors when the year changes. + +The root of the issue is that the `currentYear` variable is set dynamically in the `save()` function. Instead, this should be a static variable within the function, which can be solved with an additional attribute. + +#### Adding a new attribute + +Open the `block.json` file and add a new attribute called `fallbackCurrentYear` with the type `string`. The `attributes` section of the file should now look like this. + +```json +"attributes": { + "fallbackCurrentYear": { + "type": "string" + }, + "showStartingYear": { + "type": "boolean" + }, + "startingYear": { + "type": "string" + } +}, +``` + +Next, open the `save.js` file and use the new `fallbackCurrentYear` attribute in place of `currentYear`. Your updated `save()` function should look like this. + +```js +export default function save( { attributes } ) { + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + fallbackCurrentYear; + } else { + displayDate = fallbackCurrentYear; + } + + return ( +

© { displayDate }

+ ); +} +``` + +Now, what happens if the `fallbackCurrentYear` is undefined? + +Before the `currentYear` was defined within the function, so the `save()` function always had content to return, even if `showStartingYear` and `startingYear` were undefined. + +Instead of returning just the copyright symbol, let's add a condition that if `fallbackCurrentYear` is not set, return `null`. It's generally better to save no HTML in the database than incomplete data. + +The final `save()` function should look like this. + +```js +export default function save( { attributes } ) { + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + if ( ! fallbackCurrentYear ) { + return null; + } + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + fallbackCurrentYear; + } else { + displayDate = fallbackCurrentYear; + } + + return ( +

© { displayDate }

+ ); +} +``` + +Save both the `block.json` and `save.js` files; you won't need to make any more changes. + +#### Setting the attribute in edit.js + +The `save()` function now uses the new `fallbackCurrentYear`, so it needs to be set somewhere. Let's use the `Edit()` function. + +Open the `edit.js` file and start by defining the `fallbackCurrentYear` variable at the top of the `Edit()` functional alongside the other attributes. Next, review what's happening in the function. + +When the block loads in the Editor, the `currentYear` variable is defined. The function then uses this variable to set the content of the block. + +Now, let's set the `fallbackCurrentYear` attribute to the `currentYear` when the block loads if the attribute is not already set. + +```js +if ( currentYear !== fallbackCurrentYear ) { + setAttributes( { fallbackCurrentYear: currentYear } ); +} +``` + +This will work but can be improved by ensuring this code only runs once when the block is initialized. To do so, you can use the [`useEffect`](https://react.dev/reference/react/useEffect) React hook. Refer to the React documentation for more information about how to use this hook. + +First, import `useEffect` with the following code. + +```js +import { useEffect } from 'react'; +``` + +Then wrap the `setAttribute()` code above in a `useEffect` and place this code after the `currentYear` definition in the `Edit()` function. The result should look like this. + +```js +export default function Edit( { attributes, setAttributes } ) { + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + // Get the current year and make sure it's a string. + const currentYear = new Date().getFullYear().toString(); + + // When the block loads, set the fallbackCurrentYear attribute to the + // current year if it's not already set. + useEffect( () => { + if ( currentYear !== fallbackCurrentYear ) { + setAttributes( { fallbackCurrentYear: currentYear } ); + } + }, [ currentYear, fallbackCurrentYear, setAttributes ] ); + + ... +``` + +When the block is initialized in the Editor, the `fallbackCurrentYear` attribute will be immediately set. This value will then be available to the `save()` function, and the correct block content will be displayed without block validation errors. + +The one caveat is when the year changes. If a Copyright Date Block was added to a page in 2023 and then edited in 2024, the `fallbackCurrentYear` attribute will no longer equal the `currentYear`, and the attribute will be automatically updated to `2024`. This will update the HTML returned by the `save()` function. + +You will not get any block validation errors, but the Editor will detect that changes have been made to the page and prompt you to update. + +#### Optimizing render.php + +The final step is to optimize the `render.php` file. If the `currentYear` and the `fallbackCurrentYear` attribute are the same, then there is no need to dynamically create the block content. It is already saved in the database and is available in the `render.php` file via the `$block_content` variable. + +Therefore, update the file to render the `$block_content` if `currentYear` and `fallbackCurrentYear` match. + +```php +$current_year = date( "Y" ); + +// Determine which content to display. +if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) { + + // The current year is the same as the fallback, so use the block content saved in the database (by the save.js function). + $block_content = $content; +} else { + + // The current year is different from the fallback, so render the updated block content. + if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) { + $display_date = $attributes['startingYear'] . '–' . $current_year; + } else { + $display_date = $current_year; + } + + $block_content = '

© ' . esc_html( $display_date ) . '

'; +} + +echo wp_kses_post( $block_content ); +``` + +That's it! You now have a block that utilizes both dynamic and static rendering. + +## Wrapping up + +Congratulations on completing this tutorial and building your very own Copyright Date Block. Throughout this journey, you have gained a solid foundation in WordPress block development and are now ready to start building your own blocks. + +For final reference, the complete code for this tutorial is available in the [Block Development Examples](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/copyright-date-block-09aac3) repository on GitHub. + +Now, whether you're now looking to refine your skills, tackle more advanced projects, or stay updated with the latest WordPress trends, the following resources will help you improve your block development skills: + +- [Block Development Environment](https://developer.wordpress.org/block-editor/getting-started/devenv/) +- [Fundamentals of Block Development](https://developer.wordpress.org/block-editor/getting-started/fundamentals/) +- [WordPress Developer Blog](https://developer.wordpress.org/news/) +- [Block Development Examples](https://github.com/WordPress/block-development-examples) | GitHub repository + +Remember, every expert was once a beginner. Keep learning, experimenting, and, most importantly, have fun building with WordPress. diff --git a/docs/manifest.json b/docs/manifest.json index b91732962e0a9..6b5ee58d4d6a5 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -48,53 +48,11 @@ "parent": "getting-started" }, { - "title": "Create a Block Tutorial", - "slug": "create-block", - "markdown_source": "../docs/getting-started/create-block/README.md", + "title": "Tutorial: Build your first block", + "slug": "tutorial", + "markdown_source": "../docs/getting-started/tutorial.md", "parent": "getting-started" }, - { - "title": "WordPress Plugin", - "slug": "wp-plugin", - "markdown_source": "../docs/getting-started/create-block/wp-plugin.md", - "parent": "create-block" - }, - { - "title": "Anatomy of a Block", - "slug": "block-anatomy", - "markdown_source": "../docs/getting-started/create-block/block-anatomy.md", - "parent": "create-block" - }, - { - "title": "Block Attributes", - "slug": "attributes", - "markdown_source": "../docs/getting-started/create-block/attributes.md", - "parent": "create-block" - }, - { - "title": "Code Implementation", - "slug": "block-code", - "markdown_source": "../docs/getting-started/create-block/block-code.md", - "parent": "create-block" - }, - { - "title": "Authoring Experience", - "slug": "author-experience", - "markdown_source": "../docs/getting-started/create-block/author-experience.md", - "parent": "create-block" - }, - { - "title": "Finishing Touches", - "slug": "finishing", - "markdown_source": "../docs/getting-started/create-block/finishing.md", - "parent": "create-block" - }, - { - "title": "Share your Block with the World", - "slug": "submitting-to-block-directory", - "markdown_source": "../docs/getting-started/create-block/submitting-to-block-directory.md", - "parent": "create-block" - }, { "title": "Fundamentals of Block Development", "slug": "fundamentals", diff --git a/docs/toc.json b/docs/toc.json index 34be591647c17..fec39245761c9 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -21,31 +21,7 @@ ] }, { "docs/getting-started/quick-start-guide.md": [] }, - { - "docs/getting-started/create-block/README.md": [ - { - "docs/getting-started/create-block/wp-plugin.md": [] - }, - { - "docs/getting-started/create-block/block-anatomy.md": [] - }, - { - "docs/getting-started/create-block/attributes.md": [] - }, - { - "docs/getting-started/create-block/block-code.md": [] - }, - { - "docs/getting-started/create-block/author-experience.md": [] - }, - { - "docs/getting-started/create-block/finishing.md": [] - }, - { - "docs/getting-started/create-block/submitting-to-block-directory.md": [] - } - ] - }, + { "docs/getting-started/tutorial.md": [] }, { "docs/getting-started/fundamentals/README.md": [ {