admin管理员组文章数量:1335138
I am trying to extend the core/table
block and copy-pasted, trail and errored me into something that is close to what I am trying to do. But I it's not quite working as I like.
I like to extend the Block to have toggle elements that control the classes on the <table>
the block already does this for two base classes but I want it to work with Bootstrap CSS tables.
The original Block code can be found here.
What I like to do is something Gutenberg also does great and for the table block is does so for the style. When you set it to 'Striped' it will add a is-style-striped
to the block. It will live update the field and as soon as you manually remove the class it will automatically switch the style to 'Default'. I like to link a few class toggles to a text field like that. I searched a little in GB code but could not find where or how it's done. The buildTableClasses
function that uses classnames/dedupe
could be a way but there are a few issues here.
<ToggleControl
label={ __( 'Hover' ) }
checked={ !! tableHover }
onChange={ () => props.setAttributes( {
tableHover: ! tableHover,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
- I do not really understand the negation and double negation used here. This code is based off some tutorial I found. Currently, is behaves weird. Like inverted, classes get added when the toggles are off, but also when toggling another toggle that last class sticks, some weird. I experimented with it but it broke completely, so I posted it this way.
- The background class for the table that the block already has originally does not end up in the 'table classes' text control field.
- When I remove a class manually from the field the changes are not bound to the toggles. So I like to find a way to make this work. I could write a function for it but I like to know if there is maybe a better way to do all this. Probably better to reuse some GB core code or model the code after it. If you can point be into the right direction that would be great.
- I like to know how I can extend an already existing section, like adding new controls to it. Or removing a section like the style section that makes. Because Bootstrap has so many classes that control style that can be combined the combination of styles would be huge so class toggles is probably a better way to do it.
- During playing around with this I noticed that it sometimes broke the block and GB seems to do some checks of the output and was actually unable to restore the block. So I wonder if I should rather fork the block but it would be annoying to maintain. But if an extended block breaks users blocks when the filters come in or get removed later breaks this solution it not great.
The complete code I have currently. Ready to try plugin on Github. The save
function is copy-pasted from GB and modified for the table classes.
import classnames from 'classnames/dedupe';
const wp = window.wp;
const { __ } = wp.i18n;
const { addFilter } = wp.hooks;
const { assign } = window.lodash;
const {
createHigherOrderComponent,
} = wppose;
const {
Fragment,
} = wp.element;
const {
RichText,
InspectorControls,
getColorClassName,
} = wp.editor;
const {
PanelBody,
TextControl,
ToggleControl,
} = wpponents;
const filterBlocks = ( settings ) => {
console.log( settings );
if ( settings.name !== 'core/table' ) {
return settings;
}
const newSettings = {
...settings,
attributes: {
...settings.attributes, // spread in old attributes so we don't lose them!
tableClass: { // here is our new attribute
type: 'string',
default: 'table ',
},
tableBordered: { // here is our new attribute
type: 'boolean',
default: false,
},
tableStriped: { // here is our new attribute
type: 'boolean',
default: false,
},
tableHover: { // here is our new attribute
type: 'boolean',
default: true,
},
},
save( { attributes } ) {
const {
hasFixedLayout,
head,
body,
foot,
backgroundColor,
caption,
tableBordered,
tableStriped,
tableHover,
tableClass,
} = attributes;
const isEmpty = ! head.length && ! body.length && ! foot.length;
if ( isEmpty ) {
return null;
}
const classes = buildTableClasses( attributes );
const hasCaption = ! RichText.isEmpty( caption );
const Section = ( { type, rows } ) => {
if ( ! rows.length ) {
return null;
}
const Tag = `t${ type }`;
return (
<Tag>
{ rows.map( ( { cells }, rowIndex ) => (
<tr key={ rowIndex }>
{ cells.map(
( { content, tag, scope, align }, cellIndex ) => {
const cellClasses = classnames( {
[ `has-text-align-${ align }` ]: align,
} );
return (
<RichText.Content
className={
cellClasses ?
cellClasses :
undefined
}
data-align={ align }
tagName={ tag }
value={ content }
key={ cellIndex }
scope={
tag === 'th' ? scope : undefined
}
/>
);
}
) }
</tr>
) ) }
</Tag>
);
};
return (
<figure>
<table className={ classes === '' ? undefined : classes }>
<Section type="head" rows={ head } />
<Section type="body" rows={ body } />
<Section type="foot" rows={ foot } />
</table>
{ hasCaption && (
<RichText.Content tagName="figcaption" value={ caption } />
) }
</figure>
);
},
};
return newSettings;
};
addFilter(
'blocks.registerBlockType',
'example/filter-blocks',
filterBlocks
);
function buildTableClasses( attributes ) {
const {
hasFixedLayout,
backgroundClass,
tableStriped,
tableBordered,
tableHover,
tableClass,
} = attributes;
const classes = classnames(
tableClass.split( ' ' ),
backgroundClass,
{
table: true,
'has-fixed-layout': hasFixedLayout,
'has-background': !! backgroundClass,
'table-bordered': tableBordered,
'table-striped': tableStriped,
'table-hover': tableHover,
}
);
return classes;
}
const tableClassControl = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
if ( 'core/table' !== props.name ) {
return (
<BlockEdit { ...props } />
);
}
const {
tableStriped,
tableBordered,
tableHover,
tableClass,
} = props.attributes;
return (
<Fragment>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody
title={ __( 'Table classes' ) }
initialOpen={ true }
>
<ToggleControl
label={ __( 'Striped' ) }
checked={ !! tableStriped }
onChange={ () => props.setAttributes( {
tableStriped: ! tableStriped,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
<ToggleControl
label={ __( 'Bordered' ) }
checked={ !! tableBordered }
onChange={ () => props.setAttributes( {
tableBordered: ! tableBordered,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
<ToggleControl
label={ __( 'Hover' ) }
checked={ !! tableHover }
onChange={ () => props.setAttributes( {
tableHover: ! tableHover,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
<TextControl
label={ __( '<table> classes' ) }
type="text"
value={ tableClass }
onChange={ ( value ) =>
props.setAttributes( { tableClass: value } )
}
/>
</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'tableClassControl' );
addFilter( 'editor.BlockEdit', 'extend-block-example/with-spacing-control', tableClassControl );
I am trying to extend the core/table
block and copy-pasted, trail and errored me into something that is close to what I am trying to do. But I it's not quite working as I like.
I like to extend the Block to have toggle elements that control the classes on the <table>
the block already does this for two base classes but I want it to work with Bootstrap CSS tables.
The original Block code can be found here.
What I like to do is something Gutenberg also does great and for the table block is does so for the style. When you set it to 'Striped' it will add a is-style-striped
to the block. It will live update the field and as soon as you manually remove the class it will automatically switch the style to 'Default'. I like to link a few class toggles to a text field like that. I searched a little in GB code but could not find where or how it's done. The buildTableClasses
function that uses classnames/dedupe
could be a way but there are a few issues here.
<ToggleControl
label={ __( 'Hover' ) }
checked={ !! tableHover }
onChange={ () => props.setAttributes( {
tableHover: ! tableHover,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
- I do not really understand the negation and double negation used here. This code is based off some tutorial I found. Currently, is behaves weird. Like inverted, classes get added when the toggles are off, but also when toggling another toggle that last class sticks, some weird. I experimented with it but it broke completely, so I posted it this way.
- The background class for the table that the block already has originally does not end up in the 'table classes' text control field.
- When I remove a class manually from the field the changes are not bound to the toggles. So I like to find a way to make this work. I could write a function for it but I like to know if there is maybe a better way to do all this. Probably better to reuse some GB core code or model the code after it. If you can point be into the right direction that would be great.
- I like to know how I can extend an already existing section, like adding new controls to it. Or removing a section like the style section that makes. Because Bootstrap has so many classes that control style that can be combined the combination of styles would be huge so class toggles is probably a better way to do it.
- During playing around with this I noticed that it sometimes broke the block and GB seems to do some checks of the output and was actually unable to restore the block. So I wonder if I should rather fork the block but it would be annoying to maintain. But if an extended block breaks users blocks when the filters come in or get removed later breaks this solution it not great.
The complete code I have currently. Ready to try plugin on Github. The save
function is copy-pasted from GB and modified for the table classes.
import classnames from 'classnames/dedupe';
const wp = window.wp;
const { __ } = wp.i18n;
const { addFilter } = wp.hooks;
const { assign } = window.lodash;
const {
createHigherOrderComponent,
} = wppose;
const {
Fragment,
} = wp.element;
const {
RichText,
InspectorControls,
getColorClassName,
} = wp.editor;
const {
PanelBody,
TextControl,
ToggleControl,
} = wpponents;
const filterBlocks = ( settings ) => {
console.log( settings );
if ( settings.name !== 'core/table' ) {
return settings;
}
const newSettings = {
...settings,
attributes: {
...settings.attributes, // spread in old attributes so we don't lose them!
tableClass: { // here is our new attribute
type: 'string',
default: 'table ',
},
tableBordered: { // here is our new attribute
type: 'boolean',
default: false,
},
tableStriped: { // here is our new attribute
type: 'boolean',
default: false,
},
tableHover: { // here is our new attribute
type: 'boolean',
default: true,
},
},
save( { attributes } ) {
const {
hasFixedLayout,
head,
body,
foot,
backgroundColor,
caption,
tableBordered,
tableStriped,
tableHover,
tableClass,
} = attributes;
const isEmpty = ! head.length && ! body.length && ! foot.length;
if ( isEmpty ) {
return null;
}
const classes = buildTableClasses( attributes );
const hasCaption = ! RichText.isEmpty( caption );
const Section = ( { type, rows } ) => {
if ( ! rows.length ) {
return null;
}
const Tag = `t${ type }`;
return (
<Tag>
{ rows.map( ( { cells }, rowIndex ) => (
<tr key={ rowIndex }>
{ cells.map(
( { content, tag, scope, align }, cellIndex ) => {
const cellClasses = classnames( {
[ `has-text-align-${ align }` ]: align,
} );
return (
<RichText.Content
className={
cellClasses ?
cellClasses :
undefined
}
data-align={ align }
tagName={ tag }
value={ content }
key={ cellIndex }
scope={
tag === 'th' ? scope : undefined
}
/>
);
}
) }
</tr>
) ) }
</Tag>
);
};
return (
<figure>
<table className={ classes === '' ? undefined : classes }>
<Section type="head" rows={ head } />
<Section type="body" rows={ body } />
<Section type="foot" rows={ foot } />
</table>
{ hasCaption && (
<RichText.Content tagName="figcaption" value={ caption } />
) }
</figure>
);
},
};
return newSettings;
};
addFilter(
'blocks.registerBlockType',
'example/filter-blocks',
filterBlocks
);
function buildTableClasses( attributes ) {
const {
hasFixedLayout,
backgroundClass,
tableStriped,
tableBordered,
tableHover,
tableClass,
} = attributes;
const classes = classnames(
tableClass.split( ' ' ),
backgroundClass,
{
table: true,
'has-fixed-layout': hasFixedLayout,
'has-background': !! backgroundClass,
'table-bordered': tableBordered,
'table-striped': tableStriped,
'table-hover': tableHover,
}
);
return classes;
}
const tableClassControl = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
if ( 'core/table' !== props.name ) {
return (
<BlockEdit { ...props } />
);
}
const {
tableStriped,
tableBordered,
tableHover,
tableClass,
} = props.attributes;
return (
<Fragment>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody
title={ __( 'Table classes' ) }
initialOpen={ true }
>
<ToggleControl
label={ __( 'Striped' ) }
checked={ !! tableStriped }
onChange={ () => props.setAttributes( {
tableStriped: ! tableStriped,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
<ToggleControl
label={ __( 'Bordered' ) }
checked={ !! tableBordered }
onChange={ () => props.setAttributes( {
tableBordered: ! tableBordered,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
<ToggleControl
label={ __( 'Hover' ) }
checked={ !! tableHover }
onChange={ () => props.setAttributes( {
tableHover: ! tableHover,
tableClass: buildTableClasses( props.attributes ),
} ) }
/>
<TextControl
label={ __( '<table> classes' ) }
type="text"
value={ tableClass }
onChange={ ( value ) =>
props.setAttributes( { tableClass: value } )
}
/>
</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'tableClassControl' );
addFilter( 'editor.BlockEdit', 'extend-block-example/with-spacing-control', tableClassControl );
Share
Improve this question
edited Jul 3, 2020 at 19:07
made2popular
3443 silver badges11 bronze badges
asked Jun 27, 2020 at 18:02
NextGenThemesNextGenThemes
7568 silver badges28 bronze badges
2
|
1 Answer
Reset to default 5The double NOT operator (!!
)
It's simply a way to convert/type-cast a non-boolean value to a boolean value, and !! <expression>
gives us the opposite of ! <expression>
.
let foo = 'bar'; // non-empty string
console.log( ! foo, !! foo ); // false, true
foo = ''; // now it's an empty string
console.log( ! foo, !! foo ); // true, false
Issues in your code
Despite that
wp.editor
works, it's been deprecated, so you should usewp.blockEditor
instead:const { RichText, InspectorControls, getColorClassName } = wp.blockEditor; // not wp.editor
In
buildTableClasses()
,backgroundClass
is not an attribute in the block. So that (the first one) should bebackgroundColor
and definebackgroundClass
like so:const backgroundClass = getColorClassName( 'background-color', backgroundColor );
The
buildTableClasses()
should only be used with thesave
function, because from theedit
function you'd get the "inverted" issue because the function would receive the old attributes.And I would use a dedicated function to update the "
<table>
classes" field when a toggle control is updated and only add/remove the associated class with that toggle, e.g.table-hover
for the "Hover" toggle:function onChangeTableHover() { props.setAttributes( { // Toggle the state. tableHover: ! tableHover, // Then add/remove only the table-hover class. tableClass: classnames( tableClass, { 'table-hover': ! tableHover, } ), } ); } /* Then in the JSX: <ToggleControl label={ __( 'Hover' ) } checked={ !! tableHover } onChange={ onChangeTableHover } /> */
You might be thinking about the background color classes, but they should be controlled (added/removed) by the color picker toggles (in the "Color settings" panel/section).
When the "
<table>
classes" field is updated, e.g. you typedtable-hover
in there or you removed it, you should change the toggle states:function onChangeTableClass( value ) { // User likely typed a whitespace. if ( tableClass === value.trim() ) { props.setAttributes( { tableClass: value } ); return; } const list = value.split( / +/ ); props.setAttributes( { // Update the value. tableClass: classnames( value ), // Then the toggles. tableHover: list.indexOf( 'table-hover' ) >= 0, tableBordered: list.indexOf( 'table-bordered' ) >= 0, tableStriped: list.indexOf( 'table-striped' ) >= 0, } ); } /* Then in the JSX: <TextControl label={ __( '<table> classes' ) } type="text" value={ tableClass } onChange={ onChangeTableClass } /> */
The default value for the
tableClass
attribute should betable table-hover
because the defaulttableHover
state istrue
.(Just a suggestion) I think you should use
attribute
as thesource
for thetableClass
attribute.
Block Validation
Yes, Gutenberg does validate the block's output:
During editor initialization, the saved markup for each block is regenerated using the attributes that were parsed from the post’s content. If the newly-generated markup does not match what was already stored in post content, the block is marked as invalid. This is because we assume that unless the user makes edits, the markup should remain identical to the saved content.
So if the saved markup is <p class="foo">bar baz</p>
, but the newly-generated markup is <p class="foo new-class">bar baz</p>
, then you'd get an error.
Therefore, because you're changing the output, then you'd better off copy the block, customize it and register it as a new block…
- Creating a new block might not be as easy as simply extending the existing block, but a new block is better than having to deal with block validation errors later on, e.g. after your plugin/theme is deactivated.
If you'd rather simply extend the core table block
Then you would want to copy the original edit
component, edit the code, and use it as the edit
function for the block, similar to the way you did it with the save
function.
Why so is because you would want to update the "<table>
classes" field whenever a background color is selected/deselected, which you'd need to modify the PanelColorSettings
element.
Secondly, copying also allows you to remove/reorder existing sections or add new controls to the sections.
Try my script
You can find it on GitHub (source | build), and I built it using the wp-scripts
package.
Update: About the tableHover: ! tableHover
or <attribute>: ! <attribute>
It's equivalent to what the fourth line below does in PHP:
<?php
$attrs = [ 'tableHover' => false ];
$tableHover = $attrs['tableHover']; // get the current value
$attrs['tableHover'] = ! $tableHover; // toggle/change the value
var_dump( $tableHover, $attrs['tableHover'] ); // bool(false) bool(true)
So you asked (in the comment):
I am still confused about
tableHover: ! tableHover
ononChange
. ... why is the attribute negated when setting the attribute?
And it's because the attribute is of the boolean type, so the value should only be either true
or false
.
So if the current value in props.attributes
is true
, then the tableHover: ! tableHover
toggles/changes the value to false
.
// The currently saved block attributes.
const { tableHover } = props.attributes;
// When updating the tableHover, we could simply do:
props.setAttributes( { tableHover: ! tableHover } );
// .. which is equivalent to:
props.setAttributes( { tableHover: tableHover ? false : true } );
Or in an onChange
callback, you can use the current state of the toggle control element that's passed as the first parameter to the callback.
<ToggleControl
checked={ !! tableHover }
onChange={ ( checked ) => props.setAttributes( { tableHover: checked } ) }
/>
And that's more understandable, I guess? :)
But the tableHover: ! tableHover
is a simpler version without having to use the first onChange
parameter.
So just use any methods you prefer, but make sure to set the correct value, e.g. if the toggle control element is checked, set the tableHover
to true
.
And btw, you can also use !!
in PHP in place of (bool)
:
<?php
$foo = 'bar';
var_dump( (bool) $foo, !! $foo ); // bool(true) bool(true)
本文标签: plugin developmentHow to control an elements classes from multiple Gutenberg sidebar controls
版权声明:本文标题:plugin development - How to control an elements classes from multiple Gutenberg sidebar controls? 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742270005a2444125.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
table-sm
so it would really make things annoying. Not sure if replacing the style image based selector with a dropdown select field is possible but it would only work with custom compiled bootsrap css with a lot ofscss @extend
– NextGenThemes Commented Jul 12, 2020 at 2:38