Make WordPress Core

Opened 22 months ago

Last modified 3 months ago

#33507 accepted enhancement

Allow widget controls to be JS-driven

Reported by: westonruter Owned by: westonruter
Milestone: Future Release Priority: normal
Severity: normal Version: 3.9
Component: Widgets Keywords:
Focuses: javascript, performance Cc:

Description (last modified by westonruter)

Widgets currently have an almost complete reliance on PHP for all aspects of their behavior:

  • Generating the control form via WP_Widget::form()
  • Validating the instance data via WP_Widget::update()
  • Rendering the widget into the template via WP_Widget::widget()

The dependence on PHP can make managing widgets in WordPress relatively slow. In the Customizer, it can be excruciatingly slow since the widget form control is presented alongside the widget in the preview, and so the slow roundtrip time for sanitization and rendering is clear (especially with full-page refreshes of the Customizer, which is to be mitigated in #27355 via partial refreshes).

In the Customizer, making a change to a widget form field results in an update-widget Ajax call to pass the full form data to WordPress to pass it through the widget’s update callback for sanitization. The Ajax handler then passes this updated instance through the form callback, and the HTML output of the form callback then gets sent back in the Ajax response. (Look at the number of Ajax requests triggered when typing into a widget field.) The Customizer widget logic then tries to apply the sanitized instance data by aligning the input fields in the widget form shown in the Customizer with the input fields sent in the Ajax response. When the input fields are aligned (when the same inputs are present), then the inputs’ values will be updated to their sanitized values and a widget-synced jQuery event is triggered. But if the inputs cannot be aligned (which can happen easily if any of the fields are created dynamically), then it falls back to showing an Update button which will do a full replacement of the widget form, just as is done on the widgets admin page (and a widget-updated jQuery event is triggered). Then finally, once the widget instance is updated, then the Customizer preview can do its full page refresh with the widget instance previewed in the template.

Most of the above logic can be eliminated entirely if widget controls handled rendering of the controls and sanitization of the instance data purely with JavaScript, just like every other Customizer control normally behaves.

Widgets would hugely benefit from a revamp following patterns laid down in Customizer controls. Customizer controls were designed to be JS-driven from the start. A Customizer control gets associated with a Customizer setting, the control then applies the setting data onto the control’s template and the inputs are automatically synced back to the setting—this is accomplished via two-way data bindings provided by wp.customize.Element. So controls in the Customizer handle UI validation purely with JavaScript. There is no need for an Ajax request because the logic is in the client. (True this means that the sanitization logic has to be duplicated in PHP and JS, but this can be minimized by adopting a common schema for the input types.)

Naturally these new JS-driven widgets would need to be backwards compatible with existing widgets. We can allow a widget to opt-in to indicate it is JS-driven by supplying a new flag to the seldom-used $control_options param to WP_Widget::__construct() (which is used to pass the infamous width/height for wide widget form controls), for example:

When this is present, the Customizer would disable the existing update mechanism and defer to the control’s own logic.

Note that these new JS-driven widget controls in the Customizer would be implemented in a very similar way to how nav menu item controls have been implemented in the Customizer in 4.3. The overall widget control would be associated with a single widget instance setting. The widget’s setting would be just the JSON-serializable instance array (object) as opposed to a scalar value, and each property in the setting value would then get mapped to a different wp.customize.Element in the widget control: the inputs’ values would then get synced back into property of the setting object value by means of wp.customize.Element instances.

Some widget controls would still depend on the server for UI validation, for instance the RSS widget. However, only specific fields would need to be validated as opposed to the entire form. Note again that full server-side validation of the widget instance data would still be required. Ideally the duplication of logic here could be reduced by having a schema that both the JS and PHP validation logic could read from.

While the above focuses specifically on making the WP_Widget::form() and WP_Widget::update() both JS-driven, there may also be an opportunity to allow widgets to opt-in to JS-driven rendering of a given widget (i.e. implementing WP_Widget::widget(), in JS). This would make a lot of sense if the widget defined its template in Mustache (or Twig) and then allowed the instance array to be applied to that template either server-side or client-side. The effect for client-side would be postMessage instant previews of changes to widgets in the Customizer. The PHP-driven performant alternative to this would be partial-refresh as outlined in #27355.

By using JS-driven widgets, we will be able to use control templates for the widget controls as opposed to having to include a separate copy of the widget form with each control's params. This can drastically reduce the page weight, since the control template only needs to be included once.

By implementing JS-driven widget controls, the user experience of managing widgets would be vastly improved and the server load would be greatly reduced.

Depends on #35574 (Add REST API JSON schema information to WP_Widget)

See feature plugin: https://github.com/xwp/wp-js-widgets

Change History (24)

#1 @westonruter
22 months ago

  • Summary changed from Revamp widgets to be JS-driven to Allow widget controls to be JS-driven

#2 @westonruter
22 months ago

Aside: we need to make it easier to work with setting values that are objects. The usual pattern to update a setting with an object value is:

var value = wp.customize( 'foo' ).get();
value = _.clone( value );
value.someProperty = 'bar';
wp.customize( 'foo' ).set( value );

The cloning is needed because objects are passed by reference. So Setting.set( value ) should be updated to automatically clone any object passed as its value argument, so you could replace the above with:

wp.customize( 'foo' ).set( $.extend( 
    wp.customize( 'foo' ).get(),
    { someProperty: 'bar' }
) );

We could also consider adding a Setting.extend( obj ) which would take any properties in obj and set them on the existing cloned value, in a way similar to jQuery.extend(). You could then replace the above with:

wp.customize( 'foo' ).extend( { someProperty: 'bar' } );

#3 @wonderboymusic
22 months ago

Faster and backwards-compatible? Sounds good to me.

#4 @westonruter
22 months ago

  • Owner set to westonruter
  • Status changed from new to accepted

This ticket was mentioned in Slack in #core by wonderboymusic. View the logs.

22 months ago

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.

22 months ago

#7 @westonruter
22 months ago

  • Description modified (diff)

#8 @valendesigns
22 months ago

I'm loving this and will do whatever I can to help make it happen, with your guidance as usual. Making the Customizer more performant would go a long way to getting additional buy-in from the community as a whole. I think if we can rally around these kinds of modifications we're going to see a lot more developers using the Customizer than ever before. One of the major issues I believe most have is how long the load times can be, and with this issue, #27355, #33898, and #33901 all combined things will start to finally feel sane when considering using the Customizer for large scale web sites. @westonruter just let me know how I can help.

#9 @DrewAPicture
21 months ago

@westonruter Are you still planning to pursue this for 4.4 inclusion? We're about two weeks from beta 1 at this point.

This ticket was mentioned in Slack in #core by westonruter. View the logs.

21 months ago

#11 @westonruter
20 months ago

  • Milestone changed from 4.4 to Future Release

#12 @westonruter
17 months ago

  • Description modified (diff)

Dependency: #35574 (Add REST API JSON schema information to WP_Widget)

#13 @westonruter
16 months ago

As this depends on #35574, it seems this also depends on Fields API: we need a way to build widget form controls with JS and for widget form controls to be populated with the widget instance data directly from the JS model without any server round-trips. The only server communication needed would be to send the widget instance data to preview, which as of selective refresh (#27355) only requires re-generating the container for the element not the entire page: nevertheless, with JS-driven widgets it would also be possible to extend WidgetPartial to refresh a widget entirely with JS, for example applying the widget instance data to an Underscore template. There also seems to be a lot of overlap between such widgets and Shortcake, where perhaps they can both be distilled into the much-mused Content Blocks.

/cc @sc0ttkclark

This ticket was mentioned in Slack in #core-fields by westonruter. View the logs.

16 months ago

#15 @westonruter
15 months ago

  • Milestone changed from Future Release to 4.6

#16 @westonruter
14 months ago

Feature plugin where I'm prototyping this: https://github.com/xwp/wp-js-widgets

This ticket was mentioned in Slack in #core by westonruter. View the logs.

14 months ago

This ticket was mentioned in Slack in #core by chriscct7. View the logs.

13 months ago

#19 @westonruter
13 months ago

  • Milestone changed from 4.6 to Future Release

Punting. Feature plugin has made good progress, but not ready for core merge.

#20 @westonruter
9 months ago

  • Description modified (diff)

#21 @westonruter
6 months ago

I just released JS Widgets v0.2.0 which includes JS-driven adaptations of all the core widgets (aside from Links), as well as a Post Collection bonus widget. Another standalone widget plugin called Next Recent Posts Widget takes the concept of JS widgets a step further and uses client-side JS templating for rendering the widget as well, implementing instant updates to changes. In this widget, selective refresh used to obtain rendered REST API data, while the raw value is used until the rendered data is returned. So this means that when entering -- into the widget's title will result in -- appearing in the rendered widget while waiting for the server to return with that wptexturize applies. In this way it behaves similarly to #33738.

#22 @westonruter
5 months ago

JS Widgets v0.3.0 has been released: https://github.com/xwp/wp-js-widgets/tree/0.3.0#changelog

This release includes the important feature of ensuring that the JS Form components for the widgets can be used on the widgets admin screen in addition to the customizer. The Form JS component can now be used on any context, including the frontend or on the edit post screen. A next step is to prototype integrating JS Widgets with Shortcake to use the JS Widget Form interface as the shortcode UI, to allow any registered widgets to be used as Post Elements: https://github.com/xwp/wp-js-widgets/issues/11

This release also improves the UX of a widget's Save button on the widgets admin screen by turning it to “Saved” and disabled, similar to what is done in the customizer. See #23120.

Other changes include improvements to generating the JS templates for input fields based on the fields defined in the REST API schema.

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.

5 months ago

This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.

3 months ago

Note: See TracTickets for help on using tickets.