Repository for the Ready to try the new product editor? workshop of the WordCamp Europe 2024 edition.
we use wp-env core tool to set up the development environment.
cd woo-product-editor-workshop
WP_ENV_PORT=88 wp-env start
user: admin
password: password
- Create the plugin:
npx @wordpress/create-block --template @woocommerce/create-product-editor-block
@woocommerce/create-product-editor-block
- Start wp-env in the main repo
In the root of the project:
npx @wordpress/env start
- Watch & Compile
cd plugin/name
npm run build
npm start
( NOTE: There is a bug with Webpack and we had to install the ajv
package ( npm add ajv --save-dev
) )
- Create new section in the general tab using
get_group_by_id
andadd_section
Changing:
$basic_details = $template->get_section_by_id( 'basic-details' );
if ( $basic_details ) {
$basic_details->add_block(
[
'id' => '{{namespace}}-{{slug}}',
'order' => 40,
'blockName' => '{{namespace}}/{{slug}}',
'attributes' => [
'message' => '{{title}}',
]
]
);
}
To
$general = $template->get_group_by_id( 'general' );
if ( $general ) {
$animal_details = $general->add_section(
array(
'id' => 'animal-details',
'order' => 15,
'attributes' => array(
'title' => __( 'Animal Details', 'woocommerce' ),
),
)
);
$animal_details->add_block(
[
'id' => 'wordcamp-example-animal-data-selector',
'order' => 40,
'blockName' => 'wordcamp/example-animal-data-selector',
'attributes' => [
'message' => 'Example Animal Data Selector',
]
]
);
}
We use the ComboboxControl core component in the block.
Outside of edit function:
const options = [
{
value: 'small',
label: 'Small',
},
{
value: 'normal',
label: 'Normal',
},
{
value: 'large',
label: 'Large',
},
];
- Copy over ComboboxControl example to the
src/edit.tsx
Within Edit function:
const [ animalType, setAnimalType ] = useState();
const [ filteredOptions, setFilteredOptions ] = useState( options );
return <div { ...blockProps }>
<ComboboxControl
label="Animal type"
value={ animalType }
onChange={ setAnimalType }
options={ filteredOptions }
onFilterValueChange={ ( inputValue ) =>
setFilteredOptions(
options.filter( ( option ) =>
option.label
.toLowerCase()
.startsWith( inputValue.toLowerCase() )
)
)
}
/>
</div>;
- Remove yellow background styling in
src/editor.scss
.
- Update dropdown with sample data
const options = [
{
value: 'dog',
label: 'Dog',
},
{
value: 'cat',
label: 'Cat',
},
{
value: 'bird',
label: 'Bird',
},
];
- Add
postType
context by adding...
"usesContext": [ "postType" ],
...to the block.json
- Add import:
import { __experimentalUseProductEntityProp as useProductEntityProp } from '@woocommerce/product-editor';
- Using the
postType
from the context add this line within the Edit function:
const [ animalType, setAnimalType ] = useProductEntityProp< string >( 'meta_data.animal_type', {
postType: context.postType,
fallbackValue: '',
} );
- Add import:
import { useEntityProp } from '@wordpress/core-data';
- Add tags to Edit function:
const [ tags, setTags ] = useEntityProp( 'postType', context.postType, 'tags' );
- Add:
const onAnimalSelection = ( value: string ) => {
setAnimalType( value );
// get selected option.
const option = filteredOptions.find( opt => opt.value === value );
if ( option ) {
setTags([
// Filter out any other animal type tags if they exist.
...( tags.filter( tag => ! tag.slug.startsWith('animal_type_') ) ),
{
name: option?.label,
slug: 'animal_type_' + option.value
}
]);
}
}
- Replace the
onChange
function withonAnimalSelection
Use re-useable blocks. See this README for a list of each re-useable block and the attributes it supports.
- Add animal age block:
$animal_details->add_block(
[
'id' => 'wordcamp-example-animal-age',
'order' => 40,
'blockName' => 'woocommerce/product-number-field',
'attributes' => [
'label' => 'Animal age',
'property' => 'meta_data.animal_age',
'suffix' => 'Yrs',
'placeholder' => 'Age of animal',
'required' => true,
'min' => 1,
'max' => 20
],
]
);
- Add hide condition:
'hideConditions' => array(
array(
'expression' => '! editedProduct.meta_data.animal_type',
),
),
Let's put the whole extending code in its file.
Create and import a new extend/index.tsx
file.
import './extend';
- Import relevant functions
/**
* External dependencies
*/
import React from 'react';
import { createHigherOrderComponent } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';
- Create with
withAnimalToTheRescue
HOC:
const withAnimalToTheRescue = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
return (
<>
<h1>🐶 knows!</h1>
<BlockEdit { ...props } />
</>
);
};
}, 'withAnimalToTheRescue' );
- Filter the block instance
Warning: We only recommend using this filter with JavaScript/React outside of the block context, for example with the use of registerPlugin
.
But for the sake of demo purposes, we will import the JS below within a block.
addFilter(
'editor.BlockEdit',
'example-animal-data-selector/extend/with-animal-to-the-rescue',
withAnimalToTheRescue
);
- Extend the specific block instance
const { name, attributes } = props;
const { _templateBlockId } = attributes;
if ( name !== 'woocommerce/product-summary-field' ) {
return <BlockEdit { ...props } />;
}
if ( _templateBlockId !== 'product-description__content' ) {
return <BlockEdit { ...props } />;
}
//...
- Fill the
<SectionActions />
slot
import { __experimentalSectionActions as SectionActions } from '@woocommerce/product-editor';
<SectionActions>
<h1>🐶 knows!</h1>
</SectionActions>
- Create the SuggestionButton component
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Icon, bug } from '@wordpress/icons';
function SuggestionButton() {
return (
<Button
variant="secondary"
icon={ <Icon icon={ bug } /> }
onClick={ console.log }
>
{ __( '🐥 Animals to the Rescue!', 'woocommerce' ) }
</Button>
);
}
- Load the action type, and set the product description.
import { __experimentalUseProductEntityProp as useProductEntityProp } from '@woocommerce/product-editor';
import { useEntityProp } from '@wordpress/core-data';
const [ animalType ] = useProductEntityProp< string >(
'meta_data.animal_type',
{
postType: 'product',
fallbackValue: '',
}
);
const [ , setDescription ] = useEntityProp(
'postType',
'product',
'description'
);
function suggestDescription() {
if ( ! animalType ) {
return;
}
setDescription(
`This product is perfect for your ${ animalType.toUpperCase() }!`
);
}
Add editor block to specifically render the animal information.
- Run
npx @wordpress/create-block --no-plugin
withinsrc/
( using dynamic block, and animal-info as the block slug ) - Add
register_block_type( __DIR__ . '/build/animal-info');
( new block ) for more info on the block registration see: https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/ - Add
"usesContext": [ "postId" ],
toblock.json
- Update the
render.php
file and add:
<?php
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
if ( ! $product ) {
return '';
}
$animal_type = get_post_meta( $post_id, 'animal_type', true );
$animal_age = get_post_meta( $post_id, 'animal_age', true );
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<h3>Animal Info:</h3>
<p>
<?php esc_html_e( 'Type', 'animal-info' ); ?>: <?php echo $animal_type ?><br/>
<?php esc_html_e( 'Age', 'animal-info' ); ?>: <?php echo $animal_age ?><br/>
</p>
</div>
- Add this to "supports" within the
block.json
so we can also set the background and text colour:
"color": {
"text": true,
"background": true
}
- Use the new animal info block within the Site Editor
Single Product
template.