The 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)