Ricardo
Ricardo

Reputation: 106

Gutenberg Custom Block: Add elements by innerBlocks length

Context: I'm working on a Custom Gutenberg plugin. One of the blocks I'm trying to create is a Carousel (using Bootstrap's Carousel).

I'm trying to optional add indicators based on the number of child blocks. I'm getting the error: Block validation: Block validation failed

It definitely has something to do with me adding in the indicators. Here's my block. Can anyone help?

/**
 * BLOCK: Laboratory Blocks Carousel
 */
import classNames from 'classnames';
import { Fragment } from 'react';
import CarouselOptions, {
  CarouselOptionAttributes,
} from './options';
import heightClass from './classes';

const { registerBlockType } = wp.blocks;
const { InspectorControls, InnerBlocks } = wp.editor;
const { select, dispatch } = wp.data;
const { __ } = wp.i18n;
const ALLOWED_BLOCKS = [];

registerBlockType('laboratory-blocks/carousel', {
  title: __('LB Carousel'),
  icon: 'slides',
  category: 'laboratory-blocks',
  description: __('A Bootstrap Carousel'),
  supports: { anchor: true },
  keywords: [
    __('Carousel'),
    __('Slider'),
  ],
  attributes: {
    ...CarouselOptionAttributes,
    test: {
      type: 'object',
    },
    clientId: {
      type: 'string',
      default: 'laboratory-carousel',
    },
  },

  edit(props) {
    const { setAttributes, isSelected } = props;
    const { clientId } = props;
    // find innerBlocks, add "active" to the first slide, set attribute slideLength
    const children = select('core/editor').getBlocksByClientId(clientId)[0].innerBlocks;
    const firstChild = children[0] || false;
    if (firstChild) {
      dispatch('core/editor').updateBlockAttributes(firstChild.clientId, { className: 'active' });
      setAttributes({ clientId });
    }

    return [
      !!isSelected && (
        <InspectorControls key="inspector">
          <CarouselOptions {...props} />
        </InspectorControls>
      ),
      <Fragment>
        <p>Laboratory Blocks Carousel:&nbsp;
          <small>Include a unique ID under &quot;Carousel Options&quot;</small>
        </p>
        <hr />
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} />
      </Fragment>,
    ];
  },

  save(props) {
    const { className, attributes, innerBlocks } = props;
    const {
      carouselHeight,
      hasControls,
      hasIndicators,
      interval,
      pauseOnHover,
      carouselId,
    } = attributes;
    const height = heightClass(attributes);
    const classes = classNames(className, 'carousel', 'slide', height);
    const styles = carouselHeight ? { height: carouselHeight } : {};
    const timing = interval || false;
    const hover = pauseOnHover ? 'hover' : false;

    const containerTags = {
      ID: carouselId,
      className: classes,
      style: styles,
      'data-ride': 'carousel',
      'data-interval': timing,
      'data-pause': hover,
    };

    let Indicators;
    if (innerBlocks && 0 < innerBlocks.length) {
      Indicators = innerBlocks.map((block, i) => {
        const c = (0 === i) ? 'active' : '';
        return (
          <li
            key={`${carouselId}-trigger-${block.clientId}`}
            data-target={`#${carouselId}`}
            data-slide-to={i}
            className={c}
          />
        );
      });
    }

    return (
      <div {...containerTags}>
        <InnerBlocks.Content />
        {
          hasControls && (
            <Fragment>
              <a claclassName="carousel-control-prev" href={`#${carouselId}`} role="button" data-slide="prev">
                <span className="carousel-control-prev-icon" aria-hidden="true" />
                <span className="sr-only">Previous</span>
              </a>
              <a claclassName="carousel-control-prev" href={`#${carouselId}`} role="button" data-slide="next">
                <span className="carousel-control-next-icon" aria-hidden="true" />
                <span className="sr-only">Next</span>
              </a>
            </Fragment>
          )
        }
        {
          hasIndicators && (
            <ol className="carousel-indicators">
              {Indicators}
            </ol>
          )
        }
      </div>
    );
  },
});

Upvotes: 3

Views: 3373

Answers (1)

Ricardo
Ricardo

Reputation: 106

My solution:

  1. Move the indicators (the dots of the carousel) into a seperate component for code-reuse.
  2. Used it in both the edit and save methods.
  3. Saved the number of innerBlocks in the edit function and set it as an attribute to be used in the save function.

```

registerBlockType('laboratory-blocks/carousel', {...},
    edit: {
      const { clientId } = props;

      ...

      // find number of children and set it as an attribute
      const innerCount = select('core/editor').getBlocksByClientId(clientId)[0].innerBlocks.length;
      setAttributes({ clientId, innerCount });

      return [
        <div>
          ...
          <Indicators count={attributes.innerCount} clientId={clientId} />
        </div>
      ];
    }
    save: {
      const { clientId, attributes } = props;
      const { innerCount } = attributes;
      ...

      return [
        <div>
          ...
          <Indicators count={innerCount} clientId={clientId} />
        </div>
      ];
    }
}

```

Upvotes: 3

Related Questions