As shown in the code snippet below, I am trying to append HTML or JSX to RichText content in vain.
const { registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
registerBlockType( /* ... */, {
// ...
attributes: {
content: {
type: 'array',
source: 'children',
selector: 'p',
default: [],
},
},
edit( { className, attributes, setAttributes } ) {
const updateContentWithString = ( content ) => {
setAttributes( { content: content.concat( 'test' ) } );
}
const updateContentWithHTML = ( content ) => {
setAttributes( { content: content.concat( <Button isDefault >Test Button</Button> ) } );
}
return (
<RichText
tagName="p"
className={ className }
value={ attributes.content }
onChange={ updateContentWithString } // updateContentWithString works fine BUT updateContentWithHTML doesn't work at all
/>
);
},
save( { attributes } ) {
return <RichText.Content tagName="p" value={ attributes.content } />;
}
} );
I am wondering why updateContentWithString() works fine BUT updateContentWithHTML() doesn't work at all.
Kindly guide me on this.
Thanks
Try
setAttributes( { content: content.concat( '<Button isDefault >Test Button</Button> )' } );
In JSX <Button/> will be preprocessed/conversed to React.createElement to create component.
Note: In JSX <Button/> differs from <button/> (component vs. html)
UPDATE:
Above was related to react/JSX only.
I don't know or used Gutenberg (yet) but according to this html elements should be passed as objects, not html source. Probably you should use wp.element.createElement or RichText or even define/use own custom block types.
Search for method of editing/updating/creating html (elements) in Gutenberg.
Related
I am creating a very simple iframe. The block works fine when I add this for the first time. If I refresh the page and try to edit the block it shows me the message "This block contains unexpected or invalid content.".
I have inspected the console and I can see:
Content generated by `save` function:
<div class="wp-block-create-block-xenome xenome-align-left text-box-align-left has-very-light-gray-color has-text-color has-background" style="background-color:#000;padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px"><iframe src="" frameborder="0"></iframe></div>
Content retrieved from post body:
<div class="wp-block-create-block-xenome xenome-align-left text-box-align-left has-very-light-gray-color has-text-color has-background" style="background-color:#000;padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px"><iframe src="https://google.com" frameborder="0"></iframe></div>
Note that the src (value) is missing from the save function.
Here is my edit:
/**
* Retrieves the translation of text.
*
* #see https://developer.wordpress.org/block-editor/packages/packages-i18n/
*/
import { __ } from '#wordpress/i18n';
/**
* React hook that is used to mark the block wrapper element.
* It provides all the necessary props like the class name.
*
* #see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
*/
import {
useBlockProps,
RichText,
BlockControls,
InspectorControls,
AlignmentToolbar,
PanelColorSettings,
ContrastChecker,
} from '#wordpress/block-editor';
/**
* Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
* Those files can contain any CSS code that gets applied to the editor.
*
* #see https://www.npmjs.com/package/#wordpress/scripts#using-css
*/
import './editor.scss';
/**
* The edit function describes the structure of your block in the context of the
* editor. This represents what the editor will render when the block is used.
*
* #see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit
*
* #return {WPElement} Element to render.
*/
// export default function Edit() {
// return (
// <p { ...useBlockProps() }>
// { __( 'Xenome – hello from the editor!', 'xenome' ) }
// </p>
// );
// }
import {
// eslint-disable-next-line
__experimentalBoxControl as BoxControl,
PanelBody,
TextControl,
RangeControl,
} from '#wordpress/components';
import classnames from 'classnames';
const { __Visualizer: BoxControlVisualizer } = BoxControl;
export default function Edit( props ) {
const { attributes, setAttributes } = props;
const { text, alignment, style, shadow, shadowOpacity } = attributes;
const onChangeAlignment = ( newAlignment ) => {
setAttributes( { alignment: newAlignment } );
};
const onChangeText = ( newText ) => {
setAttributes( { text: newText } );
};
const onChangeShadowOpacity = ( newShadowOpacity ) => {
setAttributes( { shadowOpacity: newShadowOpacity } );
};
const toggleShadow = () => {
setAttributes( { shadow: ! shadow } );
};
const classes = classnames( `xenome-align-${ alignment }`, {
'has-shadow': shadow,
[ `shadow-opacity-${ shadowOpacity }` ]: shadow && shadowOpacity,
} );
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'xenome' ) } >
<p>Create your campaign at https://xenome.app and paste your share link below.</p>
<TextControl
value={ attributes.text }
onChange={ ( value ) => { setAttributes( {text: value } ) } }
placeholder={ __( 'https://xenome.app/s/2df46475-588f-404f-a50c-ec1b1293dd51/start', 'xenome' ) }
/>
</PanelBody>
{ shadow && (
<PanelBody title={ __( 'Shadow Setting', 'xenome' ) }>
<RangeControl
label={ __( 'Shadow Opacity', 'xenome' ) }
value={ shadowOpacity }
min={ 10 }
max={ 40 }
step={ 10 }
onChange={ onChangeShadowOpacity }
/>
</PanelBody>
) }
</InspectorControls>
<BlockControls
controls={ [
{
icon: 'admin-page',
title: __( 'Shadow', 'xenome' ),
onClick: toggleShadow,
isActive: shadow,
},
] }
>
</BlockControls>
<div
{ ...useBlockProps( {
className: classes,
} ) }
>
<div class="over-controls">
<p>
<svg width="20" height="20" class="custom-icon custom-icon-github" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" aria-hidden="true" focusable="false"><g id="Group_8654" data-name="Group 8654" transform="translate(3.999 -10.085)"><path id="Rectangle_4113" data-name="Rectangle 4113" d="M0,0H36a0,0,0,0,1,0,0V36a0,0,0,0,1,0,0H15A15,15,0,0,1,0,21V0A0,0,0,0,1,0,0Z" transform="translate(-3.999 10.085)" fill="#fff"></path><path id="Path_66" data-name="Path 66" d="M14.7-2.663H10.863L.378-18.009H4.216ZM7.414-12.119l3.655-5.89h3.495L9.1-9.749Zm.64,2.853L3.9-2.663H.469l5.8-8.7Z" transform="translate(12.109 42.543)" fill="#010911"></path></g></svg>
Create your campaign at https://xenome.app and paste your share link below.</p>
<TextControl
value={ attributes.text }
onChange={ onChangeText }
placeholder={ __( 'https://xenome.app/s/2df46475-588f-404f-a50c-ec1b1293dd51/start', 'xenome' ) }
/>
</div>
<BoxControlVisualizer
values={ style && style.spacing && style.spacing.padding }
showValues={
style && style.visualizers && style.visualizers.padding
}
/>
<iframe
src={ attributes.text }
frameBorder="0"
></iframe>
</div>
</>
);
}
And here is my save:
/**
* Retrieves the translation of text.
*
* #see https://developer.wordpress.org/block-editor/packages/packages-i18n/
*/
import { __ } from '#wordpress/i18n';
/**
* React hook that is used to mark the block wrapper element.
* It provides all the necessary props like the class name.
*
* #see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
*/
import { useBlockProps, RichText } from '#wordpress/block-editor';
import classnames from 'classnames';
/**
* The save function defines the way in which the different attributes should
* be combined into the final markup, which is then serialized by the block
* editor into `post_content`.
*
* #see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save
*
* #return {WPElement} Element to render.
*/
export default function save( { attributes } ) {
const { text, alignment, shadow, shadowOpacity } = attributes;
const classes = classnames( `xenome-align-${ alignment }`, {
'has-shadow': shadow,
[ `shadow-opacity-${ shadowOpacity }` ]: shadow && shadowOpacity,
} );
return (
<div
{ ...useBlockProps.save( {
className: classes,
} ) }
>
<iframe src={ attributes.text } frameBorder="0"></iframe>
</div>
);
}
If anyone can help that would be amazing.
Similar to: WordPress Gutenberg: This block contains unexpected or invalid content
A likely cause for the save() output not matching the post body is how the attribute "text" is defined. Assuming you have used create block, in your block.json, check that the source for "text" retrieves the saved value from the src attribute of the <iframe>, eg:
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
...
"attributes": {
"text": {
"source": "attribute",
"selector": "iframe",
"attribute": "src",
"default": "https://google.com"
}
},
...
}
As the title states I am teaching myself how to create a custom Gutenburg Block for wordpress development and I have written the following code. It functions correctly when you save, but when you reload the saved page you get console errors and it shows
This block contains unexpected or invalid content.
When you click resolve it shows the following:
// Import CSS.
import './editor.scss';
import './style.scss';
const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks
const { RichText } = wp.blockEditor
const { withColors } = wp.blockEditor
const { PanelColorSettings} = wp.blockEditor;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;
registerBlockType( 'cgb/block-my-block', {
title: __( 'my-block - CGB Block' ),
icon: 'shield',
category: 'common',
keywords: [
__( 'my-block — CGB Block' ),
__( 'CGB Example' ),
__( 'create-guten-block' ),
],
attributes: {
align: {
type: 'string',
default: 'full',
},
link_text: {
selector: 'a',
source: 'children',
},
link_url: {
selector: 'a',
source: 'attribute',
attribute: 'href',
},
txtColor: {
type: 'string'
},
bgcolor: {
type: 'string'
},
},
supports: {
align:true,
//align: ['wide','full'], // limit only to these
},
getEditWrapperProps() {
return {
'data-align': 'full',
};
},
edit: ( props ) => {
let link_text = props.attributes.link_text
let link_url = props.attributes.link_url
let txtColor = props.attributes.txtColor
let bgColor = props.attributes.bgColor
function onChangeContentURL ( content ) {
props.setAttributes({link_url: content})
}
function onChangeContentName ( content ) {
props.setAttributes({link_text: content})
}
function onChangeBGColor ( content ) {
props.setAttributes({bgColor: content})
}
function onChangeColor ( content ) {
props.setAttributes({txtColor: content})
}
return (
<div className={ props.className } style={{ backgroundColor:bgColor, color: txtColor }}>
<InspectorControls key= { 'inspector' } >
<PanelBody>
<PanelColorSettings
title={ __('Title Color', 'tar') }
colorSettings= { [
{
value: txtColor,
onChange: (colorValue) => onChangeColor ( colorValue ),
label: __('Color', 'tar'),
},
] }
/>
<PanelColorSettings
title={ __('Background Color', 'tar') }
colorSettings= { [
{
value: bgColor,
onChange: (colorValue) => onChangeBGColor ( colorValue ),
label: __('Color', 'tar'),
},
] }
/>
</PanelBody>
</InspectorControls>
<p>Sample Link Block</p>
<label>Name:</label>
<RichText
className={props.className} // Automatic class: gutenberg-blocks-sample-block-editable
onChange={onChangeContentName} // onChange event callback
value={link_text} // Binding
placeholder="Name of the link"
/>
<label>URL:</label>
<RichText
format="string" // Default is 'element'. Wouldn't work for a tag attribute
className={props.className} // Automatic class: gutenberg-blocks-sample-block-editable
onChange={onChangeContentURL} // onChange event callback
value={link_url} // Binding
placeholder="URL of the link"
/>
<p>— Hello from the backend.!!</p>
</div>
);
},
/**
* The save function defines the way in which the different attributes should be combined
* into the final markup, which is then serialized by Gutenberg into post_content.
*
* The "save" property must be specified and must be a valid function.
*
* #link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
*
* #param {Object} props Props.
* #returns {Mixed} JSX Frontend HTML.
*/
save: ( props ) => {
let txtColor = props.attributes.txtColor
let bgColor = props.attributes.bgColor
return (
<div className={ props.className } style={{ backgroundColor:bgColor, color:txtColor }} >
<p>— Hello from the frontend.</p>
<a href={props.attributes.link_url}>{props.attributes.link_text}</a>
</div>
);
},
} );
The console error looks like the POST and the SAVE data are different causing the error.
The message is:
Block validation: Block validation failed for `cgb/block-my-block` ({name: "cgb/block-my-block", icon: {…}, attributes: {…}, keywords: Array(3), save: ƒ, …}).
Content generated by `save` function:
<div class="wp-block-cgb-block-my-block alignfull" style="color:#000000"><p>— Hello from the frontend.</p><a></a></div>
Content retrieved from post body:
<div class="wp-block-cgb-block-my-block alignfull" style="background-color:#cd2653;color:#000000"><p>— Hello from the frontend.</p><a></a></div>
So it looks to me as if the problem is with the Save function style tag's on the root element.
<div className={ props.className } style={{ backgroundColor:bgColor, color:txtColor }} >
I have removed one style only leaving the other and it works. Putting the other back in and it breaks. Have I included this multiple style wrong? If so what is the convention for adding multiple styles that save on the root element? Also I am new and I am learning from tutorials and reading the Gutenburg github docs. If there is something rudimentary I am doing wrong please let me know.
The block validation issue is caused by a small typo where your attribute bgcolor (case sensitive) is called as bgColor in edit() and save().
Your code shows you are on the right path with creating your own custom Gutenberg block, so I'd like to share a suggestion to use array destructuring with props to make your code much easier to read and maintain. Your custom onChange functions which just call setAttributes()can also be removed in favor of calling setAttributes directly, this reduces how much code you need to write and reduces the chance of typos too..
Eg:
// Import CSS.
import './editor.scss';
import './style.scss';
const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks
const { RichText } = wp.blockEditor
const { withColors } = wp.blockEditor
const { PanelColorSettings } = wp.blockEditor;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;
registerBlockType('cgb/block-my-block', {
title: __('my-block - CGB Block'),
icon: 'shield',
category: 'common',
keywords: [
__('my-block — CGB Block'),
__('CGB Example'),
__('create-guten-block'),
],
attributes: {
align: {
type: 'string',
default: 'full',
},
link_text: {
selector: 'a',
source: 'children',
},
link_url: {
selector: 'a',
source: 'attribute',
attribute: 'href',
},
txtColor: {
type: 'string',
},
bgColor: {
type: 'string',
},
},
supports: {
align: true,
//align: ['wide','full'], // limit only to these
},
getEditWrapperProps() {
return {
'data-align': 'full',
};
},
// Use array destructuring of props
edit: ({ attributes, className, setAttributes } = props) => {
// Use array destructuring of the attributes
const { link_text, link_url, txtColor, bgColor } = attributes;
// Removed custom onChange functions that just call setAttributes
return (
<div className={className} style={{ backgroundColor: bgColor, color: txtColor }}>
<InspectorControls key={'inspector'} >
<PanelBody>
<PanelColorSettings
title={__('Title Color', 'tar')}
colorSettings={[
{
value: txtColor,
onChange: (colorValue) => setAttributes({ txtColor: colorValue }),
label: __('Color', 'tar'),
},
]}
/>
<PanelColorSettings
title={__('Background Color', 'tar')}
colorSettings={[
{
value: bgColor,
onChange: (colorValue) => setAttributes({ bgColor: colorValue }),
label: __('Color', 'tar'),
},
]}
/>
</PanelBody>
</InspectorControls>
<p>Sample Link Block</p>
<label>Name:</label>
<RichText
className={className} // Automatic class: gutenberg-blocks-sample-block-editable
onChange={(content) => setAttributes({ link_text: content })} // onChange event callback
value={link_text} // Binding
placeholder="Name of the link"
/>
<label>URL:</label>
<RichText
format="string" // Default is 'element'. Wouldn't work for a tag attribute
className={className} // Automatic class: gutenberg-blocks-sample-block-editable
onChange={(content) => setAttributes({ link_url: content })} // onChange event callback
value={link_url} // Binding
placeholder="URL of the link"
/>
<p>— Hello from the backend.!!</p>
</div>
);
},
/**
* The save function defines the way in which the different attributes should be combined
* into the final markup, which is then serialized by Gutenberg into post_content.
*
* The "save" property must be specified and must be a valid function.
*
* #link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
*
* #param {Object} props Props.
* #returns {Mixed} JSX Frontend HTML.
*/
// Use array destructuring of props
save: ({ attributes, className } = props) => {
// Use array destructuring of the attributes
const { txtColor, bgColor, link_url, link_text } = attributes;
return (
<div className={className} style={{ backgroundColor: bgColor, color: txtColor }}>
<p>— Hello from the frontend.</p>
<a href={link_url}>{link_text}</a>
</div >
);
},
});
I'm currently building a custom Hero block. I've added a ToggleControl to hide or show content within this block. This does work on the edit section in gutenberg block, it also needs to add a class to the wrapper. This also works on the edit section.
The strange thing is, it does not work on the save section of the block. The code I use to set the class is the one below:
It does work like expected in the edit section
edit: function( props ) {
const { attributes, className } = props;
const wrapperStyle = {
backgroundColor: attributes.backgroundColor,
backgroundImage: attributes.backgroundImage && 'url(' + attributes.backgroundImage + ')',
};
const classes = classnames(
className,
dimRatioToClass( attributes.backgroundOpacity ),
{
'has-background-dim': attributes.backgroundOpacity !== 0,
},
attributes.position,
{ [ `align${ attributes.align }` ]: attributes.align && attributes.fullWidthBackground },
{ [ `has-${ attributes.includeContent ? 'content' : 'no-content' }` ] : attributes.includeContent },
);
return (
<Fragment>
<Inspector { ...props } />
<div style={ wrapperStyle } className={ classes }>
<div className="wrapper-inner">
<div className="wrapper-inner-blocks">
{ attributes.includeContent === true &&
<InnerBlocks />
}
</div>
</div>
</div>
</Fragment>
);
},
But on the save section the style is not applied and the conditional tag is not working. See the code below.
Am I missing something?
save: function( props ) {
const { attributes, className } = props;
const wrapperStyle = {
backgroundColor: attributes.backgroundColor,
backgroundImage: attributes.backgroundImage && 'url(' + attributes.backgroundImage + ')',
};
const classes = classnames(
className,
{ [ `has-${ attributes.includeContent ? 'content' : 'no-content' }` ] : attributes.includeContent },
{ [ `align${ attributes.align }` ]: attributes.align && attributes.fullWidthBackground },
dimRatioToClass( attributes.backgroundOpacity ),
{
'has-background-dim': attributes.backgroundOpacity !== 0,
},
attributes.position,
);
return (
<div style={ wrapperStyle } className={ classes }>
<div className="wrapper-inner">
<div className="wrapper-inner-blocks">
{ attributes.includeContent === true &&
<InnerBlocks.Content />
}
</div>
</div>
</div>
);
}
} );
I can not comment, so, I have to post this an answer instead :/
I'm guessing the attributes.includeContent prop is the ToggleControl is question, yes?
Is it set as a Boolean in the attributes?
What do you get when you console.log it in the save? What about when you console.log its type in save? I'm just wondering if it is ending up as a string which would eval to true.
I am trying to add a padding Inspector Control to all core block of the new Gutenberg Wordpress Editor. I have created the Control on the Editor and now I am trying to apply this style to the block element its self.
But I keep getting This block contains unexpected or invalid content. Error on the blocks. Can anyone help me out here on what exactly I am not doing?
var paddingEditor = wp.compose.createHigherOrderComponent(function(
BlockEdit
) {
return function(props) {
var padding = props.attributes.padding || 0;
handleChange = name => newValue => {
if (props) {
props.setAttributes({ [name]: newValue });
}
};
return el(
Fragment,
{},
el(BlockEdit, props),
el(
editor.InspectorControls,
{},
el(
components.PanelBody,
{
title: "Padding",
className: "",
initialOpen: false
},
el("p", {}, "Padding"),
el(components.TextControl, {
value: padding,
onChange: this.handleChange("padding")
})
)
)
);
};
},
"paddingStyle");
wp.hooks.addFilter(
"editor.BlockEdit",
"my-plugin/padding-style",
paddingStyle
);
function AddPaddingAttribute(element, blockType, attributes) {
Object.assign(blockType.attributes, {
padding: {
type: "string"
}
});
return element;
}
wp.hooks.addFilter(
"blocks.getSaveElement",
"my-plugin/add-padding-attr",
AddPaddingAttribute
);
function AddPaddingStyle(props, blockType, attributes) {
if (attributes.padding) {
props.style = lodash.assign(props.style, { padding: attributes.padding });
}
return props;
}
wp.hooks.addFilter(
"blocks.getSaveContent.extraProps",
"my-plugin/add-background-color-style",
AddPaddingStyle
);
PHP
function register_block_extensions(){
wp_register_script(
'extend-blocks', // Handle.
get_template_directory_uri() . '/js/extend-blocks.js',
array( 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-editor' )
);
wp_enqueue_script( 'extend-blocks' );
}
add_action('enqueue_block_editor_assets', 'register_block_extensions');
Your editor.BlockEdit looks right, but it's hard for me to parse the older syntax. Assuming it is correct, you'll need to replace blocks.getSaveElement with this:
function AddPaddingAttribute(props) {
if (props.attributes) { // Some modules don't have attributes
props.attributes = Object.assign(
props.attributes,
{
padding: {}
}
);
}
return props;
}
wp.hooks.addFilter(
'blocks.registerBlockType',
'my-plugin/add-padding-attr',
AddPaddingAttribute
);
And modify blocks.getSaveContent.extraProps to this:
function AddPaddingStyle(props, blockType, attributes) {
return Object.assign(
props,
{
style: {
padding: attributes.padding
}
}
);
}
wp.hooks.addFilter(
"blocks.getSaveContent.extraProps",
"my-plugin/add-background-color-style",
AddPaddingStyle
);
I have a block that renders titles of all existing posts in the <Modal/>. I retrieve it from the server using <ServerSideRender/> (that returns plain html). I want to be able to choose one of the titles (ideally save it in the postTitle attribute) and display it in the block.
attributes: {
postTitle: {
type: 'string'
}
},
edit(props) {
const { attributes } = props;
const { postTitle } = props.attributes;
const MyModal = withState( {
isOpen: false,
} )
( ( { isOpen, setState } ) => (
<div>
<Button isDefault onClick={ () => setState( { isOpen: true } ) }>Choose</Button>
{ isOpen ?
<Modal onRequestClose={ () => setState( { isOpen: false } ) }>
<ServerSideRender
block="my-blocks/wordpress-title"
attributes={attributes}
/>
</Modal>
: null }
</div>
) );
return ([
<InspectorControls>
<div>
<strong>Choose Wordpress title:</strong>
<MyModal/>
</div>
</InspectorControls>,
]);
},
Is there any sensible way to retrieve data from a server, so it was possible to operate on it within a block?