Gutenberg: Table Block: Should we use InnerBlocks?

Created on 26 Nov 2019  Â·  12Comments  Â·  Source: WordPress/gutenberg

After using Media & Text, Cover, Columns, etc., it feels weird to use the Table block and have it not somehow use InnerBlocks — I keep expecting individual cell options, or row-column options, and the ability to easily move cells around using block movers.

[Block] Table [Type] Question

Most helpful comment

I've refactored RichText so it doesn't have any wrapper div elements left, but blocks still have a lot of wrapper div element for all the controls and positioning. This will make it much harder to get to work. I kind of wish we never put block controls in the block DOM, but rather put it outside the block list and position it correctly when a block is selected.

All 12 comments

How exactly would moving a cell work? Would it swap places with another cell? How would you handle moving rows or columns, if you handle that at all? If there were row blocks, then there couldn't be column blocks, and vice-versa.

I agree with this, specially with adding buttons inside the table.

For what it's worth, @ellatrix made this happen in a pull request a while back, and what we found trying to style the editor version to match the front-end was that it was extremely difficult due to the extra divs and elements that are necessary to make editable fields in the block editor. At the time we decided not to pursue it until we can reliably use something like display: contents;. Those "caniuse" stats look better, but require us to shed support for IE11 and to an extent, even classic Edge (the new Chromium based Edge is fine: https://www.microsoftedgeinsider.com/en-us/). Additional refactorings, such as improvements to horizontal margins, have happened since that conversation, so it's looking more realistic now than it did then. But it's still going to be a challenge!

I've refactored RichText so it doesn't have any wrapper div elements left, but blocks still have a lot of wrapper div element for all the controls and positioning. This will make it much harder to get to work. I kind of wish we never put block controls in the block DOM, but rather put it outside the block list and position it correctly when a block is selected.

@ellatrix I agree that moving the block toolbar outside of the block DOM would be a good idea. Is it too late to change the implementation now? I think you could do it with something like React Portals?

@ZebulanStanphill No, definitely not too late. With some adjustments to Popover, it shouldn't be too hard to do. I started work in #18779.

I've thought a bit about how this might work.

First off, there would probably have to be inner blocks for table headers (<thead>), bodies (<tbody>) and footers (<tfoot>) so that they can be added/removed. Maybe a 'Table Section' block. The top-most table block could render these Table Sections as inner blocks.

Each of those Table Sections could render inner Table Row blocks (<tr>). That gives the handy functionality of being able to select rows as blocks add new rows using the block inserter.

The tricky part is columns. A column is individual <td> or <th> elements that might span across several rows and sections. Not really sure how those could be modelled as blocks or how selection/insertion of a column might work.

Maybe someone has a good idea how that might be possible.

It would be great to have table row blocks and cell blocks, maybe even "virtual" column blocks.
Then each cell and row can be block styles assigned. This is very useful! And it is possible in HTML, too.

I wonder if the new useInnerBlockProps React hook helps with the technical challenges on this issue now. Or alternatively offers possibilities for a new API for declaring tables.

Now that the children inner blocks can be accessed, potentially the array could be mapped into table sections, rows and cells.

A wrapper around useInnerBlockProps that handles the mapping could be one way to expose this:

    const { sections } = useInnerBlockTable(
        {},
        { sections: [ 'thead', 'tbody', 'tfoot' ], rows: 4, columns: 4 }
    );

    return (
        <table>
            { sections.map( ( { name: Tag, props: sectionProps, rows } ) => (
                <Tag { ...sectionProps }>
                    { rows.map( ( { props: rowProps, cells }, index ) => (
                        <tr { ...rowProps }>
                            { cells.map( ( { cell } ) => cell ) }
                        </tr>
                    ) ) }
                </Tag>
            ) ) }
        </table>
    );

I think the main challenge would still be selecting blocks—the block editor doesn't support row or column selection, only sequential selection. 🤔

The lighter DOM prop will help the table a lot. But this is still the primary blocker:

I think the main challenge would still be selecting blocks—the block editor doesn't support row or column selection, only sequential selection. 🤔

Table is almost a textbook example for why we might need the "passthrough" prop from #7694. In the following example markup:

<table>
    <thead>
        <tr>
            <th colspan="2">The table header</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>The table body</td>
            <td>with two columns</td>
        </tr>
    </tbody>
</table>

For a good user experience you should never have to select anything other than the table itself and the td cells inside. Every other nesting elements should simply not be selectable. If the passthrough prop can help accomplish that, I think we'll be ready to move to nesting!

For a good user experience you should never have to select anything other than the table itself and the td cells inside. Every other nesting elements should simply not be selectable. If the passthrough prop can help accomplish that, I think we'll be ready to move to nesting!

I was thinking that there'd be no need for passthrough blocks (for table sections/rows). My idea is there'd be two blocks core/table and core/table-cell, with the latter being the inner block.

With useInnerBlocksProps we have access to the individual inner blocks. All the inner 'core/table-cell' blocks end up in a single array because that's how the block editor stores them:

const { children } = useInnerBlocksProps();
// inner blocks for the table block are table cell blocks like this:
// [ 
//   <TableCell />, 
//   <TableCell />, 
//   <TableCell />,
//   <TableCell />,
//       ...
// ]

Now that we have access to that array (thanks to useInnerBlocksProps), they can be mapped into table sections and rows:

const rows = mapToRows( children, rowSizes );
// The previous single array of cells is now mapped into rows:
// [
//       row 1:
//   [ 
//                <TableCell />, 
//            <TableCell />,
//       ],
//       row 2:
//   [ 
//                <TableCell />, 
//            <TableCell />,
//       ],
//       ...
// ]

Then elements like tbody and tr are just rendered as normal elements with the table cell blocks inside them.

The issue I mentioned with selection is not related to clicking through the block heirarchy, but multi-block selection. Not really an issue for rows which is just selecting consecutive blocks and already supported, but column selections would be a non-contiguous block selection (as described in these issues: https://github.com/WordPress/gutenberg/issues/2320, https://github.com/WordPress/gutenberg/issues/16895), as the selection would be every nth inner block element.

That would still be a beneficial feature for the block editor generally, but something that's been tricky to implement before.

Other challenges:

  • We'd have to keep a data representation of the sections, number of rows in each section, and cells in each row somewhere, and it would have to stay in sync with the blocks.
  • Similar to selection, inserting a column would also be inserting every nth block (which is not very nice technically, but possible).
  • While we have useInnerBlocksProps for a block's edit, I don't think we have the same for save.

Awesome approach, and thank you for outlining it so clearly.

That would still be a beneficial feature for the block editor generally, but something that's been tricky to implement before.

I recall conversations about non-contiguous multi selections in the past. Would a good first step be to allow you to select multiple separate cells by holding ⌘ when you click?

Was this page helpful?
0 / 5 - 0 ratings