The HTML structure I am trying to achive is this
<div class="accordion my-accordion" id="uniaccordionone">
<div class="accordion-item my-accord-item accorditem1">
<div class="accordion-header" id="heading1">
<button class="accordion-button my-heading-button" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseuniaccordionone1" aria-expanded="true"
aria-controls="collapseuniaccordionone1">
<span class="numtext">1</span>
<span class="my-heading">This is a heading text</span>
<span class="dynamic-icon my-icon-cover">
<svg class="icon open">
<use xlink:href="#substract"></use>
</svg> <svg class="icon close">
<use xlink:href="#add"></use>
</svg> </span>
</button>
</div>
<div id="collapseuniaccordionone1" class="my-detail-text accordion-collapse collapse show"
aria-labelledby="heading1" data-bs-parent="#uniaccordionone" style="">
<div class="accordion-body">
This is detail text </div>
</div>
</div>
<div class="accordion-item my-accord-item accorditem2">
<div class="accordion-header" id="heading2">
<button class="accordion-button my-heading-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseuniaccordionone2" aria-expanded="false"
aria-controls="collapseuniaccordionone2">
<span class="numtext">2</span>
<span class="my-heading">This is a heading text 2</span>
<span class="dynamic-icon my-icon-cover">
<svg class="icon open">
<use xlink:href="#substract"></use>
</svg> <svg class="icon close">
<use xlink:href="#add"></use>
</svg> </span>
</button>
</div>
<div id="collapseuniaccordionone2" class="my-detail-text accordion-collapse collapse" aria-labelledby="heading2"
data-bs-parent="#uniaccordionone" style="">
<div class="accordion-body">
This is detail text 2 </div>
</div>
</div>
</div>
If you look carefully the bold highlighted items in the code, some class, ids and custom attributes values are changing in a sequencial manner, like 1, 2 etc.
I use to create such custom block using php codes, in above example I used this code
if ( ! function_exists( 'uni_block_accordiontext' ) ) :
global $uni_accordion_counting, $uni_accordion_id;
function uni_block_accordiontext( $output, $attributes ) {
ob_start();
global $uni_accordion_counting, $uni_accordion_id;
$uni_accordion_counting = 0;
?>
<?php
wp_register_script('unibs', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js', array(), null, true);
wp_enqueue_script('unibs');
wp_register_style('bsaccordionstyle', get_template_directory_uri() . '/css/bsaccordion.min.css', array(), null, 'all');
wp_enqueue_style('bsaccordionstyle'); // Enqueue it!
?>
<?php if ( ! function_exists( 'uni_accordioncall_style' ) ) :
add_action( 'wp_footer', 'uni_accordioncall_style', 30, 0 );
function uni_accordioncall_style(){ ?>
<style>
.accordion-button:after{display:none;}
.accordion-button .icon.open{display:inline-block;}
.accordion-button .icon.close{display:none;}
.accordion-button:not(.collapsed) .icon.open{display:none;}
.accordion-button:not(.collapsed) .icon.close{display:inline-block;}
.accordion-button[aria-expanded="false"] .icon.open{display:inline-block;}
.accordion-button[aria-expanded="false"] .icon.close{display:none;}
</style>
<?php } endif; ?>
<div class="accordion <?php echo esc_html( $attributes['uni-accordion-class'] ); ?>" id="<?php echo esc_html( $attributes['uni-accordion-id'] ); ?>">
<?php
$uniaccordiontextrepeater = $attributes['uni_accordion_repeater'] ;
foreach ( $uniaccordiontextrepeater as $inner_control ) {
$uni_accordion_counting++ ;
$uni_accordion_id = $attributes['uni-accordion-id'];
if(isset($inner_control['uni-accorditemclass'])){
$uni_accorditemclass = $inner_control['uni-accorditemclass'];
} else{ $uni_accorditemclass = ''; }
if(isset($inner_control['uni-heading-button-class'])){
$uni_heading_button_class = $inner_control['uni-heading-button-class'];
} else{$uni_heading_button_class = '';}
if(isset($inner_control['uni-custom-num-text'])){
$uni_custom_num_text = $inner_control['uni-custom-num-text'];
} else{$uni_custom_num_text = '';}
if(isset($inner_control['heading-class'])){
$heading_class = $inner_control['heading-class'];
} else{$heading_class = '';}
if(isset($inner_control['uni-heading-text'])){
$uni_heading_text = $inner_control['uni-heading-text'];
} else{$uni_heading_text = '';}
if(isset($inner_control['uni-icon-cover-class'])){
$uni_icon_cover_class = $inner_control['uni-icon-cover-class'];
} else{$uni_icon_cover_class = '';}
if(isset($inner_control['uni-detail-text-code'])){
$uni_detail_text_class = $inner_control['uni-detail-text-code'];
} else{$uni_detail_text_class = '';}
if(isset($inner_control['uni_detail_text'])){
$uni_detail_text = $inner_control['uni_detail_text'];
} else{$uni_detail_text = '';}
?>
<div class="accordion-item <?php if($uni_accorditemclass){ echo esc_html($uni_accorditemclass); } ?> accorditem<?php echo $uni_accordion_counting; ?>">
<div class="accordion-header" id="heading<?php echo $uni_accordion_counting; ?>">
<button class="accordion-button <?php echo esc_html( $uni_heading_button_class ); ?>" type="button" data-bs-toggle="collapse" data-bs-target="#collapse<?php echo esc_html( $attributes['uni-accordion-id'] ); ?><?php echo $uni_accordion_counting; ?>" aria-expanded="<?php if ( $uni_accordion_counting == 1 && $attributes['uni-individual-open'] == false ) { echo 'true';} else { echo 'false'; } ?>" aria-controls="collapse<?php echo esc_html( $attributes['uni-accordion-id'] ); ?><?php echo $uni_accordion_counting; ?>">
<?php if($attributes['uni-show-num-text']){ ?>
<span class="numtext"><?php if($uni_custom_num_text){echo $uni_custom_num_text; } else { echo $uni_accordion_counting; } ?></span>
<?php } ?>
<span class="<?php echo esc_html( $heading_class ); ?>"><?php echo $uni_heading_text; ?></span>
<span class="dynamic-icon <?php echo esc_html( $uni_icon_cover_class ); ?>">
<?php if($attributes['uni-open-icon-id']){ ?><svg class="icon open"><use xlink:href="#<?php echo esc_html( $attributes['uni-open-icon-id'] ); ?>" /></svg><?php } ?>
<?php if($attributes['uni-close-icon-id']){ ?><svg class="icon close"><use xlink:href="#<?php echo esc_html( $attributes['uni-close-icon-id'] ); ?>" /></svg><?php } ?>
</span>
</button>
</div>
<div id="collapse<?php echo esc_html( $attributes['uni-accordion-id'] ); ?><?php echo $uni_accordion_counting; ?>" class="<?php echo $uni_detail_text_class; ?> accordion-collapse collapse <?php if ( $uni_accordion_counting == 1 && $attributes['uni-individual-open'] == false ) { echo 'show'; } ?>" aria-labelledby="heading<?php echo $uni_accordion_counting; ?>" <?php if(! $attributes['uni-individual-open']) { ?>data-bs-parent="#<?php echo esc_html( $attributes['uni-accordion-id'] ); ?>"<?php } ?>>
<div class="accordion-body">
<?php if($uni_detail_text){ echo $uni_detail_text; } ?>
</div>
</div>
</div>
<?php } ?>
</div>
<?php
return ob_get_clean();
}
endif;
So is it possible to achieve such php conditions to apply someway in the interface of the making new custom block page? for example for custom attribute value or class value, we can have checkmark to keep it sequencial, we can have some shortcode to copy value of one field class to put in another block class or custom attribute, we can have a conditional block which take arguments, like if parent container with class “accordion-button” have aria-expanded attribute value to true, then show this (inner content can be anything like a decided svg block) else show (another SVG block).
This way more dynamic and anykind of block will be possible to create using content block builder.
]]>The linked site’s homepage uses a Classic theme. The top section of the homepage features recent posts in particular post categories. Even when the same category is assigned to multiple “slots’ in this top section, the same post will not appear more than once. Additionally, none of the post featured in the top section will appear in the bottom section.
I was able to achieve this in the site’s Classic theme by maintaining a running list of all the posts previously displayed in the top section, and modifying the multiple query loops in the page’s PHP template to feed this list as an array into each query, as an exclusion.
Can the same thing be achieved in an FSE theme using the an extended version of the Query block, and if so, what would be the steps required to achieve this?
I’m not necessarily looking for details and example code at this stage, just need to know if it’s possible to achieve this at all, and where I might start, if so.
I’m looking to create a new FSE theme for the site, and I think I can make it do everything I need using just stock WP blocks, apart from this one key feature of the old theme.
Any tips greatly appreciated!
]]>I’m wondering if it’s possible to extend the functionality of a core block via a plugin or theme code, without creating a new block.
I have a very specific use-case: I want to extend the functionality of the core Query Loop block such that if multiple blocks are inserted in a post or page, they will never display any post that has already appeared on that same page.
Anyone know if this is possible?
Ideally, I’d like
An old “Classic” theme used on a site I manage had that function on the homepage, but it was hard-coded into the homepage PHP, template rather than achieved with individual Gutenberg blocks.
I realise I’ve bundled two questions together, which isn’t great etiquette I guess. Sorry about that.
Alex
]]>I encountered multiple issues, the last of them is, in my view, a bug.
Firstly, a custom colour is handled differently from a preset theme colour. In the case of a preset theme colour, a slug is applied to the class name: “has-red-background”. In the case of a custom colour, a background colour is directly specified using the “style” attribute and a hex colour code.
In the editor, this problem is simply overcome by specifying: “style={useBlockprops().style}” on the child one wants to apply the styles on. Then, an rgb colour code is always used.
However, when doing the same in the save()-method, the block crashes. Apparently, it is not possible to utilise “useBlockProps()” in the save()-method.
I created a workaround like this:
export default function save({ attributes }) {
const { backgroundColor } = attributes;
const style = useBlockProps.save().style;
const colorClass = backgroundColor && !(style?.backgroundColor)? has-${backgroundColor}-background
: "";
return (
<div {...useBlockProps.save()}>
<div class={backdrop ${colorClass}
} style={style}></div>
<InnerBlocks.Content />
</div>
)
}
However, when I do this and specify a custom color, the validation fails when I reload the editor:
Block validation: Block validation failed for test/overlap-image
({name: 'test/overlap-image', icon: {…}, keywords: Array(0), attributes: {…}, providesContext: {…},?…}).
Content generated by save
function:
<div class="wp-block-test-overlap-image overlap-image has-grey-background-color has-background" style="background-color:#5d577f"><div class="backdrop " style="background-color:#5d577f"></div></div>
Content retrieved from post body:
<div class="wp-block-test-overlap-image overlap-image has-background" style="background-color:#5d577f"><div class="backdrop " style="background-color:#5d577f"></div></div>
“grey” is the default colour I specified in the block.json file. It magically appears in validation.
Will I simply have to revert to defining a ColorPicker myself (where the colour is always saved as a valid hex code) or is there some way to avoid this?
Any help or confirmation that this is a bug is greatly appreciated.
The code can be downloaded here:
https://drive.google.com/drive/folders/1S9jiYIJDx7unxglNTibsIePcSPtdT3fI?usp=sharing
]]>The block.json file looks as follows:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "test/section-card",
"version": "0.1.0",
"title": "Section Card",
"description": "A card containing text.",
"icon": "admin-comments",
"category": "layout",
"attributes": {
"backgroundColor": {
"type": "string",
"default":"#eee"
},
"textColor": {
"type": "string"
},
"cardWidth": {
"type": "string",
"default":"40%"
}
},
"example": {},
"supports": {
"html": false,
"color": {
"text": true,
"background": true
},
"layout": {
"default": {"type": "flex", "orientation": "vertical", "verticalAlignment": "center"},
"allowVerticalAlignment": true,
"allowJustification": true,
"allowOrientation": false
}
},
"textdomain": "section-arrow",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript":""
}
If I use the following .save-method, the code works as expected:
save({ attributes }) {
const { backgroundColor, cardWidth } = attributes;
const colorClass = backgroundColor.includes('#') ? "" : has-${backgroundColor}-background
;
return (
<div {...useBlockProps.save()} style={{background:"none !important"}}>
<div
className={colorClass}
style={{ width:cardWidth, backgroundColor:(backgroundColor.includes('#') ? backgroundColor :'') }}>
<InnerBlocks.Content />
</div>
</div>
)
}
If I include any other class name such as the letter “a”, the layout is suddenly applied to the child element:
className={colorClass+" a"}
The output without the letter “a” looks like expected. As soon as any class is added (even when the has-background class is removed), the output is wrong:
View post on imgur.com
This bug is incredibly weird. I have no idea what sort of strange bug is triggering this behaviour, but it is definitely not expected. I also want to note, that the block is located inside a core/column block. Any help is greatly appreciated. For now, I will simply restrict the class name to the colour slug.
The block code can be dowloaded here:
https://drive.google.com/drive/folders/13YtczFFTYuGubJ3XwLQrA9sOpfg7lwf4?usp=drive_link
]]>We have disabled NextGEN’s shortcode manager via the following snippet in wp-config:
define('NGG_DISABLE_SHORTCODE_MANAGER', TRUE);
Upgrading to NextGEN 3.54 seems to ignore this snippet, however. The Invalid JSON error came back. Reverting to 3.41 got things working again.
I just wanted to report this in case something was missed in the update.
]]>I created a plugin for a custom block that uses InnerBlocks in order to display a tab menu. However, I figured out that when I save the block in the block editor and reload the page, the block cannot be displayed anymore.
So I debugged into the code and found out that the properties (attributes and innerBlocks) are empty in the save function, but only when I refresh the page. When I add a new custom block and update it, then the attributes and innerBlocks properties are set for the props object in the save function. But as said, when I refresh the page, the properties are empty…
This is the edit code
export default function Edit(props) {
const blockProps = useBlockProps();
const { children } = useInnerBlocksProps(blockProps, additionalInnerBlocksProps);
useEffect(() => {
console.log("in useeffect");
tab.createTabs(props.clientId);
}, [])
wp.domReady(function () {
let selectedBlock = wp.data.select('core/editor').getSelectedBlock();
console.log("block", selectedBlock);
wp.data.subscribe(function () {
let currentSelectedBlock = wp.data.select('core/editor').getSelectedBlock();
if (!currentSelectedBlock) return;
if (currentSelectedBlock !== selectedBlock) {
console.log("something was selected!");
if (currentSelectedBlock.name === 'jink/jink-tab-menu-item') {
if (currentSelectedBlock.clientId === selectedBlock?.clientId) {
console.log("wp subscribe, selected same tab menu item, some property might have changed!");
if (currentSelectedBlock.attributes.tabName != selectedBlock.attributes.tabName) {
console.log("oh ya - got you! the tab name attribute changed! :)");
tab.createTabs(props.clientId);
}
}
console.log("wp subscribe, selected different tab menu item");
const parentBlocks = wp.data.select('core/block-editor').getBlockParents(currentSelectedBlock.clientId);
if (parentBlocks.length != 0) {
var selectedIdx = wp.data.select("core/block-editor").getBlock(parentBlocks[0]).innerBlocks.findIndex(f => f.clientId === currentSelectedBlock.clientId)
if (selectedIdx >= 0) {
console.log("select selected tab", selectedIdx);
tab.selectTab(selectedIdx)
}
}
}
}
selectedBlock = currentSelectedBlock;
})
})
return (
<div class="tabcontrol">
<div class="tab" >
</div>
{children} // i think these are the "innerBlocks"?
<div >
<span>Add tab</span>
<div class="innerblocks-div-wrapper" onClick={(event) => {
setTimeout(() => {
tab.createTabs(props.clientId);
}, 100)
}}>
<InnerBlocks.ButtonBlockAppender />
</div>
</div>
</div>
);
}
And this is the save code
export default function save(props) { // <------ props.attributes && props.innerBlocks are EMPTY! :(
console.log("save::props", props);
const tab = new Tab();
return (
<div class="tabcontrol">
<div class="tab">
// The elements below will never be rendered because props.innerBlocks is empty after a page refresh...
{Array.isArray(props.innerBlocks) && props.innerBlocks.map((innerBlock, i) => {
console.log("save::Really rendering the innerblocks of the props");
return (<button key={i} class="tablinks" onClick={(event) => tab.openTag(event, innerBlock.attributes.tabName, i)}>{innerBlock.attributes.tabName}</button>)
})}
</div>
<InnerBlocks.Content />
</div>
);
}
Can somebody help me out?
The idea is that the child block has an attribute called tabName. This tabName is used to be displayed as a tab link in the tab header.
I’m having an issue with a custom block that I created for WordPress. The block is supposed to show up in the “Custom Blocks” category in the block editor, but it’s not appearing when I search for it.
I’ve checked the PHP and JavaScript code for the plugin and everything seems to be correct. I’ve also tried adding some debugging code to see if the functions are being called correctly, but I’m still not able to figure out what’s causing the issue.
Here is the PHP code for the plugin:
<?php
/*
Plugin Name: Related Post Block
Description: Adds a custom block to generate related post code snippets.
*/
// Enqueue the block editor assets
add_action('enqueue_block_editor_assets', 'related_post_block_enqueue_assets');
function related_post_block_enqueue_assets() {
// Enqueue the JavaScript file for the block
wp_enqueue_script(
'related-post-block',
plugins_url('block.js', __FILE__),
array('wp-blocks', 'wp-editor', 'wp-components', 'wp-api-fetch', 'wp-element'), // Add wp-element as a dependency
filemtime(plugin_dir_path(__FILE__) . 'block.js')
);
}
// Register the block category
add_filter('block_categories', 'related_post_block_add_category');
function related_post_block_add_category($categories) {
return array_merge(
$categories,
array(
array(
'slug' => 'custom-blocks',
'title' => 'Custom Blocks',
'icon' => 'admin-generic'
)
)
);
}
And here is the JavaScript code for the block:
(function (blocks, editor, components, apiFetch) {
var el = blocks.element.createElement;
var __ = wp.i18n.__;
blocks.registerBlockType('custom-blocks/related-post', {
title: __('Related Post', 'related-post-block'),
icon: 'admin-links',
category: 'custom-blocks',
keywords: [__('related'), __('post')],
attributes: {
url: {
type: 'string',
source: 'attribute',
attribute: 'data-url',
selector: 'input[type="url"]'
}
},
edit: function (props) {
var attributes = props.attributes;
function updateUrl(event) {
props.setAttributes({ url: event.target.value });
}
return el(
'div',
{},
el(
'input',
{
type: 'url',
placeholder: __('Paste the post URL', 'related-post-block'),
value: attributes.url,
onChange: updateUrl
}
),
el(
editor.InspectorControls,
{},
el(
components.PanelBody,
{ title: __('Options', 'related-post-block'), initialOpen: true },
el(
components.PanelRow,
{},
el(
components.Button,
{
isPrimary: true,
onClick: function () {
var generateUrl = '/wp-json/related-post-block/v1/generate';
apiFetch({ path: generateUrl, method: 'POST', body: attributes.url })
.then(function (response) {
if (response && response.data) {
editor.DomUtils.insertHTML(
document.activeElement,
response.data
);
}
});
}
},
__('Generate', 'related-post-block')
)
)
)
)
);
},
save: function () {
return null; // Save is handled on the server-side
}
});
})(window.wp.blocks, window.wp.editor, window.wp.components, window.wp.apiFetch);
I would really appreciate any help or suggestions on how to resolve this issue. Thank you in advance!
]]>