Steve
Forum Replies Created
-
Unfortunately, there’s a problem with this solution.
get_the_content() doesn’t work in all circumstances.
I’m using a 3rd party element and this function just returns the content for the page of course and not the element.So it would be much better to access the attributes for the nested blocks directly in PHP rather than extracting from the content.
Just to recap, I can access the top level attributes as follows:
function render_html($attributes, $content, $post) { ob_start(); $SomeAttribute = $attributes['SomeAttribute'];At the risk of repeating my question, does anyone know how to access the attributes for nested blocks?
Best Regards,
Steve
Forum: Developing with WordPress
In reply to: useBlockProps seems to cause an errorHi Adam,
Thank you so much for taking a look at this.
Sure, I put the edit.js below.
Steve
import { useEffect } from '@wordpress/element'; import { RichText, useBlockProps, InnerBlocks, InspectorControls, PanelColorSettings, //ContrastChecker, withColors, } from '@wordpress/block-editor'; import { Panel, PanelBody, PanelRow, RangeControl, RadioControl, ToggleControl, TextControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import './editor.scss'; function Edit(props) { const { attributes, setAttributes, backgroundColor, //textColor, setBackgroundColor, //setTextColor, } = props; const { ColumnsXS, ColumnsSM, ColumnsMD, ColumnsLG, ColumnsXL, ColumnsXXL, mainTitle, mainTitleAlignment, EnableSocialMediaButtons, DefaultButtonTextValue, TitlePosition, UniqueID, LayoutStyle, //BelowImageCardTitleClass, //AboveImageCardTitleClass, } = attributes; //Generate a value for the UniqueID if not set const onChangeID = (newID) => { setAttributes({ UniqueID: newID }); }; const onChangeColumnsXS = (newColumns) => { setAttributes({ ColumnsXS: newColumns }); }; const onChangeColumnsSM = (newColumns) => { setAttributes({ ColumnsSM: newColumns }); }; const onChangeColumnsMD = (newColumns) => { setAttributes({ ColumnsMD: newColumns }); }; const onChangeColumnsLG = (newColumns) => { setAttributes({ ColumnsLG: newColumns }); }; const onChangeColumnsXL = (newColumns) => { setAttributes({ ColumnsXL: newColumns }); }; const onChangeColumnsXXL = (newColumns) => { setAttributes({ ColumnsXXL: newColumns }); }; const onChangeMainTitle = (value) => { setAttributes({ mainTitle: value }); }; const onChangeTitleAlignment = (value) => { setAttributes({ mainTitleAlignment: value }); }; const onChangeLayoutStyle = (value) => { setAttributes({ LayoutStyle: value }); }; const onChangedDefaultButtonText = (value) => { setAttributes({ DefaultButtonTextValue: value }); }; const onChangeTitlePosition = (value) => { setAttributes({ TitlePosition: value }); //(value); }; useEffect(() => { if (TitlePosition === 'above-image') { setAttributes({ AboveImageCardTitleClass: 'title-above-image-displayed', }); setAttributes({ BelowImageCardTitleClass: 'title-below-image-not-displayed', }); } else { setAttributes({ AboveImageCardTitleClass: 'title-above-image-not-displayed', }); setAttributes({ BelowImageCardTitleClass: 'title-below-image-displayed', }); } }, [TitlePosition]); //useEffect(() => { //return; //document.getElementById {UniqueID} //const SetTitlePosition = (value) => { // if (TitlePosition === 'above-image') { // //Display the title above the image // const a = document.getElementsByClassName( // <code>title-above-image-not-displayed ${UniqueID}</code> // ); // [...a].forEach( // (x) => (x.className += ' title-above-image-displayed ') // ); // [...a].forEach((x) => // x.classList.remove('title-above-image-not-displayed') // ); // //Hide the title below the image // const b = document.getElementsByClassName( // <code>title-below-image-displayed ${UniqueID}</code> // ); // [...b].forEach( // (x) => (x.className += ' title-below-image-not-displayed') // ); // [...b].forEach((x) => // x.classList.remove('title-below-image-displayed') // ); // } else { // //************************************************************************************************************** // //Find every instance of the class which disables the display of the social media buttons and replace it with // //the class to enable the display. // const a = document.getElementsByClassName( // <code>title-above-image-displayed ${UniqueID}</code> // ); // [...a].forEach( // (x) => (x.className += ' title-above-image-not-displayed') // ); // [...a].forEach((x) => // x.classList.remove('title-above-image-displayed') // ); // const b = document.getElementsByClassName( // <code>title-below-image-not-displayed ${UniqueID}</code> // ); // [...b].forEach( // (x) => (x.className += ' title-below-image-displayed') // ); // [...b].forEach((x) => // x.classList.remove('title-below-image-not-displayed') // ); // //************************************************************************************************************** // } // //}; // }, [TitlePosition, LayoutStyle]); const onChangeEnableSocialMediaButtons = (value) => { setAttributes({ EnableSocialMediaButtons: value }); }; //If the social media buttons are enabled, search through each card and display the social media buttons for each. useEffect(() => { if (EnableSocialMediaButtons === true) { //Find every instance of the class which disables the display of the social media buttons and replace it with //the class to enable the display. const a = document.getElementsByClassName( 'social-media-links-disabled' ); [...a].forEach( (x) => (x.className += ' social-media-links-enabled') ); [...a].forEach((x) => x.classList.remove('social-media-links-disabled') ); } else { //************************************************************************************************************** //Find every instance of the class which disables the display of the social media buttons and replace it with //the class to enable the display. const a = document.getElementsByClassName( 'social-media-links-enabled' ); [...a].forEach( (x) => (x.className += ' social-media-links-disabled') ); [...a].forEach((x) => x.classList.remove('social-media-links-enabled') ); //************************************************************************************************************** } }, [EnableSocialMediaButtons]); //heading alignment const mainTitleClass = <code>main-heading-align-${mainTitleAlignment}</code>; if (UniqueID === '') { const lID = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); onChangeID(lID); // console.log('just set it to = ' + lID); } // let divStyles = {}; // const className = getColorClassName( // 'background-color', //); //className={backgroundColor.class} //console.log('props'); //console.log(props); //const myClasses = <code><code>${backgroundColor.class} netmonics-xs-is-${ColumnsXS}-columns netmonics-sm-is-${ColumnsSM}-columns netmonics-md-is-${ColumnsMD}-columns netmonics-lg-is-${ColumnsLG}-columns netmonics-xl-is-${ColumnsXL}-columns netmonics-xxl-is-${ColumnsXXL}-columns</code></code>; //console.log(myClasses); const myClasses = <code>${backgroundColor.class} netmonics-xs-is-${ColumnsXS}-columns netmonics-sm-is-${ColumnsSM}-columns netmonics-md-is-${ColumnsMD}-columns netmonics-lg-is-${ColumnsLG}-columns netmonics-xl-is-${ColumnsXL}-columns netmonics-xxl-is-${ColumnsXXL}-columns</code>; //console.log(myClasses); //const myClasses = //' netmonics-xs-is-2-columns netmonics-sm-is-3-columns netmonics-md-is-3-columns netmonics-lg-is-4-columns netmonics-xl-is-4-columns netmonics-xxl-is-4-columns '; return ( <div id="{UniqueID}" {...useBlockProps({ className: myClasses, })} > <InspectorControls> <PanelColorSettings title={__('Color Settings', 'text-box')} icon="admin-appearance" initialOpen={false} disableCustomColors={false} colorSettings={[ { value: backgroundColor.color, onChange: setBackgroundColor, label: __('Background Color', 'text-box'), }, // { // value: textColor.color, // onChange: setTextColor, // label: __('Text Color', 'text-box'), // }, ]} > {/* <ContrastChecker textColor={textColor.color} backgroundColor={backgroundColor.color} /> */} </PanelColorSettings> <Panel header=""> <PanelBody title="Columns" initialOpen={false}> <PanelRow> {__( 'Set the number of columns for each screen size', 'team-members' )} </PanelRow> <PanelRow>{__('', 'team-members')}</PanelRow> <RangeControl label={__( 'Extra Small (less than 575px)', 'team-members' )} min={1} max={6} onChange={onChangeColumnsXS} value={ColumnsXS} /> <RangeControl label={__( 'Small (between 575px and 768px)', 'team-members' )} min={1} max={6} onChange={onChangeColumnsSM} value={ColumnsSM} /> <RangeControl label={__( 'Medium (between 768px and 992px)', 'team-members' )} min={1} max={6} onChange={onChangeColumnsMD} value={ColumnsMD} /> <RangeControl label={__( 'Large (between 992px and 1200px)', 'team-members' )} min={1} max={6} onChange={onChangeColumnsLG} value={ColumnsLG} /> <RangeControl label={__( 'XL (between 1200px and 1400px)', 'team-members' )} min={1} max={6} onChange={onChangeColumnsXL} value={ColumnsXL} /> <RangeControl label={__( 'XXL (greater than 1400px)', 'team-members' )} min={1} max={6} onChange={onChangeColumnsXXL} value={ColumnsXXL} /> </PanelBody> </Panel> <Panel header=""> <PanelBody title="Call to Action Button " initialOpen={false} > <TextControl label="Default Text" value={DefaultButtonTextValue} onChange={onChangedDefaultButtonText} help={__('Enter the default text for the button')} /> </PanelBody> </Panel> <Panel header=""> <PanelBody title="Main Heading" initialOpen={false}> <PanelRow> {__('Configure the heading', 'team-members')} </PanelRow> <PanelRow> {__('', 'team-members')} <RadioControl label="Alignment" selected={mainTitleAlignment} options={[ { label: 'Left', value: 'left' }, { label: 'Centre', value: 'center' }, ]} onChange={onChangeTitleAlignment} /> </PanelRow> </PanelBody> </Panel> <Panel header=""> <PanelBody title="Layout Style" initialOpen={false}> <PanelRow> {__('Configure the layout', 'team-members')} </PanelRow> <PanelRow> {__('', 'team-members')} <RadioControl label="Layout" selected={LayoutStyle} options={[ { label: 'Stacked', value: 'stacked' }, { label: 'Image Left', value: 'image-left', }, ]} onChange={onChangeLayoutStyle} /> </PanelRow> </PanelBody> </Panel> <Panel header=""> <PanelBody title="Social Media Buttons" initialOpen={false}> <PanelRow>{__('', 'team-members')}</PanelRow> <PanelRow> <ToggleControl label="Enable Social Media Buttons" checked={EnableSocialMediaButtons} onChange={onChangeEnableSocialMediaButtons} /> </PanelRow> </PanelBody> </Panel> {/* It only makes sense to display the title position options if the layout style is stacked */} {LayoutStyle === 'stacked' && ( <Panel header=""> <PanelBody title="Title" initialOpen={false}> <PanelRow>{__('', 'team-members')}</PanelRow> <PanelRow> <RadioControl label="Set title position above or below the image" selected={TitlePosition} options={[ { label: 'Above', value: 'above-image', }, { label: 'Below', value: 'below-image', }, ]} onChange={onChangeTitlePosition} /> </PanelRow> </PanelBody> </Panel> )} </InspectorControls> <RichText placeholder={__('UniqueID', 'team-member')} tagName="p" onChange={onChangeID} value={UniqueID} allowedFormats={[]} className="netmonics-cards-unique-id" /> <RichText placeholder={__('Main Title', 'team-members')} tagName="h2" value={mainTitle} allowedFormats={[]} onChange={onChangeMainTitle} className={mainTitleClass} /> <InnerBlocks allowedBlocks={['netmonics/generic-card']} orientation="horizontal" // Set the orientation to horizontal as the blocks are aligned horizontally and when dragged and dropped // this will cause a blue vertical line to appear indicating where the block will be dropped. template={[ ['netmonics/generic-card'], ['netmonics/generic-card'], ['netmonics/generic-card'], ]} //templateLock="all" //Prevent more items being added, 'insert' is also a valid option. /> </div> ); } export default withColors({ backgroundColor: 'background-color', // textColor: 'color', })(Edit);Forum: Developing with WordPress
In reply to: useBlockProps seems to cause an errorI’m not sure because under the section titled ‘block wrapper props’ it says ‘classNames’ plural:
The first thing to notice here is the use of the useBlockProps React hook on the block wrapper element. In the example above, the block wrapper renders a “div” in the editor, but in order for the Gutenberg editor to know how to manipulate the block, add any extra classNames . . .
Though the example below does show just a single class.
Steve
Forum: Developing with WordPress
In reply to: useBlockProps seems to cause an errorI’ve just noticed you modified your reply, sorry, I’ll take a look at that.
Forum: Developing with WordPress
In reply to: useBlockProps seems to cause an errorIt seems I can’t show ticks because this site uses them for displaying code.
I put them where ‘xxx’ is in the following string:
const myClasses = xxx<code>${backgroundColor.class} netmonics-xs-is-${ColumnsXS}-columns netmonics-sm-is-${ColumnsSM}-columns netmonics-md-is-${ColumnsMD}-columns netmonics-lg-is-${ColumnsLG}-columns netmonics-xl-is-${ColumnsXL}-columns netmonics-xxl-is-${ColumnsXXL}-columns</code>xxx;Forum: Developing with WordPress
In reply to: useBlockProps seems to cause an errorHi Adam,
Thank you for the idea.
It doesn’t work unfortunately, I got errors with the above line, tried putting it in ticks i.e. ` but still didn’t work.
const myClasses =<code>${backgroundColor.class} netmonics-xs-is-${ColumnsXS}-columns netmonics-sm-is-${ColumnsSM}-columns netmonics-md-is-${ColumnsMD}-columns netmonics-lg-is-${ColumnsLG}-columns netmonics-xl-is-${ColumnsXL}-columns netmonics-xxl-is-${ColumnsXXL}-columns</code>;Please let me know if you have any other ideas and I’ll give them a try.
Best Regards,
Steve
- This reply was modified 3 years, 11 months ago by Steve.
I found a way to solve this.
I generated an ID for each block which is stored as an attribute.
I can access the ID attribute in PHP and then check each line in the comments for that ID. When I find it, I know that the next line contains the item attributes for the block.
I stop reading when I detect the closing tag for the block.That way each block only loads it’s own comments which contain the attributes.
I hope that makes sense.
Unfortunately, I discovered an issue with the above code.
I’ve now discovered that it extracts the content for all blocks on the page not just the block making the call.
Does anyone know how to obtain the content for the block making the call rather than for the whole page?
To recap, I use a callback function ‘render_html’ function which extracts the comments as follows:
function render_html($attributes, $content, $post) { ob_start(); //Load the block's content $dom1 = new DOMDocument; $dom1->loadHTML(get_the_content()); $xpath1 = new DOMXpath($dom1); //Get the comments $comments = $xpath1->query('.//comment()', $dom1);Best Regards,
Steve
- This reply was modified 3 years, 11 months ago by Steve.
Hi,
I’ve solved this problem now, in case anyone is interested, this is what I did:
1) I removed source/query etc. from the attributes so that the attributes would not be stored in the html but as JSON in the comments of the block’s content.
2) I changed the top level save function to return the innerblock as follows:
import { InnerBlocks } from ‘@wordpress/block-editor’;
export default function save() {
return <InnerBlocks.Content />;
}3) I changed the inner block’s save to return null:
export default function Save() { return null; }4) I changed the PHP as follows, the comments explain what’s going on (I hope):
<?php /** * Plugin Name: Cards * Description: A generic cards block * Requires at least: 5.9.3 * Requires PHP: 7.0 * Version: 0.1.0 * Author: Stephen Bugden * License: GPL-2.0-or-later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Text Domain: generic-cards * * @package netmonics */ function render_html($attributes, $content, $post) { $post = get_post($post_id); // = parse_blocks($post->post_content); ob_start(); ?> <!-- <h1>Attributes<?php echo esc_html(var_dump($attributes)) ?>!</h3> <h3>The number of columns is <?php echo esc_html($attributes['myColumns']) ?>!</h3> <h2>The content is:</h2><p><?php echo esc_html($content) ?>!</p> <h2>The content is </h2><p><?php echo esc_html(get_the_content()) ?>!</p> <h2>The content is </h2><p><?php echo var_dump(parse_blocks( get_the_content() )) ?>!</p> <h2>The post is </h2><p><?php echo var_dump($post) ?>!</p> --> <!-- Write out the block's content --> <h2>The content is </h2><p><?php echo esc_html(get_the_content()) ?>!</p> <?php // $parsed_blocks = parse_blocks($post->post_content); // echo '<pre><h2>parsed blocks </h2>'; // print_r($content); // echo '</pre>'; //Load the block's content $dom1 = new DOMDocument; $dom1->loadHTML(get_the_content()); $xpath1 = new DOMXpath($dom1); echo "<h1>Comments</h1>"; //Get the comments $comments = $xpath1->query('.//comment()', $dom1); //Filter out the the comments for the InnerBlocks $commentPrefix = "wp:netmonics/generic-card {"; $JSON = ""; foreach($comments as $comment){ if (substr( trim($comment->nodeValue.PHP_EOL), 0, strlen($commentPrefix) ) === $commentPrefix) { //Add a comma to the end of each line except for the last line if ($JSON !== "") { $JSON = $JSON . ","; } //Retrieve the JSON from the comment $JSON .= substr( $comment->nodeValue.PHP_EOL,strlen($commentPrefix), strlen($comment->nodeValue.PHP_EOL) - strlen($commentPrefix) - 3 ); } } //Create an array for the details $JSON = '{"card-details": [' . $JSON . ']}'; echo $JSON; //Create a JSON object from the JSON string $decoded_json = json_decode($JSON, true); $cardDetails = $decoded_json['card-details']; echo "<br><br>"; //Loop through the JSON write out the details. foreach($cardDetails as $cardDetail) { echo 'Name: ' . $cardDetail['name'] . "<br>"; echo 'Bio: ' . $cardDetail['bio'] . "<br>"; echo 'Description: ' . $cardDetail['description'] . "<br>"; $socialLinks = $cardDetail['socialLinks']; if (!is_null($socialLinks)) { if (!empty($socialLinks)) { // list is not empty. echo "<br>"; foreach($socialLinks as $socialLink) { echo ' icon:' . $socialLink['icon'] . "<br>"; echo ' link:' . $socialLink['link'] . "<br>"; echo "<br>"; } } } echo "<br>"; } return ob_get_clean(); } function cards_init() { register_block_type_from_metadata( __DIR__, array( 'render_callback' => 'render_html' ) ); } add_action( 'init', 'cards_init' );See the content contains the JSON for the innerblocks:
The content is
<!– wp:block {"ref":4939} /–> <!– wp:block {"ref":6275} /–> <!– wp:kadence/rowlayout {"uniqueID":"_0bfc30-08","columns":1,"colLayout":"equal","inheritMaxWidth":true} –> <div class="wp-block-kadence-rowlayout alignnone"><div id="kt-layout-id_0bfc30-08" class="kt-row-layout-inner kt-layout-id_0bfc30-08"><div class="kt-row-column-wrap kt-has-1-columns kt-gutter-default kt-v-gutter-default kt-row-valign-top kt-row-layout-equal kt-tab-layout-inherit kt-m-colapse-left-to-right kt-mobile-layout-row kb-theme-content-width"><!– wp:kadence/column {"uniqueID":"_3ab532-7c"} –> <div class="wp-block-kadence-column inner-column-1 kadence-column_3ab532-7c"><div class="kt-inside-inner-col"><!– wp:netmonics/generic-cards –> <!– wp:netmonics/generic-card {"name":"Phil","bio":"bio","id":10676,"url":"https://netmonics6.local/wp-content/uploads/2022/05/Phil-14.jpg","socialLinks":[{"icon":"facebook","link":"https://facebook.com"},{"icon":"twitter","link":"https://twitter.com"}]} /–> <!– wp:netmonics/generic-card {"name":"Robert","bio":"bio","id":10679,"url":"https://netmonics6.local/wp-content/uploads/2022/05/Robert-8.jpg"} /–> <!– wp:netmonics/generic-card {"name":"Steve","bio":"bio","id":10680,"url":"https://netmonics6.local/wp-content/uploads/2022/05/Steve-Not-Despeckled-4.jpg"} /–> <!– /wp:netmonics/generic-cards –></div></div> <!– /wp:kadence/column –></div></div></div> <!– /wp:kadence/rowlayout –>!The JSON strings combined into an array:
Comments
{“card-details”: [{“name”:”Phil”,”bio”:”bio”,”id”:10676,”url”:”https://netmonics6.local/wp-content/uploads/2022/05/Phil-14.jpg”,”socialLinks”:[{“icon”:”facebook”,”link”:”https://facebook.com”},{“icon”:”twitter”,”link”:”https://twitter.com”}]} ,{“name”:”Robert”,”bio”:”bio”,”id”:10679,”url”:”https://netmonics6.local/wp-content/uploads/2022/05/Robert-8.jpg”} ,{“name”:”Steve”,”bio”:”bio”,”id”:10680,”url”:”https://netmonics6.local/wp-content/uploads/2022/05/Steve-Not-Despeckled-4.jpg”} ]}The values extracted from the JSON object:
Name: Phil
Bio: bio
Description:
icon:facebook
link:https://facebook.com
icon:twitter
link:https://twitter.comName: Robert
Bio: bio
Description:Name: Steve
Bio: bio
Description:So now I have the attributes stored as JSON. The save can be rewritten as PHP so now changes appear instantly without having to refresh each instance of the block and changes to the HTML won’t break the block.
I did get stuck for a while because I didn’t realise that the innerblock was registered with the same name as another block. This meant that my changes weren’t picked up and it took me a while to realise what was going on. That explains why I wasn’t seeing the JSON in the content, as commented on above.
I hope this all makes sense. Finally, my dream of creating blocks which update on the fly and don’t easily break seems possible.
Best Regards,
Steve
I’ve tried modifying the function to include the $content variable:
function render_html($attributes, $content) { ob_start(); ?> <h1>Attributes<?php echo esc_html(var_dump($attributes)) ?>!</h3> <h3>The number of columns is <?php echo esc_html($attributes['myColumns']) ?>!</h3> <h2>The content is:</h2><p><?php echo esc_html($content) ?>!</p> <h2>The content is </h2><p><?php echo esc_html(get_the_content()) ?>!</p> <h2>The content is </h2><p><?php echo var_dump(parse_blocks( get_the_content() )) ?>!</p> <?php return ob_get_clean(); }But I don’t see the attributes in the output.
I’ve defined the attributes as follows:
attributes: { name: { type: 'string', }, bio: { type: 'string', }, id: { type: 'number', }, alt: { type: 'string', default: '', }, url: { type: 'string', }, description: { type: 'string', }So as I don’t use the source as ‘html’ I thought that meant the values would be stored separately in a JSON block.
I’m stuck for what to try next. Can anyone please help?
If my question isn’t clear, please let me know, I can try to clarify my explanations.
Best Regards,
Steve
Thank you to @dranand220 for replying to me, I received an email but can’t see any way to respond in this thread.
Does it perhaps mean:
save: () => { return <InnerBlocks.Content /> }I’ve tried this and it doesn’t make any difference to the attributes, I only get the top level not the attributes from the innerblocks.
I was just reading this:
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/creating-dynamic-blocks/It says that the save function should return null, so I’ve changed it as follows:
export default function save({ attributes }) { return null; }It also says:
If you are using InnerBlocks in a dynamic block you will need to save the InnerBlocks in the save callback function using <InnerBlocks.Content/>I assume this means in the PHP callback function, in my case from the code above that would be inside the ‘render_html()’ function. Just don’t know how to apply <InnerBlocks.Content/> to that function.
Anyone got any ideas? No idea if this would make the attributes accessible though.
Steve
Forum: Fixing WordPress
In reply to: Unable to create zip file for gutenberg blockResolved!
Forum: Fixing WordPress
In reply to: Unable to create zip file for gutenberg blockOK sure will do, sorry didn’t realise I should do that.
Thank you once again.