In this post I am demonstrating how to use nested arrays in WordPress to develop a block plugin.
I will share the issues I had while creating in case they help you along your path.
There are a few things that you need to consider when starting.
First is how you choose to provide the frontend rendering. In the example I will show, I chose to use dynamic rendering utilizing PHP to create the UI.
Then how you want to structure your data attributes that will be saved in the WordPress database during creation of the block plugin. There are a few key parameters that gave me issues when creating the block plugin. Below I will share them and describe the issue. I posted a question when I was troubleshooting one of my issues on the WordPress Stackoverflow site. I eventually stumbled on my own answer and updated the site. If you would like to view it, check out the link below.
Nested Array In WordPress Block Plugin
Troubleshooting Issues
Javascript parseInt()
This is a subtle issue but I figured I would post it as well. The issue starts when the user chooses to have either rowCount or Column count set at 10. When Javascript parses the Int via parseInt function, it will render either 0 or 1. The fix was simple, I needed to add the radix value in the parseInt function. I am using decimal numbers so I need to use 10 as the value.
parseInt(<myValue>);
changed to
parseInt(<myValue>, 10);
Nested Array Attributes
This turned out my biggest issue. I just needed to reread the WordPress Block Plugin Reference Guides.
Using dynamic rendering with my plugin for the frontend, I wanted my attributes saved data to be saved in the WordPress block comment delimiter. You can see this via the Code Editor or straight from your MySQL database. Below is an example of how I wanted my attributes to be saved.
Here I have created a few attributes to save:
This value tracks the size of the top most array and lets the user add / remove arrays in the top array.
This value tracks the size of each nested array allows the user to add / remove items in the nested array.
This is the top most array that contains the nested array labeled as "cells".
<!-- wp:create-block/copyright-date-block {"rowCount":3,"content":[{"cells":["First Array Item 1","First Array Item 2"]},{"cells":["Second Array Item 1","Second Array Item 2"]}]} /-->
To achieve this in dynamic rendering, you just need to omit the source parameter when creating the top most array in the attributes section in the block.json file. I was specify the source parameter as the query parameter indicating the array and it failed to keep the data changes.
Javascript Spread Operator
Its been awhile since I have worked with Javascript so this is something new to me. It is the 3 dots before a variable. The spread operator allows you to make a copy of and variables data/properties to use in another variable or other operation. This reduces syntax needed to create an array from an existing array with similar properties.
In my example, I was creating a reference to the top most array that holds the nested array like the code below shows.
var oldArr = attributes.content;
Instead I needed to use this syntax to get the attribute name content with preexisting data.
var oldArr = [...attributes.content];
Code
This was built off the same template I used in my previous post.
I have not changed the name or descriptions yet. You can update as you see fit.
In order to follow this example nested arrays in WordPress block plugin configuration, you need to open a command prompt and navigate to the plugin directory of the plugin you are building. Then enter in this command to build the necessary files as you update them in a code editor.
npm run start
block.json
block.json is the main configuration file that points the all the necessary configurations. This file will hold all the necessary attribute settings and the block plugin file configurations.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "create-block/copyright-date-block",
"version": "0.1.0",
"title": "Copyright Date Block",
"category": "widgets",
"description": "My new description here...",
"example": {},
"attributes": {
"rowCount": {
"type": "number",
"default": 1
},
"columnCount": {
"type": "number",
"default": 2
},
"content": {
"type": "array",
"default": [{"cells":["",""]}],
"query": {
"cells": {
"type": "array",
"default": [],
"source": "query",
"query": {
"text": {
"type": "string",
"source": "text"
}
}
}
}
}
},
"supports": {
"color": {
"background": true,
"text": true
},
"html": true,
"typography": {
"fontSize": true
}
},
"textdomain": "copyright-date-block",
"editorScript": "file:./index.js",
"render": "file:./render.php",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js"
}
Edit.js
This file is what manipulates the attributes data values and saves the attributes in the WordPress database.Â
Below is a brief description of each of the functions I created to implement the data logic.
This function does what the name implies, it updates the top level array and the nested array sizes. The row parameter is to adjust the top level array. The column parameter is to adjust the nested array.
This function also does what the name implies, it updates the text at the row index and column index.
Also the RichText component at the bottom where the editor UI is built, there is an id parameter to be set. It has an id set depending on the index value. If the index value is an even number, then the id is set to even or odd if the number is odd. This is for visuals while building the block plugin. Each nested array will be surrounded by a different color border. The styling for this logic is in the editor.scss file.
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';
import { InspectorControls, RichText } from '@wordpress/block-editor';
import { PanelBody} from '@wordpress/components';
import { __experimentalNumberControl as NumberControl } from '@wordpress/components';
export default function Edit( { attributes, setAttributes } ) {
const { rowCount, columnCount, content } = attributes;
const blockProps = useBlockProps();
function updateArray(rows, columns, attributes){
console.log(rows + " - " + columns);
var oldArr = [...attributes.content];
if(rows > attributes.rowCount){
var diff = rows - attributes.content.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 < attributes.rowCount) {
var diff = oldArr.length - rows;
oldArr = oldArr.slice(0, -diff);
}
for(let i = 0; i < oldArr.length; i++){
if(columns > parseInt(attributes.columnCount, 10)){
var diff = columns - oldArr[i].cells.length;
for(let j = 0; j < parseInt(diff); j++){
oldArr[i].cells.push("");
}
} else if(columns < attributes.columnCount){
var diff = oldArr[i].cells.length - columns;
var newArr = oldArr[i].cells.slice(0, -diff);
oldArr[i].cells = newArr;
}
}
setAttributes({rowCount : attributes.content.length, columnCount : columns, content : oldArr});
console.log(oldArr);
}
function updateText(idx, index, text, attributes){
var oldArr = [...attributes.content];
oldArr[index].cells[idx] = text;
setAttributes( { content: oldArr });
}
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'copyright-date-block' ) }>
<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 }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
{ content.map((item, index) => {
return (
item.cells.map((innerContent, idx) => {
return (
<RichText
className= { "blah" }
id = { index % 2 === 0 ? "even" : "odd" }
tagName="p"
value={ innerContent}
allowedFormats={ [ 'core/bold', 'core/italic' ] }
onChange={ ( value ) => updateText(idx, index, value, attributes) }
placeholder={ __( '?...' ) }
/>
)
})
)
})
}
</div>
</>
);
}
Render.php
Dynamic rendering is done in this file. This is what is used to build the UI for the frontend. This example is simply created and styled similar to the UI in the block editor.
<?php
$rowCount = $attributes['rowCount'];
$content = $attributes['content'];
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<?php
for($i = 0; $i < count($content); $i++){
foreach($content[$i]['cells'] as $item){
?>
<p class="example" id =<?php echo $i % 2 === 0 ? "even" : "odd";?>>
<?php echo $item; ?>
</p>
<?php
}
}
?>
</div>
Editor.scss
The file used for styling in the editor of the block plugin.
.wp-block-create-block-copyright-date-block {
border: 1px dotted #f00;
}
.wp-block-create-block-copyright-date-block p#odd {
border: 1px solid rgb(59, 56, 255);
}
.wp-block-create-block-copyright-date-block p#even {
border: 1px solid rgb(255, 255, 255);
}
Style.scss
The file used for styling in the frontend of the block plugin.
.wp-block-create-block-copyright-date-block {
background-color: #21759b;
color: #fff;
padding: 2px;
}
.wp-block-create-block-copyright-date-block p#odd {
border: 1px solid rgb(59, 56, 255);
}
.wp-block-create-block-copyright-date-block p#even {
border: 1px solid rgb(255, 255, 255);
}