Tables With Dynamically Nested Arrays in WordPress Block Plugin
Building a WordPress block plugin for implementing tables with dynamically nested arrays using custom CSS styling.

In this post I will show you an example of how to create tables with dynamically nested arrays to build a WordPress block plugin.

ID Name Drinks Coffee?
1 Robert Yes
2 Jennifer No

I wanted to further my understanding of creating custom block plugins for WordPress. Recently I had a need for DOM TablesΒ but the built in block Table for WordPress was giving me issues. Possibly due to conflicts with another plugin. I have already built and styled DOM TablesΒ in a previous post linked below. With this in mind, I set out to build a WordPress block plugin for tables with dynamically nested arrays.

Dynamic Tables With Nested Arrays WordPress Block Plugin

Notes

The table content is controlled by an attribute set in block.json. This attribute will be of type array. This top level array will be the rows in the table, set as DOM TR. Inside this array will be another array to hold the data that the user can edit, set as DOM TD. The data, or table cells, can be edited and saved by using WordPress RichText.

The user can edit how many rows they want and how many cells they want in the table. The data is not saved to a separate database, just dynamically created through the editor. When the post / page is saved, then the data is saved to the content of that post / page the block plugin is used in.

I have also implemented custom styling with the a few attributes to separate the table rows with different colors that the user can modify as well.

React Keys Attribute

One of the issues I had was how to properly implement the syntax in JSX for the nested array to render in the editor inΒ edit.js. The missing piece I needed was adding a key attribute in the DOM element, specifically theΒ TRΒ element. React needs aΒ keyΒ attribute whenever you work with arrays due to its having a sense ofΒ StateΒ processing for element updates. Meaning React needs a way to identify which elements in an array have been modified, added or removed to update the contents. These keys need to be unique to prevent conflicts.

Code

I will run through each of the needed files in the project with explanations where necessary.

Block.json

Added a few attributes for styling the colors of the table. One for the text color, even table rows and odd table rows.

The main attribute that is used to build this WordPress block plugin for tables with dynamically nested arrays is the content attribute.

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "r2creations24/my-custom-blocks-table",
	"version": "0.1.0",
	"title": "My Custom Blocks Table",
	"category": "widgets",
	"icon": "smiley",
	"description": "Custom blocks created by R2creations24.",
	"example": {},
  "attributes": {
    "rowCount": {
      "type": "number",
      "default": 1
    },
    "columnCount": {
      "type": "number",
      "default": 2
    },
    "textColor": {
      "type": "string",
      "default": "#ffffff"
    },
    "evenRowColor": {
      "type": "string",
      "default": "#219b8b"
    },
    "oddRowColor": {
      "type": "string",
      "default": "#21759b"
    },
    "content": {
      "type": "array",
      "default": [ {"cells": ["",""] } ],
      "query": {
        "cells": {
          "type": "array",
          "default": [],
          "source": "query",
          "query": {
            "text": {
              "type": "string",
              "source": "text"
            }
          }
        }
      }
    }
  },
	"supports": {
		"html": false
	},
	"textdomain": "my-custom-blocks-table",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"render": "file:./render.php",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}


Edit.js

This file holds the logic for the dynamic tables.


I am using WordPress library PanelColorSettings to set the various color attributes. This is useful because you can create a single panel for all colors you wish to set.

There are two main custom functions used to build the table based on user input.

Breakdown

Imports And Main Function

import { __ } from '@wordpress/i18n';
import './editor.scss';
import { 
    useBlockProps, 
    InspectorControls, 
    RichText, 
    PanelColorSettings } from '@wordpress/block-editor';
import { PanelBody} from '@wordpress/components';
import { __experimentalNumberControl as NumberControl } from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {

  const { content } = attributes;

  const newStyle = {
    "--text-color": attributes.textColor,
    "--even-table-row-bg-color": attributes.evenRowColor,
    "--odd-table-row-bg-color": attributes.oddRowColor
  };

  const blockProps = useBlockProps({
    style: newStyle
  });
  
  const onChangeTextColor = value => setAttributes({ textColor: value !== undefined ? value : "#ffffff" });
  
  // attribute functions
  
  	return (
  	<>
  	// editor right pane return
  	
  	// editor UI return
  	</>
  		);
}

Attribute Functions

The parameters received are the rowCount and the columnCount specified from the user. First the array will be increased or decreased based on the rowCount the user has selected. Next the nested array for the table cells will go throughΒ  a similar logic to increase or decrease the cells in each tablerow.

The parameters received are the row and column indices of where this editable RichTextΒ is in the nested array to update the data content.

updateText
  function updateText(idx, index, text, attributes) {
    var oldArr = [...attributes.content];
    oldArr[index].cells[idx] = text;
    setAttributes({ content: oldArr });
  }
updateArray
  function updateArray(rows, columns, attributes) {
    console.log(rows + " - " + columns);
    var oldArr = [...attributes.content];
    if (rows > oldArr.length) {
      var diff = rows - oldArr.length;
      for (let i = 0; i < parseInt(diff); i++) {
        var cells = [];
        for (let j = 0; j < parseInt(columns, 10); j++) {
          cells.push("");
        }
        oldArr.push({cells});
    }
    } else if (rows < oldArr.length) {
      var diff = oldArr.length - rows;
      oldArr = oldArr.slice(0, -diff);
    }
    for (let i = 0; i < oldArr.length; i++) {
      if (columns > parseInt(oldArr[i].cells.length, 10)) {
        var diff = columns - oldArr[i].cells.length;
        for (let j = 0; j < parseInt(diff); j++) {
          oldArr[i].cells.push("");
        }
      } else if (columns < parseInt(oldArr[i].cells.length, 10)) {
        var diff = oldArr[i].cells.length - columns;
        var newArr = oldArr[i].cells.slice(0, -diff);
        oldArr[i].cells = newArr;
      }
    }
    setAttributes({ rowCount: rows, columnCount: columns, content: oldArr });
    console.log(oldArr);
  }

Editor Right Pane Return

			<InspectorControls>
				<PanelBody title={ __( 'Settings', 'my-custom-blocks-table' ) }>
                    <NumberControl
                      __next40pxDefaultSize
                      spinControls='native'
                      label = { __('Configure row count')}
                      isShiftStepEnabled={ true }
                      onChange={ ( value ) => updateArray(value, attributes.columnCount, attributes) }
                      shiftStep={ 1 }
                      min={1}
                      value={ parseInt(attributes.content.length, 10) }
                    />
                    <NumberControl
                      __next40pxDefaultSize
                      spinControls='native'
                      label = { __('Configure column count')}
                      isShiftStepEnabled={ true }
                      onChange={ ( value ) => updateArray(attributes.rowCount, value, attributes) }
                      shiftStep={ 1 }
                      min={2}
                      value={ attributes.content.length > 0 ? parseInt(attributes.content[0].cells.length, 10) : 2 }
                    />
                    <PanelColorSettings
                        title = { __('Configure Colors')}
                        colorSettings={
                            [
                                {
                                    value: attributes.textColor,
                                    onChange: onChangeTextColor,
                                    label: __('Text Color')
                                },
                                {
                                    value: attributes.evenRowColor,
                                    onChange: (value) => setAttributes({ evenRowColor: value !== undefined ? value : "#219b8b"}),
                                    label: __('Even Row Color')
                                },
                                {
                                    value: attributes.oddRowColor,
                                    onChange: (value) => setAttributes({ oddRowColor: value !== undefined ? value : "#21759b"}),
                                    label: __('Odd Row Color')
                                }
                            ]
                        }
                    />
                </PanelBody>
            </InspectorControls>

Editor UI Return

			<table { ...blockProps }>
						{ content.map((item, index) => {
                  return (
                        <tr key={index} class = { index > 0 ? (index % 2 === 0 ? "even" : "odd") : "empty" }>
							                {item.cells.map((innerContent, idx) => {
                                  return (
                                    <RichText
                                      tagName="td"
                                      value={ innerContent}
                                      allowedFormats={ [ 'core/bold', 'core/italic' ] } 
                                      onChange={ ( value ) => updateText(idx, index, value, attributes) } 
                                      placeholder={ __( '?...' ) } 
                                    />
                                  )
											        })}       
                        </tr>
                    )
						  })
					  }
			</table>

Save.js

We are implementing dynamic rendering in WordPress so this file just needs to return null.

export default function save() {
	return null;
}

Style.scss

Stylesheet that is used on the backend in the editor as well as the frontend for the end user. I wanted to keep both the same.

I had an issue with render.php adding class names to elements. When I wanted "even", it would echo "n". Always the last letter. I have tried many different methods to resolve but ended up just go with it. I have included these different class names to CSS.

.wp-block-r2creations24-my-custom-blocks-table {
	padding: 2px;
  display: table;
  align-items: center;
  background-color: #21759b;
  color: var(--text-color);
  padding: 2px;
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
}

.wp-block-r2creations24-my-custom-blocks-table tr {
  width: 100%;
  overflow-wrap: break-word;
  background-color: rgb(197, 173, 64);
  text-align: start;
}

.wp-block-r2creations24-my-custom-blocks-table tr.odd {
  background-color: var(--odd-table-row-bg-color);
  text-align: end;
}

.wp-block-r2creations24-my-custom-blocks-table tr.even {
  background-color: var(--even-table-row-bg-color);
  text-align: end;
}

.wp-block-r2creations24-my-custom-blocks-table tr.d {
  background-color: var(--odd-table-row-bg-color);
  text-align: end;
}

.wp-block-r2creations24-my-custom-blocks-table tr.n {
  background-color: var(--even-table-row-bg-color);
  text-align: end;
}

.wp-block-r2creations24-my-custom-blocks-table tr td:first-child {
  text-align: center;
}

.wp-block-r2creations24-my-custom-blocks-table tr td {
  padding: 15px 5px 20px 5px;
  min-width: 1px;
}

.wp-block-r2creations24-my-custom-blocks-table tr.empty td {
  padding: 25px 5px 25px 5px;
  min-width: 1px;
}

.wp-block-r2creations24-my-custom-blocks-table tr.y td {
  padding: 25px 5px 25px 5px;
  min-width: 1px;
}

.wp-block-r2creations24-my-custom-blocks-table tr:hover {
  box-shadow: 0px 1px 20px 2px rgba(155, 211, 221, 0.7098039216);
  border: none;
  position: relative;
  transform: scaleX(1.05);
}

Render.php

Dynamically rendering the attributes. Here I also take the attributes for the stylesheet and apply them as CSS variables.

<?php

 $rowCount = $attributes['rowCount'];
 $content = $attributes['content'];
 $textColor = $attributes['textColor'];
 $evenRowColor = $attributes['evenRowColor'];
 $oddRowColor = $attributes['oddRowColor'];

 $style = "color: ".$textColor.";";
 $style .= "--odd-table-row-bg-color: ".$oddRowColor.";";
 $style .= "--even-table-row-bg-color: ".$evenRowColor.";";


?>
<table <?php echo get_block_wrapper_attributes(array("style" => $style)); ?>>
	<?php
		for($i = 0; $i < count($content); $i++){
			?>
			<tr class =<?php echo ( $i > 0 ? ($i % 2 === 0 ? "even" : "odd") : "empty"); ?>>
			<?php

			foreach($content[$i]['cells'] as $item){
	?>
				<td class="example" >
					<?php echo $item; ?>
				</td>
	<?php
			}
			?>
			</tr>
			<?php
		}
	?>
		
	</table>

I hope this was useful.