diff --git a/plugins/woocommerce-blocks/docs/extensibility/README.md b/plugins/woocommerce-blocks/docs/extensibility/README.md index 69ff14090f4..153d524557a 100644 --- a/plugins/woocommerce-blocks/docs/extensibility/README.md +++ b/plugins/woocommerce-blocks/docs/extensibility/README.md @@ -7,4 +7,5 @@ These documents are all dealing with extensibility in the various WooCommerce Bl | Document | Description | | ---------- | ---------- | [Payment Method Integration](./payment-method-integration.md) | Information about implementing payment methods. -[Checkout Flow and Events](./checkout-flow-and-events.md) | All about the checkout flow in the checkout block and the various emitted events that can be subscribed to. \ No newline at end of file +[Checkout Flow and Events](./checkout-flow-and-events.md) | All about the checkout flow in the checkout block and the various emitted events that can be subscribed to. +[Available Filters](./available-filters.md) | All about the filters that you may use to change values of certain elements of WooCommerce Blocks. diff --git a/plugins/woocommerce-blocks/docs/extensibility/available-filters.md b/plugins/woocommerce-blocks/docs/extensibility/available-filters.md new file mode 100644 index 00000000000..b5c6006f4ea --- /dev/null +++ b/plugins/woocommerce-blocks/docs/extensibility/available-filters.md @@ -0,0 +1,154 @@ +# Filters +Like traditional WordPress filters (you register a callback with a specific filter, your callback accepts a number of +arguments, then it returns a value), we are introducing filters to the WooCommerce Blocks extension. These will function +very similarly to the traditional filters. + +Your extension will use `__experimentalRegisterCheckoutFilter` to set up a filter. + +This function has the following signature: + +```typescript +( + namespace: string, + filters: Record< string, CheckoutFilterFunction > +) +``` + +and a `CheckoutFilterFunction` has this signature: + +```typescript +type CheckoutFilterFunction = < T >( + value: T, + extensions: Record< string, unknown >, + args?: CheckoutFilterArguments +) => T; +``` + +In this, you'll specify which filter you want to work with (available filters are listed below) and the +function (`CheckoutFilterFunction`) to execute when this filter is applied. + +When the `CheckoutFilterFunction` is invoked, the following arguments are passed to it: +- `value` - The value to be filtered. +- `extensions` A n object containing extension data. If your extension has extended any of the store's API routes, one + of the keys of this object will be your extension's namespace. The value will contain any data you add to the endpoint. + Each key in the `extensions` object is an extension namespace, so a third party extension cannot interfere with _your_ + extension's schema modifications, unless there is a naming collision, so please ensure your extension has a unique + namespace that is unlikely to conflict with other extensions. +- `args` - An object containing any additional data passed to the filter function. This usually (but not always) contains at least a key +called `context`. The value of `context` will be (at the moment) either `cart` or `checkout`. This is provided to inform + extensions about the exact location that the filter is being applied. The same filter can be applied in multiple + places. + +## Available filters + +This section of the document will list the filters that are currently available to extensions, where exactly +the filter is applied, and what data might be passed to the `CheckoutFilterFunction`. + +### Cart Line Items +Line items refers to each item listed in the cart or checkout. For instance +the "Sunglasses" and "Beanie with logo" in this image are the line items. + + + +The following filters are available for line items: + +| Filter name | Description | Return type | +|---|---|---| +| `itemName` | Used to change the name of the item before it is rendered onto the page | `string` +| `cartItemPrice` | This is the price of the item, multiplied by the number of items in the cart. | `string` and **must** contain the substring `` where the price should appear. +| `subtotalPriceFormat` | This is the price of a single item. Irrespective of the number in the cart, this value will always be the current price of _one_ item. | `string` and **must** contain the substring `` where the price should appear. +| `saleBadgePriceFormat` | This is amount of money saved when buying this item. It is the difference between the item's regular price and its sale price. | `string` and **must** contain the substring `` where the price should appear. + +Each of these filters has the following arguments passed to it: `{ context: 'cart', cartItem: CartItem }` ([CartItem](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L113)) + +### Order Summary Items +In the Checkout block, there is a sidebar that contains a summary of what the customer is about to purchase. +There are some filters available to modify the way certain elements are displayed on each item. + +The sale badges are not shown here, so those filters are not applied in the Order Summary. + + + +| Filter name | Description | Return type | +|---|---|---| +| `itemName` | Used to change the name of the item before it is rendered onto the page | `string` +| `cartItemPrice` | This is the price of the item, multiplied by the number of items in the cart. | `string` and **must** contain the substring `` where the price should appear. +| `subtotalPriceFormat` | This is the price of a single item. Irrespective of the number in the cart, this value will always be the current price of _one_ item. | `string` and **must** contain the substring `` where the price should appear. + +Each of these filters has the following additional arguments passed to it: `{ context: 'summary', cartItem: CartItem }` ([CartItem](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L113)) + +### Totals footer item (in Cart and Checkout) + +The word 'Total' that precedes the amount due, present in both the Cart _and_ Checkout blocks, is also passed through filters. + +| Filter name | Description | Return type | +|---|---|---| +| `totalLabel` | This is the label for the cart total. It defaults to 'Total' (or the word for 'Total' if using translations). | `string` + +There are no additional arguments passed to this filter. + +## Examples + +### Changing the wording of the Totals label in the Cart and Checkout +For this example, let's suppose we are building an extension that lets customers pay a deposit, and defer the full amount until a later date. + +To make it easier to understand what the customer is paying and why, let's change the value of `Total` to `Deposit due today`. + +1. We need to create a `CheckoutFilterFunction`. +```typescript +const replaceTotalWithDeposit = () => 'Deposit due today'; +``` +2. Now we need to register this filter function, and have it executed when the `totalLabel` filter is applied. + We can access the `__experimentalRegisterCheckoutFilters` function on the `window.wc.blocksCheckout` object. + As long as your extension's script is enqueued _after_ WooCommerce Blocks' scripts (i.e. by registering `wc-blocks-checkout` as a dependency), then this will be available. + ```typescript +const { __experimentalRegisterCheckoutFilters } = window.wc.blocksCheckout; +__experimentalRegisterCheckoutFilters( 'my-hypothetical-deposit-plugin', { + totalLabel: replaceTotalWithDeposit +} ); +``` + +| Before | After | +|---|---| +| | | + + +### Changing the format of the item's single price +Let's say we want to add a little bit of text after an item's single price **in the Cart only**, just to make sure our customers know +that's the price per item. + +1. We will need to register a function to be executed when the `subtotalPriceFormat` is applied. Since we only want this to happen in the +Cart context, our function will need to check the additional arguments passed to it to ensure the `context` value is `cart`. + +We can see from the table above, that our function needs to return a string that contains a substring of ``. +This is a placeholder for the numeric value. The Cart block will interpolate the value into the string we return. +```typescript +const appendTextToPriceInCart = ( value, extensions, args ) => { + if( args?.context !== 'cart') { + // Return early since this filter is not being applied in the Cart context. + // We must return the original value we received here. + return value; + } + return ' per item'; +}; +``` +2. Now we must register it. Refer to the first example for information about `__experimentalRegisterCheckoutFilters`. +```typescript +const { __experimentalRegisterCheckoutFilters } = window.wc.blocksCheckout; +__experimentalRegisterCheckoutFilters( 'my-hypothetical-price-plugin', { + subtotalPriceFormat: appendTextToPriceInCart +} ); +``` + + +| Before | After | +|---|---| +| | | + +## Troubleshooting +If you are logged in to the store as an administrator, you should be shown an error like this if your filter is not +working correctly. + + + +The error will also be shown in your console. diff --git a/plugins/woocommerce-blocks/packages/checkout/registry/index.ts b/plugins/woocommerce-blocks/packages/checkout/registry/index.ts index fbba64f6c9e..cc49a8ddb6d 100644 --- a/plugins/woocommerce-blocks/packages/checkout/registry/index.ts +++ b/plugins/woocommerce-blocks/packages/checkout/registry/index.ts @@ -11,7 +11,7 @@ import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; import { returnTrue } from '../utils'; type CheckoutFilterFunction = < T >( - label: T, + value: T, extensions: Record< string, unknown >, args?: CheckoutFilterArguments ) => T;