3

This is really a two stage question, for not very relevant context you can look at this question - How to customize a shortcode using the customizer. In brief I got a widget and a shortcode that implement the same functionality and that is customized in the customizer by adapting the form generated for the widget into a customizer section.

Now I would like to use partial refresh when possible, but some ssettings require CSS and/or JS changes and a full refresh is a must to reflect changes (if you want to avoid writting crazy JS code just for that).

So the easier part of the question is the shortcode section - how to have two settings in the saame section with one doing a partial refresh while the other doing a full one.

The advanced part is to have it working for the widget as well.

To make it less theoretical, here is the code in question - https://github.com/tiptoppress/category-posts-widget/tree/4.7.1

That is a "recent posts" type of widget which lets you specify filtering criteria based on wp_query settings, therefor some settings just require the trip to the server to produce a "preview". Some other setting control the display by inserting CSS rules, while some other setting - browser side cropping, need JS for implementation and the JS need to be activated per widget, and for this the ids of the widgets are enqueued.

Partials are good solution for the wp_query related part, but not for the CSS. In theory you can enqueue JS in the partial (I assume), but it is not obvious how to dequeue it.

The question here is not really if it is possible to hack something that will make it work, as given enough effort everything can be massaged into a working state, the problem is to do it in a way which will not violate DRY and KISS principals and show the user exactly what he will see on the front end and not just an approximation. In english - I don't want to fork how the widget's front end works just so I will be able to claim that I use partial as the difference in actual user UX between a refresh and a partial is not that big to justify forking the front end code.

(yes this is kind of two questions in one, but maybe the answers are not as different as I suspect they will be)

  • Can you update your question with the initial code you have that is currently doing the full refresh? – Weston Ruter Nov 24 '16 at 22:26
  • @WestonRuter, I have updated the question to give more context, but basically the widget uses refresh as we didn't set any other transport method for it. – Mark Kaplun Nov 25 '16 at 5:54
  • I'm going to add a full answer here, probably tomorrow, but I worked out an initial demonstration to show the conditional full-refresh when enqueued dependencies change for a given example widget, using a minimal amount of code. Here's the standalone demo plugin: gist.github.com/westonruter/d4a21d8c891974e808975dcdfe97625d – Weston Ruter Nov 26 '16 at 8:43
  • @WestonRuter,looking at the gist ... css is kinda easier because as you have done it, you can override previous rules but using the same rules with a different values, just later. I assume that with CSS insertion into the DOM CSS rules behave like they are the last rule in the html and as I am right now believing in inserting small pices of CSS into the HEAD instead of adding them in files it will not violate for me the DRY principal. – Mark Kaplun Nov 27 '16 at 3:57
  • JS is harder (I think) because JS scripts will usually set some event handlers, and those are harder to remove, and since you mostly do not need to remove handlers, you will need to write non trivial extra code only to be able to get partial refresh working. – Mark Kaplun Nov 27 '16 at 4:07
6

tl;dr See standalone example plugin that demonstrates initiating a full refresh when a selective refresh request enqueues different scripts or styles and also which re-initializes JS behaviors for selectively-refreshed partial placement containers.


I understand the goal to be to minimize the amount of logic required in the customizer to preview changes to updated partials that enqueue different scripts and styles based on a setting change. The solution here has been accounted for in the design of selective refresh in the customizer. In particular, when a partial is rendered it does so in the context of the URL on which it will be displayed. This means that all of the scripts and styles enqueued for that given URL should be enqueued when the partial is rendered. To this end, the customize_render_partials_before action has the following docs:

Fires immediately before partials are rendered.

Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts and styles which may get enqueued in the response.

And eventually core may go ahead and do wp_enqueue_scripts() itself by default. For the initial implementation of selective refresh, however, the enqueueing was left to plugins.

Once the scripts and styles have been enqueued, the need is then to communicate back to the JS in the page which assets have been enqueued in the response. This is what the customize_render_partials_response filter is for, as its docs indicate:

Filters the response from rendering the partials.

Plugins may use this filter to inject $scripts and $styles, which are dependencies for the partials being rendered. The response data will be available to the client via the render-partials-response JS event, so the client can then inject the scripts and styles into the DOM if they have not already been enqueued there.

If plugins do this, they'll need to take care for any scripts that do document.write() and make sure that these are not injected, or else to override the function to no-op, or else the page will be destroyed.

Plugins should be aware that $scripts and $styles may eventually be included by default in the response.

The filtered $response array is exposed in JavaScript via a render-partials-response event triggered on wp.customize.selectiveRefresh. So to initiate a full refresh when a selective refresh request enqueues assets not already on the page, what is needed is to first export the handles for enqueued scripts and styles for the initial page load, and then in the render-partials-response JS event check to see if the enqueued script and style handles during the selective refresh request are the same that were initially enqueued on the page. If they differ, then the JS just needs to call wp.customize.selectiveRefresh.requestFullRefresh(). The following JS would be enqueued only in the customizer preview:

/* global wp, _ */
/* exported WPSE_247251 */
var WPSE_247251 = (function( api ) {

    var component = {
        data: {
            enqueued_styles: {},
            enqueued_scripts: {}
        }
    };

    /**
     * Init.
     *
     * @param {object} data Data exported from PHP.
     * @returns {void}
     */
    component.init = function init( data ) {
        if ( data ) {
            _.extend( component.data, data );
        }
        api.selectiveRefresh.bind( 'render-partials-response', component.handleRenderPartialsResponse );
    };

    /**
     * Handle render-partials-response event, requesting full refresh if newly-enqueued styles/styles differ.
     *
     * @param {object} response Response.
     * @returns {void}
     */
    component.handleRenderPartialsResponse = function handleRenderPartialsResponse( response ) {
        if ( ! response.wpse_247251 ) {
            return;
        }
        if ( ! _.isEqual( component.data.enqueued_styles, response.wpse_247251.enqueued_styles ) ) {
            api.selectiveRefresh.requestFullRefresh();
        }
        if ( ! _.isEqual( component.data.enqueued_scripts, response.wpse_247251.enqueued_scripts ) ) {
            api.selectiveRefresh.requestFullRefresh();
        }
    };

    return component;
})( wp.customize );

An inline script can be added in PHP to export the enqueued_scripts and enqueued_styles by then calling WPSE_247251.init( data ).

When a partial is selectively refreshed, a bit more is needed to ensure that any JS behaviors associated with the container are re-initialized. Specifically, the JS should be engineered so that there is a function that is responsible for finding elements to set up in the DOM. By default this function should look at the entire body. But then the JS can check to see if it is running in the context of the customizer preview, and if so, listen for the partial-content-rendered event triggered on wp.customize.selectiveRefresh to then call that function passing in placement.container as the scope for initializing rather than the entire body:

if ( 'undefined' !== typeof wp && 'undefined' !== typeof wp.customize && 'undefined' !== typeof wp.customize.selectiveRefresh ) {
    wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
        initializeAllElements( $( placement.container ) );
    } );
}

So again, see a standalone example plugin that demonstrates initiating a full refresh when when a selective refresh request enqueues different scripts or styles and also which re-initializes JS behaviors for selectively-refreshed partial placement containers.

In closing, if WordPress themes could just be implemented with something like React we could avoid adding special support for selectively refreshing elements on the page since the changes to the state would automatically cause the desired changes in the DOM. Since we're not there yet (although prototypes are being worked on for this) we have to settle for the next best experience for quickly refreshing parts of a page with the minimal extra effort for developers: selective refresh.

  • +1 for the detailed explanation that will probably be useful to many, but one concern that I should have clarified in the question and comments is that doing a decision path like "request a partial" -> "inspect result" > "request full refresh" ends doing two server requests instead of one, and with wordpress AJAX being generally slow, it might end up with twice the time it would take a full refresh, and for half the time, IIRC, while the selective refresh is attempted, there is no indication to the user what is going on. Maybe this is a core bug that needs to be solved – Mark Kaplun Nov 28 '16 at 6:31
  • Anyway, if it is deemed the cost of the two requests is not that bad, will it not be better/easier to just fail the response for the partial request to trigger the full refresh? or did I miss something? – Mark Kaplun Nov 28 '16 at 6:35
  • “while the selective refresh is attempted, there is no indication to the user what is going on” — when an element is being selectively-refreshed, the element has an transparency added to it. The element gets a class name when it is being refreshed. – Weston Ruter Nov 29 '16 at 7:34
  • It's true that this approach results in two requests, but this is the price to pay for KISS/DRY. It could indeed be an improvement to return false in the partial refresh response to force a refresh instead of returning the full placements that get injected into the DOM and then do a full refresh. But this wouldn't reduce the number of requests at all. In order to implement this, the partial refresh requests would need to include the page's enqueued script/style handles as the requested partials' placement contexts so that they'd be available for comparison on the server instead of client. – Weston Ruter Nov 29 '16 at 7:39
  • accepting it as a long "not possible right now" answer to the original question ;) – Mark Kaplun Nov 29 '16 at 15:05
0

I wold like to add some notes to certain parts of this question.

Mark set a downvote for my answer of injecting React to WordPress. React is present for 5 years now and it is relatively stable. These words in @WestonRuter answer I like the most.

In closing, if WordPress themes could just be implemented with something like React we could avoid adding special support for selectively refreshing elements on the page since the changes to the state would automatically cause the desired changes in the DOM. Since we're not there yet (although prototypes are being worked on for this) we have to settle for the next best experience for quickly refreshing parts of a page with the minimal extra effort for developers: selective refresh.

To get to the stage where we can live preview (in the Preview pane) anything we need capable JavaScript. React looks like a missing part, but we actually need to create settings, controls, sections, and panels live in JavaScript and to update the WordPress manager global, which seems easy.

@WestonRuter may correct me.

The promises:

  • KISS (keep-it-simple) and
  • DRY (do-not-repeat-yourself).

These are from the common sense, but these don't reflect the state we have now. We repeat and must repeat since our models are dual. They exist in both JavaScript and WordPress PHP world.

Here is one section of the PHP classes we have.

enter image description here

Similar models exist in JavaScrpt at least to hold the data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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