Gutenberg removing table tag on save

513 Views Asked by At

I am attempting to create a Gutenberg block to track exercise sets. I'm using a RichText component to allow users to edit default values in a table I pre-populate for them.

The block works well on the editor and, after saving, renders correctly in the post. However, when I reload the editor, I receive this error message: Block validation: Expected tag name 'thead', instead saw 'table'. It's almost like Gutenberg is stripping the table tag but leaving everything else.

Of course, that doesn't make sense, but I'm not sure what else it could be.

Here's my code, heavily edited for readability:

const { registerBlockType } = wp.blocks;
const { AlignmentToolbar, BlockAlignmentToolbar, BlockControls, RichText, useInnerBlockProps } = wp.blockEditor;
const { Component } = wp.element;

registerBlockType('bsd-strong-post/training-session', {
  title: __('Strong Post', 'bsd-strong-post'),
  description: __('Provides a short summary of a training session', 'bsd-strong-post'),
  category: 'common',
  icon: blockIcons.weight_lifting,
  keywords: [    
    __('strength workout', 'bsd-strong-post'),
    __('strong', 'bsd-strong-post'),
    __('training', 'bsd-strong-post')
  ],
  supports: {
    html: true
  },
  attributes: {
    /* ... */,
    dayTemplateContent: {
      type: 'string',
      source: 'html',
      selector: '.bsd-strong-post-training-template'
    },
    /* ... */
  },
  /* ... */
  edit: class extends Component {
    constructor(props) {
      super(...arguments);
      this.props = props;

      /* ... */

      this.dayTemplateHandler = this.dayTemplateHandler.bind(this);
      this.onChangeBlockTemplate = this.onChangeBlockTemplate.bind(this);
    }

    /* ... */

    dayTemplateHandler(new_val) { 
      const dayTemplateList = this.state.dayTemplateList;
      
      let selectedDayTemplate = dayTemplateList.filter(item => {
        return item.value == new_val;
      })

      if (selectedDayTemplate[0]['label']) {
        this.props.setAttributes({ 
          dayTemplateId: new_val,
          dayTemplateName: selectedDayTemplate[0]['label']
        });
      }

      this.getTemplate(new_val);
    }

    getTemplate(templateId) {
      api.getDayTemplate(templateId)
      .then((data) => {
        
        if (!data.status || data.status == 0) {
          return false;
        };

        if (!data.day_template) {
          return false;
        };

        this.props.setAttributes({ 
          dayTemplateContent: data.day_template.template_content          
        });

        return data.day_template;
      }).catch((err) => {
        console.log('getTemplate caught error')
        return false;
      });
    }

    onChangeBlockTemplate(value) {
      this.props.setAttributes({
        dayTemplateContent: value
      });
    }

    /* ... */

    render() {
      const { dayTemplateHandler, onChangeBlockTemplate, phaseControlHandler, programControlHandler, updateBlockAlignment, updateTextAlignment } = this;
      const { block_alignment, dayTemplateId, dayTemplateName, dayTemplateContent, phaseId, phaseName, programAuthor, programId, programName, programPhases, text_alignment } = this.props.attributes;

      /* ... */

      return [
        <InspectorControls>
          <PanelBody title={ __('Basics', 'bsd-strong-post') }>
              <SelectControl
                label={ __('Day', 'bsd-strong-post') }
                help={ __('The training session (e.g., Day One)', 'bsd-strong-post') }
                value={ dayTemplateId }
                options={ this.state.phaseTemplates }
                onChange={ dayTemplateHandler }
              />  
            }
          </PanelBody>
        </InspectorControls>,
        <div className='bsd-strong-post-block-editor'>
          <div className={ this.props.className }>
            <RichText
              placeholder={ __('Log your lifts here') }
              value={ dayTemplateContent }
              multiline={ false }
              onChange={ onChangeBlockTemplate }
              className='bsd-strong-post-training-log'
            />
          </div>
        </div>
      ];
    } 
  },

  save: (props) => {
    return (
      <div className={ `align${props.attributes.block_alignment}` }>
        <ul className='list-unstyled'style={{ textAlign: props.attributes.text_alignment }}>
          <li>
            <strong>{ __('Program', 'bsd-strong-post') }: </strong> 
            <span className='bsd-strong-post-program'>{ props.attributes.programName }</span>
          </li>
          <li>
            <strong>{ __('Phase', 'bsd-strong-post') }: </strong> 
            <span className='bsd-strong-post-phase-ph'>{ props.attributes.phaseName }</span>
          </li>
          <li>
            <strong>{ __('Day', 'bsd-strong-post') }: </strong> 
            <span className='bsd-strong-post-day-ph'>{ props.attributes.dayTemplateName }</span>
          </li>
          <li>
            <strong>{ __('Author', 'bsd-strong-post') }: </strong> 
            <span className='bsd-strong-post-author-ph'>{ props.attributes.programAuthor }</span>
          </li>
        </ul>        
        <RichText.Content
          value={ props.attributes.dayTemplateContent }
          className='bsd-strong-post-training-log'            
        />        
      </div>
    )
  }
});

Here's the console output on reload:

Content generated by 'save' function:

<div class="wp-block-bsd-strong-post-training-session alignwide"><ul class="list-unstyled"><li><strong>Program: </strong><span class="bsd-strong-post-program">Madcow</span></li><li><strong>Phase: </strong><span class="bsd-strong-post-phase-ph">Intermediate</span></li><li><strong>Day: </strong><span class="bsd-strong-post-day-ph">Day 1</span></li><li><strong>Author: </strong><span class="bsd-strong-post-author-ph">Madcow</span></li></ul>        
        <thead>
            <tr>
                <th scope="col">Exercise</th>
                <th scope="col">Set 1</th>
                <th scope="col">Set 2</th>
            </tr>
        </thead>
        <tbody>
            <tr class="bsd-strong-post-exercise-one">
                <td class="bsd-strong-post-exercise-name">Squat</td>
                <td class="bsd-strong-post-set-1">95 x 5</td>
                <td class="bsd-strong-post-set-2">135 x 5</td>
            </tr>
        </tbody>
    </div>

Content retrieved from post body:

<div class="wp-block-bsd-strong-post-training-session alignwide"><ul class="list-unstyled"><li><strong>Program: </strong><span class="bsd-strong-post-program">Madcow</span></li><li><strong>Phase: </strong><span class="bsd-strong-post-phase-ph">Intermediate</span></li><li><strong>Day: </strong><span class="bsd-strong-post-day-ph">Day 1</span></li><li><strong>Author: </strong><span class="bsd-strong-post-author-ph">Madcow</span></li></ul><table class='bsd-strong-post-training-template'>       
        <thead>
            <tr>
                <th scope='col'>Exercise</th>
                <th scope='col'>Set 1</th>
                <th scope='col'>Set 2</th>
            </tr>
        </thead>
        <tbody>
            <tr class='bsd-strong-post-exercise-one'>
                <td class='bsd-strong-post-exercise-name'>Squat</td>
                <td class='bsd-strong-post-set-1'>95 x 5</td>
                <td class='bsd-strong-post-set-2'>135 x 5</td>
            </tr>
        </tbody>
    </table></div>

I can see that the content displayed below Content generated by 'save' function: is missing the <table> and </table> tags. I've tried to work around this by adding tagName='table' in the RichText.Content properties inside the save function, but then the console shows duplicate <table> and </table> tags.

EDIT: The table is populated when a user makes a change to the Select control in InspectorControls. This action calls dayTemplateHandler, which among other things, calls getTemplate, a function that gets the content of the table from the database. Here's an example of that output (data.day_template.template_content):

<table class='bsd-strong-post-training-template'>       
        <thead>
            <tr>
                <th scope='col'>Exercise</th>
                <th scope='col'>Set 1</th>
                <th scope='col'>Set 2</th>
            </tr>
        </thead>
        <tbody>
            <tr class='bsd-strong-post-exercise-one'>
                <td class='bsd-strong-post-exercise-name'>Squat</td>
                <td class='bsd-strong-post-set-1'>95 x 5</td>
                <td class='bsd-strong-post-set-2'>135 x 5</td>
            </tr>
        </tbody>
    </table>
1

There are 1 best solutions below

1
On BEST ANSWER

On reviewing the table template and considering the error, I suspect the issue is the selector of the dayTemplateContent attribute, .bsd-strong-post-training-template

The first time the content is saved, it successfully loads the template data from database and saves the complete table structure. When the content is reloaded, the block validator fails as the selector of dayTemplateContent reads in the child nodes of the table's css selector (which is thead) and doesn't match expected content. Ref: HTML example of blockquote/paragraphs

Try wrapping the <table> template with a <div class="bsd-strong-post-training-template"> or changing the selector.