Ask your WordPress questions! Pay money and get answers fast! (more info)

Access Customizer widget instance form WordPress

  • SOLVED

Question updated: (21/03/2015 - 12:58)

When previewing widgets via the WordPress Customizer the widget instance form is not available. I had a similar issue on the widgets admin age but solved this by 'forcing' a save widget operation the first time a widget was added to a widget area. More on this WordPress issue can be found here: https://core.trac.wordpress.org/ticket/14686

Due to the preview nature of the Customizer the widget can't be 'force' saved as a user may not want to keep it after previewing. So another method must be employed.

Interestingly when previewing a widget via the Customizer the widget instance form IS sent to the preview window. This can be seen using browser inspector tools where a full ID number is specified in the widget HTML. However, in the Customizer window the widget instance form will NOT be updated unless you click "Save & Publish" and reload the Customizer window.

The issue can be replicated and demonstrated via a simple plugin below.

Usage instructions:
1. Activate the plugin.
2. Go to Appearance > Customizer and add the test widget to any widget area. Notice the widget instance form has no full id number and clicking the "Toggle Advanced" button doesn't do anything. However, if you use browser inspector tools you'll see that in the preview window the widget has a full ID specified in the HTML.
3. Click 'Save & Publish' and reload the Customizer page.
4. Expand the widget in the Customizer. You will now see it has the same full id number assigned (to the front end preview). Also, when you now click the "Toggle Advanced" button will show/hide the hidden section. I need this to happen the first time the widget is added via the Customizer without the widget being permanently (i.e. via a force widget save operation) added unless the user clicks "Save & Publish".

<?php
/*
Plugin Name: Widget Instance Test
Description: Tests a minimum widget implementation.
Author: David Gwyer
Version: 0.1
Author URI: http://wpgothemes.com
*/

// test widget class
class test_widget extends WP_Widget{

function test_widget(){
$this->WP_Widget(false, 'Widget Instance Test');
}

function update( $new_instance, $old_instance ) {
$instance = $old_instance;
$instance['title'] = strip_tags($new_instance['title']);
return $instance;
}

function widget($args, $instance){
extract( $args );

echo $before_widget;

$title = empty( $instance['title'] ) ? '' : $instance['title'];
if ( isset($title) && !empty( $title ) ) echo $args['before_title'] . $title . $args['after_title'];
echo 'front-end';

echo $after_widget;
}

function form($instance){
$instance = wp_parse_args( (array) $instance, array( 'title' => 'Default Title' ) );
$title = strip_tags($instance['title']);
?>

<script language="javascript">
jQuery(document).ready(function ($) {

var wpgo_connect_number = '<?php echo $this->number; ?>';

$('div#wpgo-advanced-btn-' + wpgo_connect_number).click(function () {
$('div#wpgo-advanced-' + wpgo_connect_number).toggle('slow');
});

});
</script>

<style>
.wpgo-advanced { display: none; }
</style>

<p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>" /></p>

<div style="width:103px;padding:6px 10px;background-color:#ccc;cursor:pointer;" id="wpgo-advanced-btn-<?php echo $this->number; ?>">Toggle Advanced</div>
<div class="wpgo-advanced" id="wpgo-advanced-<?php echo $this->number; ?>">
Advanced section...
</div>
<p>Debug Info:</p>
<?php
print_r($this);
}
}
add_action('widgets_init', create_function('', 'register_widget("test_widget");'));

Answers (2)

2015-03-20

Reigel Gallarde answers:

I sent you a message ;)


Reigel Gallarde comments:

if what you are looking for is a way to add javascript functions to the added widget, I tried this code to be working...

$( document ).on( "widget-added", function(event, widget) {

// match the correct widget..
if (widget[0].id.match(/reigel-colorpicker-widget/)) {

// this is the html mark up from the "function form($instance){}" in a class that extends WP_Widget
var $form $(widget).find('.form');

// this is the way to access elements inside the form... in this example, I've created a colorpicker...
$form.find('.colorpicker').colorpicker();
}

});


Reigel Gallarde comments:

fixed some typos...

$( document ).on( "widget-added", function(event, widget) {

// match the correct widget.. class reigel-colorpicker-widget extends WP_Widget
if (widget[0].id.match(/reigel-colorpicker-widget/)) {

// this is the html mark up from the "function form($instance){}" in a class that extends WP_Widget
var $form = $(widget).find('.form');

// this is the way to access elements inside the form... in this example, I've created a colorpicker...
$form.find('.colorpicker').colorpicker();

}

});


Reigel Gallarde comments:

$(document).on("widget-added", function (event, widget) {

// match the correct widget.. class reigel-colorpicker-widget extends WP_Widget
if (widget[0].id.match(/reigel-colorpicker-widget/)) {

// this is the html mark up from the "function form($instance){}" in a class that extends WP_Widget
var $form = $(widget).find('.form');

// this is the way to access elements inside the form... in this example, I've created a colorpicker...
$form.find('.colorpicker').colorpicker();

}

});


David Gwyer comments:

This isn't what I am looking for. I need a way to 'force' save the newly added widget via the Customizer.

As stated I already did this for the WordPress admin widgets page (see code in the original question) but I need similar code to do the same thing when first adding a widget via the Customizer.


Reigel Gallarde comments:

force save like clicking the Save and Publish button? or just the apply button of the widget?


David Gwyer comments:

Please see my updated answer for a full test plugin to demonstrate exactly what the issue is.


Reigel Gallarde comments:

here's what I've got working so far...

jQuery(document).ready( function($) {

$( document ).on( 'widget-added', function(event, widget) {
// click the apply button... can't find another way.. wpWidgets variable is not available...
$(widget).find('.widget-control-save').click();
});

$(document).ajaxSuccess(function (event, XMLHttpRequest, ajaxOptions) {

var request = {}, pairs = ajaxOptions.data.split('&'), i, split, widget;

for (i in pairs) {

split = pairs[i].split('=');

request[decodeURIComponent(split[0])] = decodeURIComponent(split[1]);

}
if (request.action && (request.action === 'update-widget')) {

// when updating the widget in theme customizer, the ajax response contains all the data
// but it does not update the widget... weird... this next line of code will do that...
var obj = jQuery.parseJSON( XMLHttpRequest.responseText );
$('#customize-control-widget_'+request['widget-id']+ ' .widget-content').html(obj.data.form);

}
});
});


for this to work, you must save this in a javascript file and enqueue the script to theme customizer....
https://codex.wordpress.org/Theme_Customization_API#Step_2:_Create_a_JavaScript_File


Reigel Gallarde comments:

found a shorter way of updating the widget...


$( document ).on( 'widget-added', function(event, widget) {
$(widget).find('.widget-control-save').click();
});
$( document ).on( 'widget-synced', function(event,widget ,form) {
$('#' + widget[0].id + ' .widget-content').html(form);
});


I removed the ajax thingy...


David Gwyer comments:

Still doesn't work. And you have to be careful firing off code for EVERY 'widget-added' event as this event fires when the Customizer loads for all widgets used in the Customizer.


Reigel Gallarde comments:

I'm not really sure about what you mean by "It's not working."

I have here a video showing that it's working... (at least it shows that $widget->id or $widget->number is already available.)
The video demonstrate first the status of the default behavior of the customizer. And then later on added the code to show what we wanted...

[[LINK href="https://drive.google.com/file/d/0B00G_cWB-XOURC1PcEhpQV9Gazg/view?usp=sharing"]]https://drive.google.com/file/d/0B00G_cWB-XOURC1PcEhpQV9Gazg/view?usp=sharing[[/LINK]]

Firing also "widget-added" can be filtered to fire only to your specific widget... you use "if" like this: if (widget[0].id.match(/reigel-colorpicker-widget/)) {

where reigel-colorpicker-widget is a class name that extends to WP_Widget.

what's not working for you? $widget->id or $widget->number are already available as what the video demonstrated..


David Gwyer comments:

Hi Reigel,

Thanks for your efforts on this.

What I meant by it's not working is that the Customizer is not loading properly using the code you posted. In the preview window I'm seeing a pre-loader icon spinning endlessly so I'm not sure what's going on there.

I'm using this jQuery code:

jQuery(document).ready(function ($) {

$( document ).on( 'widget-added', function(event, widget) {
$(widget).find('.widget-control-save').click();
});

$( document ).on( 'widget-synced', function(event,widget ,form) {
$('#' + widget[0].id + ' .widget-content').html(form);
});

});


This is added to a separate file and enqueued via the 'customize_controls_print_scripts' WordPress hook.

Anyway, as I mentioned in the updated description because of the very nature of the Customizer we can't 'force' save the widget because the user may only want to preview the widget and not save it.

In other words if the user decides to quit the Customizer without manually clicking "Save & Publish" then the widget should be removed (which is the normal default behavior). If we do a 'force' save then will the widget be removed if the user doesn't click "Save & Publish"?

I have updated the question description again with a better test widget, wrapped in a plugin. When adding the new widget via the Customizer the "Toggle Advanced" button ONLY works when you click "Save & Publish" and then reload the Customizer page.

<strong>The core problem here is that this needs to work the first time the widget is added BUT on the condition that the widget will not be made permanent unless the user manually clicks the "Save & Publish" button.</strong>

Also the default text only displays in the preview window if you update the text field.


Reigel Gallarde comments:

no, my code will only hit apply... not save and publish.. I get that from your post... so I did not hit save and publish... as what you can see in the video... I have pressed manually the save and publish button... I'm not sure why you are seeing the "pre-loader"... as in my theme, I don't see it... also, you can look at the developer tools on chrome to see any activity...

again, this code will not do "Save and Publish"... only "Apply" in each widget... I will do another video if you need one.


Reigel Gallarde comments:

I have created another video using your latest widget sample...

[[LINK href="https://drive.google.com/file/d/0B00G_cWB-XOUdkJUT29FRVM4Q3c/view?usp=sharing"]]https://drive.google.com/file/d/0B00G_cWB-XOUdkJUT29FRVM4Q3c/view?usp=sharing[[/LINK]]

and yes we are using the same action customize_controls_print_scripts.


David Gwyer comments:

Hi Reigel,

That sounds fine if the widget doesn't persist. i.e. is removed if the user quits the Customizer without saving. That is important. Have you tried quitting the Customizer without saving? Is the widget removed?

Unfortunately I can't test your code as it prevents the Customizer from loading. What hook are you adding the script via? I'm using 'customize_controls_print_scripts'.

Can you wrap your code in a plugin that I can test with the test widget plugin my end on a default WordPress theme? You can add it to the test widget plugin if you like, as long as you test it and confirm it works your end.


Reigel Gallarde comments:

yes quitting the customizer is same as refreshing the page... and yes, the code does not save it... only untill Save and Publish is clicked...


David Gwyer comments:

OK, I just need to be able to test the code my end. As I said can you wrap it in a plugin or add it to the test widget plugin?


Reigel Gallarde comments:

i did nothing more than just adding the test widget in my functions.php... created a widget.js file and enqueue it using customize_controls_print_scripts action hook...

can you check your console for JavaScript error?


David Gwyer comments:

I will test and get back to you.


David Gwyer comments:

Still doesn't work. What version of WordPress are you using? I'm using the latest trunk running the Twenty Ten default WordPress theme. The JS error in console reports:
"Uncaught TypeError: undefined is not a function". When I uncomment your code the error goes away.

Here is an updated version of the plugin I'm using which includes all the code in two files.

File: widget-instance-test.php
====================

<?php
/*
Plugin Name: Widget Instance Test
Description: Tests a minimum widget implementation.
Author: David Gwyer
Version: 0.1
Author URI: http://wpgothemes.com
*/

function wpgo_load_widgets_script() {
wp_enqueue_script( 'admin_widget_bug_fix', plugins_url( 'widget-bug-fix.js', __FILE__ ), array( 'jquery' ) );
}
add_action( 'customize_controls_print_scripts', 'wpgo_load_widgets_script' );

// test widget class
class test_widget extends WP_Widget{

function test_widget(){
$this->WP_Widget(false, 'Widget Instance Test');
}

function update( $new_instance, $old_instance ) {
$instance = $old_instance;
$instance['title'] = strip_tags($new_instance['title']);
return $instance;
}

function widget($args, $instance){
extract( $args );

echo $before_widget;

$title = empty( $instance['title'] ) ? '' : $instance['title'];
if ( isset($title) && !empty( $title ) ) echo $args['before_title'] . $title . $args['after_title'];
echo 'front-end';

echo $after_widget;
}

function form($instance){
$instance = wp_parse_args( (array) $instance, array( 'title' => 'Default Title' ) );
$title = strip_tags($instance['title']);
?>

<script language="javascript">
jQuery(document).ready(function ($) {

var wpgo_connect_number = '<?php echo $this->number; ?>';

$('div#wpgo-advanced-btn-' + wpgo_connect_number).click(function () {
$('div#wpgo-advanced-' + wpgo_connect_number).toggle('slow');
});

});
</script>

<style>
.wpgo-advanced { display: none; }
</style>

<p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>" /></p>

<div style="width:103px;padding:6px 10px;background-color:#ccc;cursor:pointer;" id="wpgo-advanced-btn-<?php echo $this->number; ?>">Toggle Advanced</div>
<div class="wpgo-advanced" id="wpgo-advanced-<?php echo $this->number; ?>">
Advanced section...
</div>
<p>Debug Info:</p>
<?php
print_r($this);
}
}
add_action('widgets_init', create_function('', 'register_widget("test_widget");'));

File: widget-bug-fix.js
===============

jQuery(document).ready(function ($) {
$( document ).on( 'widget-added', function(event, widget) {
console.log('widget added');
$(widget).find('.widget-control-save').click();
});
$( document ).on( 'widget-synced', function(event,widget ,form) {
$('#' + widget[0].id + ' .widget-content').html(form);
});
});


Reigel Gallarde comments:

can you check what file and what line number the error is? or provide a screenshot please...


David Gwyer comments:

I have uploaded a video showing you what I see when trying to access the Customizer.

https://www.youtube.com/watch?v=SfKlbXUUu4M


Reigel Gallarde comments:

hmm first of.. Twenty Ten is not the default of the latest wordpress.. it's Twenty Fifteen.

okay, this is odd... I tried the Twenty Ten, and found out that it has a weird behavior compared to zerif theme that I was using....
The 'widget-added' is triggered even if that widget is already added (meaning, it was saved before).
But we don't want that behavior. We want it to just fire if we add a widget (meaning, when we click to add a widget ).

I found a fixed to this... by default, all currently added widgets are hidden. So what I did is to check if that widget is hidden. If it's hidden, that widget belongs to sa currently saved widgets to the theme... we don't want to fire 'widget-added' to that cause we already have their instance...

here a fixed code... for javascript only....

jQuery(document).ready( function($) {
$( document ).on( 'widget-added', function(event, widget) {
//console.log('widget added.') // will only fire every time you add a widget...
var $saveBtn = $(widget).find('.widget-control-save');
// let's check if the control for this widget is ready...
if ($saveBtn.closest('.control-section').is(':visible')) {
// if the control for this widget is ready, let's hit apply...
$saveBtn.click();
}
});
$( document ).on( 'widget-synced', function(e,widget ,form) {
$('#' + widget[0].id + ' .widget-content').html(form);
});
});


David Gwyer comments:

The solution must work for any theme of course. I'll test and get back to you.


David Gwyer comments:

Reigel,

You're a genius! The solution works fine. Although it's a shame that we need to employ such a 'hacky' solution to be able to access the widget instance form via the Customizer (and widget admin page). This is clearly a WordPress bug.

I do have an issue remaining with one of my widgets though. When you add it to a widget area via the Customizer and update any of the text fields the changes update automatically in the preview window for the FIRST time only. If you try and update any field again the change is ignored in the preview window.

If you hit Apply, then the changes take effect, and you can once again make a change to any of the fields which will get updated automatically in the preview window but subsequent changes are again ignored. I have no idea what is causing this issue but I mention it as it doesn't happen when your code is disabled.

However, based on your work so far I'd be happy to award you the money for this answer, and ask you to look into this widget issue as a separate problem. I actually have many other PHP & JavaScript tasks that need working on if you are available/interested. If so please send contact details.


Reigel Gallarde comments:

ok great... let's move on... :-)


David Gwyer comments:

Money awarded. :)

2015-03-20

Romel Apuya answers:

maybe trigger the save widget

$(document ).on("widget-added", function(event, widget) {
$(this).trigger('saved_widget);
});


Romel Apuya comments:

$(document ).on("widget-added", function(event, widget) {
$(this).trigger('saved_widget,widget);
});


David Gwyer comments:

This doesn't work.


David Gwyer comments:

I don't have any other plugins active either, as well as running the Twenty Ten default theme on the latest version of WordPress trunk.