1

I want to get the block ID of my widgets (e.g. #block-1, which is displayed on the frontend after saving a widget) on the widgets screen in the backend. What I try to achieve is to display the ID of the widget in the toolbar so that you can easily copy it. copy widget ID

I currently use a custom API endpoint to receive the ID but it will trigger a request on every render (I already debounced it, but am searching for a better way).

My idea was than to subscribe to the save action of the widgets, since the ID is first available after saving the widgets screen:

const widgetIdToolbar = createHigherOrderComponent( ( BlockEdit ) => {
    if ( select( 'core/editor' ).getCurrentPostType() ) {
        return BlockEdit;
    }
    
    return ( props ) => {
        if ( props.name === 'core/widget-area' || props.name === 'core/legacy-widget' ) {
            return ( <BlockEdit { ...props } /> );
        }
        
        const [ widgetId, setWidgetId ] = useState( 0 );

        subscribe( () => {
            const isAutosavingPost = select( 'core/editor' ).isAutosavingPost();
            const isSavingPost = select( 'core/editor' ).isSavingPost();
                
            if ( isSavingPost || isAutosavingPost ) {
                // do stuff
            }
        } );
        
        return (
            <>
                <BlockEdit { ...props } />
            </>
        );
    }
}, 'widgetIdToolbar' );

addFilter( 'editor.BlockEdit', 'rh-widget-toolbar/add-toolbar', widgetIdToolbar );

Unfortunately, both isAutosavingPost and isSavingPost always return false. It seems they don’t apply to widgets. How to get the similar functionality for “on widgets save”?

13
  • widgets are implemented as a legacy widget block that wraps around the widget to provide a compatibility layer, though in this case it shouldn't matter, the root problem is that the widgets screen is not a post editor so there is no main post to autosave. As an aside, what are you trying to do that requires this?
    – Tom J Nowell
    Commented May 11, 2022 at 10:18
  • also is this on the edit widgets screen, or the customizer screen?
    – Tom J Nowell
    Commented May 11, 2022 at 10:27
  • As I said, I want to get the block ID of the widgets in the backend, which is then displayed as ID in the frontend. This ID is first available after saving the widgets and not before (except for blocks that use server-side rendering). Thus I want to subscribe to the save action to get the ID directly afterwards. It’s the dedicated widgets screen I’m talking about. Will update the question accordingly.
    – KittMedia
    Commented May 11, 2022 at 10:29
  • I see, what are you trying to do when this happens that requires it though? Needing the block ID is usually a sign that you're doing something wrong, or that a much simpler solution is present that you're unaware of. If you can provide the context then those can be shared, or it'll avoid unnecessary questions. It's also possible that someone will provide an answer that while technically correct, can't be used in your situation.
    – Tom J Nowell
    Commented May 11, 2022 at 10:39
  • Also, where did you put the subscribe code in your question? Is it enqueued on that page or is it included some other way only when that widget runs? Or on a particular event? Are you trying to use the block ID to traverse upwards or across or to get the sidebar ID?
    – Tom J Nowell
    Commented May 11, 2022 at 11:09

1 Answer 1

0

If the goal is to display the block ID so that the user can get it easily, instead try using a block editor filter to add a panel in the sidebar:

From: https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/#editor-blockedit

const { createHigherOrderComponent } = wp.compose;
const { Fragment } = wp.element;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;
 
const withInspectorControls = createHigherOrderComponent( ( BlockEdit ) => {
    return ( props ) => {
        return (
            <Fragment>
                <BlockEdit { ...props } />
                <InspectorControls>
                    <PanelBody>My custom control</PanelBody>
                </InspectorControls>
            </Fragment>
        );
    };
}, 'withInspectorControl' );
 
wp.hooks.addFilter(
    'editor.BlockEdit',
    'my-plugin/with-inspector-controls',
    withInspectorControls
);

Then inside the panel, use props to retrieve the blocks ID and display it, perhaps in an input with the disabled property. Specifically clientId.

It might look like this (untested):

const { createHigherOrderComponent } = wp.compose;
const { Fragment } = wp.element;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;
 
const withWidgetIDPanel = createHigherOrderComponent( ( BlockEdit ) => {
    return ( props ) => {
        const widgetID = props.__internalWidgetId ? props.__internalWidgetId : 0;
        return (
            <Fragment>
                <BlockEdit { ...props } />
                <InspectorControls>
                    <PanelBody>
                        <input
                            class="components-text-control__input"
                            type="text"
                            disabled
                            value={ props.widgetID }
                        />
                    </PanelBody>
                </InspectorControls>
            </Fragment>
        );
    };
}, 'withWidgetIDPanel' );
 
wp.hooks.addFilter(
    'editor.BlockEdit',
    'tomjn/display-widget-id',
    withWidgetIDPanel
);

This will give you the widget ID if it's available in the sidebar for every legacy widget if enqueued on the widgets page, and provides a useful debug panel for non-widget editors too. It also gives you a place to use react hooks and other WordPress APIs in the context of a specific block, without needing to figure out which widget belongs to which block ID.

Note that when the widget ID changes, React will re-render the component automatically, no subscription is necessary. Note that no local state is in use.

Frontend Block ID

If however you want the ID that gets used on the frontend, there's an attribute named __internalWidgetId. When this changes React will re-render the edit component because its props have changed, so no subscription to the store is necessary.

Before the widget is saved, this __internalWidgetId attribute does not exist, and will be null if accessed, and when it comes into existence again the edit component will re-render because changed props are how React knows to re-render a DOM element.

Storing the widget ID in local state is not necessary and could lead to problems. Just pass the raw attribute down if it's not null to any child components that need it or use the internal widget ID directly.

As a bonus, you can select on the core/edit-widgets store to grab the widget data directly.

4
  • Unfortunately, as far as I could see, the __internalWidgetId is only available on blocks that use server-side rendering (or when the save function returns null), which does not apply to most of the blocks.
    – KittMedia
    Commented May 12, 2022 at 6:16
  • that's because only legacy widgets are actually widgets and have a widget ID, for other blocks the technical answer is no, they don't have a widget ID because they're not really widgets, they're just blocks. For those you should rely on the existing block based solution of the advanced panel with its HTML anchor ( aka the ID ) and the css class
    – Tom J Nowell
    Commented May 12, 2022 at 10:46
  • At a stretch, getWidgets in the core/edit-widgets store may provide a psuedo ID you can use for non-widget blocks, perhaps combining the block ID with getWidget(, this seems to provide an id attribute as well as some wrapping HTML
    – Tom J Nowell
    Commented May 12, 2022 at 10:51
  • I'd also suggest useSelect instead of subscribing so that re-rendering is used, this storing the widget ID in local state is a massive can of worms that backfires
    – Tom J Nowell
    Commented May 12, 2022 at 10:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.