Can a widget in the Customizer be “single-use” (i.e. disabled after 1 instance has been added)?

Approach

So I’ve looked into this and my approach is this:

  1. When launching the customizer go through all sidebars and disable the widget as soon as you find any usage of it
  2. Whenever a sidebar is changed do that again.

Disclaimer

This has the following limitations:

  1. This is the first time I’ve dabbled in playing with the JS API of the Customizer. So I might be doing inefficient stuff here, but hey, it works 😉
  2. Only cares about the Customizer (as stated in the question)
  3. Doesn’t do any kind of server-side validation. It merely hides the UI, so if you’re worried about someone circumventing the UI this is incomplete/insecure.
  4. Disabling the widget is global – any use in any sidebar disables the widget globally.

The code

Now that we have the Disclaimer done let’s look at the code:

(function() {
    wp.customize.bind( 'ready', function() {

        var api = wp.customize,
            widgetId = 'foo_widget',
            widget = wp.customize.Widgets.availableWidgets.findWhere( { id_base: widgetId } );

        /**
         * Counts how often a widget is used based on an array of Widget IDs.
         *
         * @param widgetIds
         * @returns {number}
         */
        var countWidgetUses = function( widgetIds ){

            var widgetUsedCount = 0;

            widgetIds.forEach(function(id){

                if( id.indexOf( widgetId ) == 0 ){
                    widgetUsedCount++;
                }

            });

            return widgetUsedCount;

        };

        var isSidebar = function( setting ) {
            return (
                0 === setting.id.indexOf( 'sidebars_widgets[' )
                &&
                setting.id !== 'sidebars_widgets[wp_inactive_widgets]'
            );
        };

        var updateState = function(){

            //Enable by default...
            widget.set('is_disabled', false );

            api.each( function( setting ) {
                if ( isSidebar( setting ) ) {
                    //...and disable as soon as we encounter any usage of the widget.
                    if( countWidgetUses( setting.get() ) > 0 ) widget.set('is_disabled', true );
                }
            } );

        };

        /**
         * Listen to changes to any sidebar.
         */
        api.each( function( setting ) {
            if ( isSidebar( setting ) ) {
                setting.bind( updateState );
            }
        } );

        updateState();

    });
})( jQuery );

Sidenote: Use the customize_controls_enqueue_scripts action to add the script.


You could probably extend this to limit it to work an a per sidebar base instead of globally. I’d say listen to the activation of a sidebar and then count the widgets in that sidebar. But I didn’t find time to look into that as well.

Leave a Comment