6

First, I want to clarify - this is not a CSS question.

I want to change the data that is showing of the widget, when its in closed / open mode inside the wp-admin, in a sidebar or a page builder. Here is an image to better explain.

enter image description here

I want to be able to add something / remove from the title of the widget dynamically and use widget $instance to do so

The desired result:
Add a small info label stating mobile / desktop / both - which is an option picked inside that specific widget

Ideas anyone?

UPDATE
Since i see some intrest in a solution to this question:
@cjbj solution works beautifully but only in the sidebar and only partially:

add_filter ('dynamic_sidebar_params','wpse261042_change_widget_title');
function wpse261042_change_widget_title ($params) {

    $widget_id          = $params[0]['widget_id'];
    $widget_instance    = strrchr ($widget_id, '-');
    $wlen               = strlen ($widget_id);
    $ilen               = strlen ($widget_instance);
    $widget_name        = substr ($widget_id,0,$wlen-$ilen);
    $widget_instance    = substr ($widget_instance,1);

    // get the data
    $widget_instances   = get_option('widget_' . $widget_name);
    $data               = $widget_instances[$widget_instance];
    $use_mobile         = $data['use_mobile'];  // option inside my widget

    if($use_mobile == 'yes') {$string = 'desktop / mobile';} else {$string = 'desktop only';}

    $params[0]['widget_name'] .= $string;
    return $params;
}

However.. you cannot insert any html in that string (or at least i couldnt)

Would update if i find a working solution.

1
  • Try using jQuery to read the UI options of your widget and just append HTML to the title? Commented Mar 28, 2017 at 14:33

3 Answers 3

4
+100

Background:

The reason why filtering with dynamic_sidebar_params doesn't work with HTML is because WordPress strips HTML from Widget Heading in wp_widget_control() function like this:

$widget_title = esc_html( strip_tags( $sidebar_args['widget_name'] ) );

WordPress also strips HTML in default JavaScript in wp-admin/js/widgets.js

So without a customised solution, there is no default filter or option either with PHP or with JavaScript to achieve what you want.

Custom PHP Solution:

A custom PHP solution is possible that'll only work in wp-admin -> Appearance -> Widgets, but not in Customizer -> Widgets.

WordPress adds wp_widget_control() function (that generates Widget Control UI) with dynamic_sidebar_params hook, so it's possible to override it using this filter hook. However, in customizer, WordPress calls wp_widget_control() function directly, so this solution will not work for the customizer.

The solution works like the following (add this code in a custom plugin):

add_filter( 'dynamic_sidebar_params', 'wpse261042_list_widget_controls_dynamic_sidebar', 20 );
function wpse261042_list_widget_controls_dynamic_sidebar( $params ) {
    global $wp_registered_widgets;
    // we only want this in wp-admin (may need different check to enable page builder)
    if( is_admin() ) {
        if ( is_array( $params ) && is_array( $params[0] ) && $params[0]['id'] !== 'wp_inactive_widgets' ) {
            $widget_id = $params[0]['widget_id'];
            if ( $wp_registered_widgets[$widget_id]['callback'] ===  'wp_widget_control' ) {
                // here we are replacing wp_widget_control() function 
                // with our custom wpse261042_widget_control() function
                $wp_registered_widgets[$widget_id]['callback'] = 'wpse261042_widget_control';
            }
        }
    }
    return $params;
}

function wpse261042_widget_control( $sidebar_args ) {
    // here copy everything from the core wp_widget_control() function
    // and change only the part related to heading as you need 
} 

As I said before, this solution doesn't work for the customizer and more likely to require future updates as we are overriding a core function.

Custom JavaScript Solution (Recommended):

Fortunately, it's possible to customize this behaviour with JavaScript. WordPress updates Widget Control Heading with JavaScript anyway. To do that WordPress keeps a placeholder in-widget-title CSS class and updates it with the widget title field value from JavaScript CODE. We can manipulate this to achieve our goal.

Related Core JS files:

First you need to know that WordPress loads wp-admin/js/customize-widgets.js file (with customize-widgets handle) in wp-admin -> Customize -> Widgets (customizer) and wp-admin/js/widgets.js file (with admin-widgets handle) in wp-admin -> Appearance -> Widgets to manipulate Widget Control UI. These two files do similar operations for Widget UI markups and Widget Heading UI manipulation, but a lot of different things as well. So we need to consider that for our JavaScript based solution.

Considerations for Customizer:

Customizer doesn't load Widget UI markup immediately after page load, instead, it loads with JavaScript when the corresponding Widgets -> Sidebar panel is open. So we need to manipulate the Widget UI after WordPress loads it. For example, since customizer CODE is event based, I've used the following line of CODE to set the event handler at the correct moment:

section.expanded.bind( onExpanded );

Also, customizer used AJAX to load changes immediately, that's why I've used the following line to tap into the data change:

control.setting.bind( updateTitle );

Also, I needed to tap into widget-added event with the following line of CODE:

$( document ).on( 'widget-added', add_widget );

Common for Customizer & wp-admin -> Appearance -> Widgets:

Both the above mentioned JavaScript files trigger widget-updated event when an widget is updated. Although customizer does it immediately with AJAX, while traditional Widget admin does it after you Click the Save button. I've used the following line of CODE for this:

$( document ).on( 'widget-updated', modify_widget );

Considerations for wp-admin -> Appearance -> Widgets:

Contrary to the customizer, traditional Widgets admin loads the Widget Control UI with PHP, so I've traversed the UI HTML to make the initial changes like this:

$( '#widgets-right div.widgets-sortables div.widget' ).each( function() { // code } ); 

Custom Plugin CODE:

Following is a complete Plugin with JavaScript based solution that will work both in wp-admin -> Appearance -> Widgets and Customizer -> Widgets:

wpse-widget-control.php Plugin PHP file:

<?php
/**
 *  Plugin Name: Widget Control
 *  Plugin URI: https://wordpress.stackexchange.com/questions/261042/how-to-influence-the-information-displayed-on-widget-inside-wp-admin
 *  Description: Display additional info on Widget Heading in wp-admin & customizer using JS
 *  Author: Fayaz
 *  Version: 1.0
 *  Author URI: http://fmy.me/
 */

    if( is_admin() ) {
        add_action( 'current_screen', 'wpse261042_widget_screen' );
    }

    function wpse261042_widget_screen() {
        $currentScreen = get_current_screen();
        if( $currentScreen->id === 'customize' ) {
            add_action( 'customize_controls_enqueue_scripts', 'wpse261042_customizer_widgets', 99 );
        }
        else if( $currentScreen->id === 'widgets' ) {
            add_action( 'admin_enqueue_scripts', 'wpse261042_admin_widgets', 99 );
        }
    }

    function wpse261042_customizer_widgets() {
        wp_enqueue_script( 'custom-widget-heading', plugin_dir_url( __FILE__ ) . 'custom-widget-heading.js', array( 'jquery', 'customize-widgets' ) );
    }

    function wpse261042_admin_widgets() {
        wp_enqueue_script( 'custom-widget-heading', plugin_dir_url( __FILE__ ) . 'custom-widget-heading.js', array( 'jquery', 'admin-widgets' ) );
    }

custom-widget-heading.js JavaScript file:

(function( wp, $ ) {
    var compare = {
        // field to compare
        field: 'title',
        // value to be compared with
        value: 'yes',
        // heading if compare.value matches with compare.field value
        heading: ' <i>(mobile/desktop)</i> ',
        // alternate heading
        alt_heading: ' <i>(desktop only)</i> ',
        // WP adds <span class="in-widget-title"></span> in each widget heading by default
        heading_selector: '.in-widget-title'
    };

    var widgetIdSelector = '> .widget-inside > form > .widget-id';
    // heading is added as prefix of already existing heading, modify this as needed
    function modify_heading( $elm, isMain ) {
        var html = $elm.html();
        if ( html.indexOf( compare.heading ) == -1 && html.indexOf( compare.alt_heading ) == -1 ) {
            if( isMain ) {
                $elm.html( compare.heading + html );
            }
            else {
                $elm.html( compare.alt_heading + html );
            }
        }
    };
    function parseFieldSelector( widgetId ) {
        return 'input[name="' + widgetIdToFieldPrefix( widgetId ) + '[' + compare.field + ']"]';
    };

    // @note: change this function if you don't want custom Heading change to appear for all the widgets.
    // If field variable is empty, then that means that field doesn't exist for that widget.
    // So use this logic if you don't want the custom heading manipulation if the field doesn't exist for a widget
    function modify_widget( evt, $widget, content ) {
        var field = null;
        var field_value = '';
        if( content ) {
            field = $( content ).find( parseFieldSelector( $widget.find( widgetIdSelector ).val() ) );
        }
        else {
            field = $widget.find( parseFieldSelector( $widget.find( widgetIdSelector ).val() ) );
        }
        if( field ) {
            field_value = ( ( field.attr( 'type' ) != 'radio' && field.attr( 'type' ) != 'checkbox' )
                          || field.is( ':checked' ) ) ? field.val() : '';
        }
        modify_heading( $widget.find( compare.heading_selector ), field_value == compare.value );
    }

    function parseWidgetId( widgetId ) {
        var matches, parsed = {
            number: null,
            id_base: null
        };
        matches = widgetId.match( /^(.+)-(\d+)$/ );
        if ( matches ) {
            parsed.id_base = matches[1];
            parsed.number = parseInt( matches[2], 10 );
        } else {
            parsed.id_base = widgetId;
        }
        return parsed;
    }
    function widgetIdToSettingId( widgetId ) {
        var parsed = parseWidgetId( widgetId ), settingId;
        settingId = 'widget_' + parsed.id_base;
        if ( parsed.number ) {
            settingId += '[' + parsed.number + ']';
        }
        return settingId;
    }
    function widgetIdToFieldPrefix( widgetId ) {
        var parsed = parseWidgetId( widgetId ), settingId;
        settingId = 'widget-' + parsed.id_base;
        if ( parsed.number ) {
            settingId += '[' + parsed.number + ']';
        }
        return settingId;
    }
    var api = wp.customize;
    if( api ) {
        // We ate in the customizer
        widgetIdSelector = '> .widget-inside > .form > .widget-id';
        api.bind( 'ready', function() {
            function add_widget( evt, $widget ) {
                var control;
                control = api.control( widgetIdToSettingId( $widget.find( widgetIdSelector ).val() ) );

                function updateTitle( evt ) {
                    modify_widget( null, $widget );
                };
                if ( control ) {
                    control.setting.bind( updateTitle );
                }
                updateTitle();
            };
            api.control.each( function ( control ) {
                if( control.id &&  0 === control.id.indexOf( 'widget_' ) ) {
                    api.section( control.section.get(), function( section ) {
                        function onExpanded( isExpanded ) {
                            if ( isExpanded ) {
                                section.expanded.unbind( onExpanded );
                                modify_widget( null, control.container.find( '.widget' ), control.params.widget_content );
                            }
                        };
                        if ( section.expanded() ) {
                            onExpanded( true );
                        } else {
                            section.expanded.bind( onExpanded );
                        }
                    } );
                }
            } );
            $( document ).on( 'widget-added', add_widget );
        } );
    }
    else {
        // We are in wp-admin -> Appearance -> Widgets
        // Use proper condition and CODE if you want to target any page builder
        // that doesn't use WP Core Widget Markup or Core JavaScript
        $( window ).on( 'load', function() {
            $( '#widgets-right div.widgets-sortables div.widget' ).each( function() {
                modify_widget( 'non-customizer', $( this ) );
            } );
            $( document ).on( 'widget-added', modify_widget );
        } );
    }
    $( document ).on( 'widget-updated', modify_widget );
})( window.wp, jQuery );

Plugin Usage:

Note: With the above sample Plugin CODE as it is, if you set the title of a Widget to yes, then it'll show (mobile/desktop) within the Widget Control UI heading, all the other Widget's will have (desktop only) in the Heading. In the customizer the change will be immediate, in wp-admin -> widgets the change will show after you save the changes. Of course you can change this behaviour by modifying the CODE (in JavaScript) to do the heading change for a different field_name or to only show that specific heading to some widgets and not to all of them.

For example, say you have a field named use_mobile, and you want to set the heading to (mobile/desktop) when it's set to yes. Then set the compare variable to something like:

var compare = {
    field: 'use_mobile',
    value: 'yes',
    heading: ' <i>(mobile/desktop)</i> ',
    alt_heading: ' <i>(desktop only)</i> ',
    heading_selector: '.in-widget-title'
};

Also, if you want to change the entire heading (instead of just .in-widget-title), then you may change the heading_selector setting along with the correct markup for heading & alt_heading to do so. The possibilities are endless, but make sure WordPress core CODE doesn't produce any error if you want to be too much creative with the resulting markup.

Page builder integration:

Whether or not either of these solutions will work for a page builder will depend on that page builder implementation. If it uses WordPress supplied methods to load Widget Control UI then it should work without any modification, otherwise similar (but modified) implication may be possible for page builders as well.

3
  • hey @Fayaz going to test the jquery approach since this is the only way (So it seems) i can write a "fix it all" solution. Will get back to you soon.
    – Sagive
    Commented Mar 31, 2017 at 20:51
  • 1
    thanks for your help. This is the only apparoach that works.
    – Sagive
    Commented Apr 1, 2017 at 20:40
  • @SagiveSEO I'm glad it worked for you :)
    – Fayaz
    Commented Apr 1, 2017 at 21:05
3

Let's first see whether it is possible to change the information that is displayed in the widget titles in admin. This list is generated by wp_list_widget_controls, which calls on dynamic_sidebar, which contains a filter dynamic_sidebar_params to change the parameters in the controls, including the title. Let's try that:

add_filter ('dynamic_sidebar_params','wpse261042_change_widget_title');
function wpse261042_change_widget_title ($params) {
  $string = ' Added info';
  $params[0]['widget_name'] .= $string;
  return $params;
  }

The $string is not exactly in the place where you point at, but I'd say it's good enough.

Now, we need to replace $string with some information from inside the current widget. Luckily, we know which widget we are in, because $params also contains the widget_id. I'll refer to this answer for an explanation how you use the widget_id to retrieve widget data. Here we go:

 // we need to separate the widget name and instance from the id
 $widget_id = $params[0]['widget_id'];
 $widget_instance = strrchr ($widget_id, '-');
 $wlen = strlen ($widget_id);
 $ilen = strlen ($widget_instance);
 $widget_name = substr ($widget_id,0,$wlen-$ilen);
 $widget_instance = substr ($widget_instance,1);
 // get the data
 $widget_instances = get_option('widget_' . $widget_name);
 $data = $widget_instances[$widget_instance];

Now the array $data contains the instances of the widget and you can choose which one you want to pass to $string in the function.

4
  • Thank you for you reply. Can you provide a complete function? i dont understand how the two blocks of code you provided play toghter... , so.. there is no way to hook to the widget itself? only via the sidebar holding it? - again - thank you for you answer - its really helpfull.
    – Sagive
    Commented Mar 28, 2017 at 13:46
  • No, I can't, because I don't know what your widget looks like. The second block goes into the first block before the line $string = ' Added info';. But you have to go from $data to $string yourself. Use var_dump on $data to see which data you want to assign to $string.
    – cjbj
    Commented Mar 28, 2017 at 14:04
  • (and no, you cannot put something in the widget code itself to do this, because by the time this code get evaluated the widget admin page is almost done already)
    – cjbj
    Commented Mar 28, 2017 at 14:05
  • it works for the sidebar - however i cant seem to use any html in that string. But, thank you... trying to find a workaround solution that would also work inside page builders and would allow to integrate some HTML in order to make it more visual for the client / the user.
    – Sagive
    Commented Mar 28, 2017 at 18:06
1

WordPress already has a similar feature built into the widgets UI. See for example how the "Title" value input by the user is appended to the title of this search widget:

Search widget with title appended

The code that does this is found in wp-admin/js/widgets.js:

appendTitle : function(widget) {
    var title = $('input[id*="-title"]', widget).val() || '';

    if ( title ) {
        title = ': ' + title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }

    $(widget).children('.widget-top').children('.widget-title').children()
            .children('.in-widget-title').html(title);

},

It finds the input element within a widget that has an id attribute ending in -title, and appends the value entered in that input to the text in the widget title bar.

So, if the setting that you have in mind is based on an input field (no matter whether the type is text or radio, etc.), you just need to give it an id ending in -title, and WordPress will take care of the rest.

And this way, the string in the title bar will automatically be updated when the user modifies the setting.

1
  • thats a great idea... runing some test on the idea - this would include some jquery but that might not be so bad.
    – Sagive
    Commented Mar 29, 2017 at 6:38

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.