--- myst: html_meta: "description": "Volto add-ons development training module 4, add-ons block edit" "property=og:description": "Volto add-ons development training module 4" "property=og:title": "Volto add-ons development block edit options" "keywords": "Volto" --- # Block editing with a form We'll add schema-based editing of the block settings. In Volto the convention is to use the schema generated from a function, so that the resulting object can be freely mutated, as it comes from a closure. The most basic schema would look something like this: ```jsx export const TableSchema = ({formData, intl}) => ({ title: 'Table', fieldsets: [ { id: 'default', title: 'Default', fields: ['description'], }, ], properties: { description: { title: 'Description', widget: 'textarea' } }, required: ['title'], }); ``` This schema is based on the one used by `plone.restapi` to edit the server-side `Dexterity` content. We're appropriating it. It lives on the client side right now, so we have some freedom in extending it with new logic and capabilities. The only requirement is that Volto's form implementation understands it. But even here we have a lot of freedom, as the form passes all the field props to the widgets. To understand how to structure the schema, you need to read Volto's [Field.jsx] code. In it, we see the following logic: ```jsx const Widget = getWidgetByFieldId(props.id) || getWidgetByName(props.widget) || getWidgetByChoices(props) || getWidgetByVocabulary(props.vocabulary) || getWidgetByVocabularyFromHint(props) || getWidgetByFactory(props.factory) || getWidgetByType(props.type) || getWidgetDefault(); ``` The precedence order of the algorithm is pretty self-explanatory. You can specify a widget for a particular field with: ```jsx config.widgets.id.some_fieldname = MyWidget`; ``` Or you can set the `widget` property in a schema: ```jsx //... properties: { headline: { title: "Headline", widget: "headline_widget", } } //... ``` See [Volto's widget documentation] for more details, including how to designate a widget for a particular Dexterity field. Now we add a basic schema to control the table styling. Create the `src/DataTable/schema.js` file: ```jsx import { defineMessages } from 'react-intl'; export const TableSchema = ({ intl }) => ({ title: 'Table', fieldsets: [ { id: 'default', title: intl.formatMessage(messages.defaultFieldset), fields: ['file_path'], }, { id: 'style', title: intl.formatMessage(messages.styling), fields: ['fixed', 'celled', 'striped', 'compact', 'basic', 'inverted'], }, ], properties: { file_path: { title: intl.formatMessage(messages.dataFile), widget: 'object_browser', mode: 'link', }, fixed: { type: 'boolean', title: intl.formatMessage(messages.fixed), }, compact: { type: 'boolean', title: intl.formatMessage(messages.compact), }, basic: { type: 'boolean', title: intl.formatMessage(messages.basic), }, celled: { type: 'boolean', title: intl.formatMessage(messages.celled), }, inverted: { type: 'boolean', title: intl.formatMessage(messages.inverted), }, striped: { type: 'boolean', title: intl.formatMessage(messages.striped), }, }, required: ['file_path'], }); const messages = defineMessages({ fixed: { id: 'Fixed width table cells', defaultMessage: 'Fixed width table cells', }, compact: { id: 'Make the table compact', defaultMessage: 'Make the table compact', }, basic: { id: 'Reduce complexity', defaultMessage: 'Reduce complexity', }, celled: { id: 'Divide each row into separate cells', defaultMessage: 'Divide each row into separate cells', }, inverted: { id: 'Table color inverted', defaultMessage: 'Table color inverted', }, striped: { id: 'Stripe alternate rows with color', defaultMessage: 'Stripe alternate rows with color', }, styling: { id: 'Styling', defaultMessage: 'Styling', }, defaultFieldset: { id: 'Default', defaultMessage: 'Default', }, dataFile: { id: 'Data file', defaultMessage: 'Data file', }, }); ``` Notice that our schema is actually a function that returns a JavaScript object, not least because we need to have access to the `intl` utility to provide internationalization. To use the schema, we need to change the block edit component from `src/DataTable/DataTableEdit.jsx`: ```jsx // ... import { InlineForm } from '@plone/volto/components'; import { TableSchema } from './schema'; // ... const DataTableEdit = (props) => { const { selected, onChangeBlock, block, data } = props; const schema = TableSchema(props); return (