Add product form extensibility tutorials (#50137)

* Start on the generic fields tutorial

* Add custom field tutorial

* Some small updates

* Address linting

* Minor updates

* Update manifest

* Address PR feedback, mostly formatting changes
This commit is contained in:
louwie17 2024-08-01 10:50:51 -03:00 committed by GitHub
parent 606224d0d0
commit 97b3b10a2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 362 additions and 3 deletions

View File

@ -813,7 +813,7 @@
"post_title": "Product editor development handbook", "post_title": "Product editor development handbook",
"menu_title": "Development handbook", "menu_title": "Development handbook",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-editor-development/product-editor.md", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-editor-development/product-editor.md",
"hash": "b574a4a5476899342cd229033a22ecdf9859914ea34446f8276e2b0ad5cb8c7f", "hash": "bbf230f9f13bc2404096f5b5b2f7394ba5ab391d78e5efa9627342f5b33be7dd",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-editor-development/product-editor.md", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-editor-development/product-editor.md",
"id": "59450404de2750d918137e7cf523e52bedfd7214", "id": "59450404de2750d918137e7cf523e52bedfd7214",
"links": { "links": {
@ -839,7 +839,30 @@
"id": "0c29c74a7e7e9fd88562df1afa489659f460879e" "id": "0c29c74a7e7e9fd88562df1afa489659f460879e"
} }
], ],
"categories": [] "categories": [
{
"content": "# How-to Guides for the Product form\n\nThere are several ways to extend and modify the new product form. Below are links to different tutorials.\n\n## Generic fields\n\nOne way to extend the new product form is by making use of generic fields. This allows you to easily modify the form using PHP only. We have a wide variety of generic fields that you can use, like a text, checkbox, pricing or a select field. You can find the full list [here](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/blocks/generic/README.md).\n\nTo see how you can make use of these fields you can follow the [generic fields tutorial](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/how-to-guides/generic-fields-tutorial.md).\n\n## Writing a custom field\n\nIt is also possible to write your own custom field and render those within the product form. This is helpful if the generic fields don't quite fit your use case.\nTo see an example of how to create a basic dropdown field in the product form you can follow [this tutorial](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/how-to-guides/custom-field-tutorial.md).\n",
"category_slug": "how-to-guides",
"category_title": "How To Guides",
"posts": [
{
"post_title": "Extending the product form with generic fields",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-editor-development/how-to-guides/generic-fields-tutorial.md",
"hash": "dc00134aa0a50c6d2e7aa5fb558c2d307fb45ab23cd5a31ab6133eebf83a12bd",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-editor-development/how-to-guides/generic-fields-tutorial.md",
"id": "f221ccb6d42c5e67a0a7916b955253ab7e546641"
},
{
"post_title": "Extending the product form with custom fields",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-editor-development/how-to-guides/custom-field-tutorial.md",
"hash": "fd33aa63a9d397a1df35ef032a6f1f76fd1ab5aac1bccc94e29a56fa8bfc2aa4",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-editor-development/how-to-guides/custom-field-tutorial.md",
"id": "fed80efbb225df9054fadd6e1fc45c2cd03e7f99"
}
],
"categories": []
}
]
}, },
{ {
"content": "\nEnsuring the quality of your WooCommerce projects is essential. This section will delve into quality exoectations, best practices, coding standards, and other methodologies to ensure your projects stand out in terms of reliability, efficiency, user experience, and more. \n", "content": "\nEnsuring the quality of your WooCommerce projects is essential. This section will delve into quality exoectations, best practices, coding standards, and other methodologies to ensure your projects stand out in terms of reliability, efficiency, user experience, and more. \n",
@ -1464,5 +1487,5 @@
"categories": [] "categories": []
} }
], ],
"hash": "d8fe058ebcce4d1f40585f1634b1e98e27063d81dd0dd7783217ab60d0bc915a" "hash": "013248fe7a892f063140734071d4c29205d819e137991f9ea25676c1364f3d66"
} }

View File

@ -0,0 +1,14 @@
# How-to Guides for the Product form
There are several ways to extend and modify the new product form. Below are links to different tutorials.
## Generic fields
One way to extend the new product form is by making use of generic fields. This allows you to easily modify the form using PHP only. We have a wide variety of generic fields that you can use, like a text, checkbox, pricing or a select field. You can find the full list [here](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/blocks/generic/README.md).
To see how you can make use of these fields you can follow the [generic fields tutorial](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/how-to-guides/generic-fields-tutorial.md).
## Writing a custom field
It is also possible to write your own custom field and render those within the product form. This is helpful if the generic fields don't quite fit your use case.
To see an example of how to create a basic dropdown field in the product form you can follow [this tutorial](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/how-to-guides/custom-field-tutorial.md).

View File

@ -0,0 +1,263 @@
# Extending the product form with custom fields
Aside from extending the product form using generic fields it is also possible to use custom fields. This does require knowledge of JavaScript and React.
If you are already familiar with writing blocks for the WordPress site editor this will feel very similar.
## Getting started
To get started we would recommend reading through the [fundamentals of block development docs](https://developer.wordpress.org/block-editor/getting-started/fundamentals/) in WordPress. This gives a good overview of working with blocks, the block structure, and the [JavaScript build process](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor/).
This tutorial will use vanilla JavaScript to render a new field in the product form for those that already have a plugin and may not have a JavaScript build process set up yet.
If you want to create a plugin from scratch with the necessary build tools, we recommend using the `@wordpress/create-block` script. We also have a specific template for the product form: [README](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/create-product-editor-block/README.md).
## Creating a custom field
### Adding and registering our custom field
Adding and registering our custom field is very similar as creating a brand new block.
Inside a new folder within your plugin, let's create a `block.json` file with the contents below. The only main difference between this `block.json` and a `block.json` for the site editor is that we will set `supports.inserter` to false, so it doesn't show up there. We will also be registering this slightly different.
```json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "tutorial/new-product-form-field",
"title": "Product form field",
"category": "woocommerce",
"description": "A sample field for the product form",
"keywords": [ "products" ],
"attributes": {},
"supports": {
"html": false,
"multiple": true,
// Setting inserter to false is important so that it doesn't show in the site editor.
"inserter": false
},
"textdomain": "woocommerce",
"editorScript": "file:./index.js"
}
```
In the same directory, create a `index.js` file, which we can keep simple by just outputting a hello world.
In this case the `edit` function is the part that will get rendered in the form. We are wrapping it with the `createElement` function to keep support for React.
```javascript
( function ( wp ) {
var el = wp.element.createElement;
wp.blocks.registerBlockType( 'tutorial/new-product-form-field', {
title: 'Product form field',
attributes: {},
edit: function () {
return el( 'p', {}, 'Hello World (from the editor).' );
},
} );
} )( window.wp );
```
In React:
```jsx
import { registerBlockType } from '@wordpress/blocks';
function Edit() {
return <p>Hello World (from the editor).</p>;
}
registerBlockType( 'tutorial/new-product-form-field', {
title: 'Product form field',
attributes: {},
edit: Edit,
} );
```
Lastly, in order to make this work the block registration needs to know about the JavaScript dependencies, we can do so by adding a `index.asset.php` file with the below contents:
```php
<?php return array('dependencies' => array('react', 'wc-product-editor', 'wp-blocks' ) );
```
Now that we have all the for the field we need to register it and add it to the template.
Registering can be done on `init` by calling `BlockRegistry::get_instance()->register_block_type_from_metadata` like so:
```php
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\BlockRegistry;
function example_custom_product_form_init() {
if ( isset( $_GET['page'] ) && $_GET['page'] === 'wc-admin' ) {
// This points to the directory that contains your block.json.
BlockRegistry::get_instance()->register_block_type_from_metadata( __DIR__ . '/js/sample-block' );
}
}
add_action( 'init', 'example_custom_product_form_init' );
```
We can add it to the product form by hooking into the `woocommerce_layout_template_after_instantiation` action ( see [block addition and removal](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/block-template-lifecycle.md#block-addition-and-removal) ).
What we did was the following ( see [here](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/README.md#usage) for more related functions ):
- Get a group by the `general` id, this is the General tab.
- Create a new section on the general tab called `Tutorial Section`
- Add our custom field to the `Tutorial Section`
```php
add_action(
'woocommerce_layout_template_after_instantiation',
function( $layout_template_id, $layout_template_area, $layout_template ) {
$general = $layout_template->get_group_by_id( 'general' );
if ( $general ) {
// Creating a new section, this is optional.
$tutorial_section = $general->add_section(
array(
'id' => 'tutorial-section',
'order' => 15,
'attributes' => array(
'title' => __( 'Tutorial Section', 'woocommerce' ),
'description' => __( 'Fields related to the tutorial', 'woocommerce' ),
),
)
);
$tutorial_section->add_block(
[
'id' => 'example-new-product-form-field',
'blockName' => 'tutorial/new-product-form-field',
'attributes' => [],
]
);
}
},
10,
3
);
```
### Turn field into a dropdown
We recommend using components from `@wordpress/components` as this will also keep the styling consistent. We will use the [ComboboxControl](https://wordpress.github.io/gutenberg/?path=/docs/components-comboboxcontrol--docs) core component in this field.
We can add it to our `edit` function pretty easily by making use of `wp.components`. We will also add a constant for the filter options.
**Note:** I also added the `blockProps` to the top element, we still recommend using this as some of these props are being used in the product form. When we add the block props we need to also let the form know it is an interactive element. We do this by adding at-least one attribute with the `__experimentalRole` set to `content`.
So lets add this to our `index.js` attributes:
```javascript
attributes: {
"message": {
"type": "string",
"__experimentalRole": "content",
"source": "text",
"selector": "div"
}
},
```
Dropdown options, these can live outside of the `edit` function:
```javascript
const DROPDOWN_OPTIONS = [
{
value: 'small',
label: 'Small',
},
{
value: 'normal',
label: 'Normal',
},
{
value: 'large',
label: 'Large',
},
];
```
The updated `edit` function:
```javascript
// edit function.
function ( { attributes } ) {
// useState is a React specific function.
const [ value, setValue ] = wp.element.useState();
const [ filteredOptions, setFilteredOptions ] = wp.element.useState( DROPDOWN_OPTIONS );
const blockProps = window.wc.blockTemplates.useWooBlockProps( attributes );
return el( 'div', { ...blockProps }, [
el( wp.components.ComboboxControl, {
label: "Example dropdown",
value: value,
onChange: setValue,
options: filteredOptions,
onFilterValueChange: function( inputValue ) {
setFilteredOptions(
DROPDOWN_OPTIONS.filter( ( option ) =>
option.label
.toLowerCase()
.startsWith( inputValue.toLowerCase() )
)
)
}
} )
] );
},
```
In React:
```jsx
import { createElement, useState } from '@wordpress/element';
import { ComboboxControl } from '@wordpress/components';
import { useWooBlockProps } from '@woocommerce/block-templates';
function Edit( { attributes } ) {
const [ value, setValue ] = useState();
const [ filteredOptions, setFilteredOptions ] =
useState( DROPDOWN_OPTIONS );
const blockProps = useWooBlockProps( attributes );
return (
<div { ...blockProps }>
<ComboboxControl
label="Example dropdown"
value={ value }
onChange={ setValue }
options={ filteredOptions }
onFilterValueChange={ ( inputValue ) =>
setFilteredOptions(
DROPDOWN_OPTIONS.filter( ( option ) =>
option.label
.toLowerCase()
.startsWith( inputValue.toLowerCase() )
)
)
}
/>
</div>
);
}
```
### Save field data to the product data
We can make use of the `__experimentalUseProductEntityProp` for saving the field input to the product.
The function does rely on `postType`, we can hardcode this to `product`, but the `postType` is also exposed through a context. We can do so by adding `"usesContext": [ "postType" ],` to the `block.json` and getting it from the `context` passed into the `edit` function props.
So the top part of the edit function will look like this, where we also replace the `value, setValue` `useState` line:
```javascript
// edit function.
function ( { attributes, context } ) {
const [ value, setValue ] = window.wc.productEditor.__experimentalUseProductEntityProp(
'meta_data.animal_type',
{
postType: context.postType,
fallbackValue: '',
}
);
// .... Rest of edit function
```
Now if you select small, medium, or large from the dropdown and save your product, the value should persist correctly.
Note, the above function supports the use of `meta_data` by dot notation, but you can also target other fields like `regular_price` or `summary`.

View File

@ -0,0 +1,58 @@
# Extending the product form with generic fields
We have large list of generic fields that a plugin can use to extend the new product form. You can find the full list [here](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/blocks/generic/README.md). Each field contains documentation for what attributes the field supports.
## Using a generic block
Using a generic block is pretty easy. We have created an template API that allows you to add new fields, the API refers to them as `blocks`. There are a couple actions that allow us to interact with these templates. There is the `woocommerce_layout_template_after_instantiation` that is triggered when a new template is registered. There are also other actions triggered when a specific field/block is added ( see [block addition and removal](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/block-template-lifecycle.md#block-addition-and-removal) ).
Let's say we want to add something to the basic details section, we can do so by making use of the above mentioned hook:
This will add a number field called **Animal age** to each template that has a `basic-details` section.
```php
add_action(
'woocommerce_layout_template_after_instantiation',
function( $layout_template_id, $layout_template_area, $layout_template ) {
$basic_details = $layout_template->get_section_by_id( 'basic-details' );
if ( $basic_details ) {
$basic_details->add_block(
[
'id' => 'example-tutorial-animal-age',
// This orders the field, core fields are seperated by sums of 10.
'order' => 40,
'blockName' => 'woocommerce/product-number-field',
'attributes' => [
// Attributes specific for the product-number-field.
'label' => 'Animal age',
'property' => 'meta_data.animal_age',
'suffix' => 'Yrs',
'placeholder' => 'Age of animal',
'required' => true,
'min' => 1,
'max' => 20
],
]
);
}
},
10,
3
);
```
### Dynamically hiding or showing the generic field
It is also possible to dynamically hide or show your field if data on the product form changes.
We can do this by adding a `hideCondition` ( plural ). For example if we wanted to hide our field if the product price is higher than 20, we can do so by adding this expression:
```php
'hideConditions' => array(
array(
'expression' => 'editedProduct.regular_price >= 20',
),
),
```
The `hideConditions` also support targeting meta data by using dot notation. You can do so by writing an expression like this: `! editedProduct.meta_data.animal_type` that will hide a field if the `animal_type` meta data value doesn't exist.

View File

@ -37,6 +37,7 @@ Please note that this check is currently not being enforced: the product editor
## Related documentation ## Related documentation
- [Examples on Template API usage](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/README.md/) - [Examples on Template API usage](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/README.md/)
- [How to guides](https://github.com/woocommerce/woocommerce/blob/trunk/docs/product-editor-development/how-to-guides/README.md)
- [Related hooks and Template API documentation](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/BlockTemplates/README.md) - [Related hooks and Template API documentation](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/BlockTemplates/README.md)
- [Generic blocks documentation](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/blocks/generic/README.md) - [Generic blocks documentation](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/blocks/generic/README.md)
- [Validations and error handling](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/contexts/validation-context/README.md) - [Validations and error handling](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/contexts/validation-context/README.md)