From beabf5f00b301c718b34a8eb806b7a69b928bc60 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 17 Dec 2016 23:55:40 -0800 Subject: [PATCH 1/2] Refactor body_selector, singular_only, and archive_only into fallback_dependent_selector * Allows partial selectors to have make use of post ID placeholders in the form of %d. * Selective refresh fallback behavior is changed to be prevented when changing a post that isn't referenced on a given template (via body_class or post_class). * Nevertheless, the selective refresh requests still are made so that the REST API Backbone models have the opportunity to update their rendered properties. * Adds initial support for Twenty Seventeen. --- js/customize-post-field-partial.js | 37 ++++++++++++++- js/customize-preview-posts.js | 37 ++++++++------- php/class-wp-customize-posts-preview.php | 31 +++++-------- ...-customize-posts-twenty-eleven-support.php | 2 +- ...customize-posts-twenty-fifteen-support.php | 2 +- ...stomize-posts-twenty-seventeen-support.php | 45 +++++++++++++++++++ ...customize-posts-twenty-sixteen-support.php | 2 +- ...ass-customize-posts-twenty-ten-support.php | 2 +- ...ustomize-posts-twenty-thirteen-support.php | 2 +- ...-customize-posts-twenty-twelve-support.php | 2 +- tests/data/themes/dummy/functions.php | 2 +- 11 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 php/theme-support/class-customize-posts-twenty-seventeen-support.php diff --git a/js/customize-post-field-partial.js b/js/customize-post-field-partial.js index 59a6abd..7b39d20 100644 --- a/js/customize-post-field-partial.js +++ b/js/customize-post-field-partial.js @@ -35,8 +35,6 @@ api.selectiveRefresh.partialConstructor.deferred.prototype.initialize.call( partial, id, args ); partial.addInstantPreviews(); - - // @todo If singular_only, and this is not the post singular post for this partial, then no refresh! }, /** @@ -123,6 +121,41 @@ return refreshPromise; }, + /** + * Handle fail to render partial. + * + * {@inheritdoc} + * + * @this {wp.customize.selectiveRefresh.partialConstructor.deferred} + * @returns {void} + */ + fallback: function postFieldPartialFallback() { + var partial = this, dependentSelector; + + /* + * Skip invoking fallback behavior for partials on documents that lack matches for + * the fallback dependent selector. The default fallback dependent selector is + * essentially checking to see if a body_class or post_class exists in the document + * which references the given post. If the dependent selector fails to match any + * elements, then the selector dependency fails and the partial should not be added. + * Note that the dependent selector could have been used as a determiner for whether + * the partial was added in the first place. However, this would have meant that no + * selective refresh requests would have been spawned by the change, and this would + * have meant that any Backbone models for the WP-API would not have had the chance + * to get the rendered updates from the server. + */ + dependentSelector = partial.params.fallbackDependentSelector; + if ( ! dependentSelector ) { + dependentSelector = '.hentry.post-%d, body.page-id-%d, body.postid-%d'; + } + dependentSelector = dependentSelector.replace( /%d/g, String( partial.params.post_id ) ); + if ( 0 === $( dependentSelector ).length ) { + return; + } + + api.selectiveRefresh.partialConstructor.deferred.prototype.fallback.call( partial ); + }, + /** * @inheritdoc */ diff --git a/js/customize-preview-posts.js b/js/customize-preview-posts.js index d05150d..d0140d0 100644 --- a/js/customize-preview-posts.js +++ b/js/customize-preview-posts.js @@ -36,7 +36,7 @@ // Add the partials. _.each( api.previewPosts.partialSchema( setting.id ), function( schema ) { - var partial, addPartial, matches, baseSelector; + var partial, matches, postId, postType, selectorBases; matches = schema.id.match( idPattern ); if ( ! matches ) { @@ -46,25 +46,28 @@ if ( api.selectiveRefresh.partial.has( schema.id ) ) { return; } + postType = matches[1]; + postId = parseInt( matches[2], 10 ); if ( schema.params.selector ) { - if ( ! schema.params.bodySelector ) { - baseSelector = '.hentry.post-' + String( parseInt( matches[2], 10 ) ) + '.type-' + matches[1]; + + selectorBases = [ + '.hentry.post-' + String( postId ) + ]; + if ( 'page' === postType ) { + selectorBases.push( 'body.page.page-id-' + String( postId ) ); } else { - baseSelector = '.postid-' + String( parseInt( matches[2], 10 ) ) + '.single-' + matches[1]; + selectorBases.push( 'body.postid-' + String( postId ) ); } - schema.params.selector = baseSelector + ' ' + schema.params.selector; - - addPartial = - ! schema.params.singularOnly && ! schema.params.archiveOnly || - schema.params.singularOnly && api.previewPosts.data.isSingular || - schema.params.archiveOnly && ! api.previewPosts.data.isSingular; + schema.params.selector = _.map( selectorBases, function( selectorBase ) { + var selector = selectorBase + ' ' + schema.params.selector; + selector = selector.replace( /%d/g, String( postId ) ); + return selector; + } ).join( ', ' ); - if ( addPartial ) { - partial = new api.selectiveRefresh.partialConstructor.post_field( schema.id, { params: schema.params } ); - api.selectiveRefresh.partial.add( partial.id, partial ); - addedPartials.push( partial ); - } + partial = new api.selectiveRefresh.partialConstructor.post_field( schema.id, { params: schema.params } ); + api.selectiveRefresh.partial.add( partial.id, partial ); + addedPartials.push( partial ); } else { partial = new api.selectiveRefresh.partialConstructor.post_field( schema.id, { params: schema.params } ); @@ -79,7 +82,7 @@ partial.refresh = function refreshWithoutSelector() { var deferred = $.Deferred(); if ( this.params.fallbackRefresh ) { - api.selectiveRefresh.requestFullRefresh(); + api.selectiveRefresh.requestFullRefresh(); // @todo Do partial.fallback()? deferred.resolve(); } else { deferred.reject(); @@ -209,7 +212,7 @@ } ); }; - wp.customize.selectiveRefresh.bind( 'render-partials-response', api.previewPosts.handleRenderPartialsResponse ); + api.selectiveRefresh.bind( 'render-partials-response', api.previewPosts.handleRenderPartialsResponse ); }; /** diff --git a/php/class-wp-customize-posts-preview.php b/php/class-wp-customize-posts-preview.php index 5de56c4..b6b6c5c 100644 --- a/php/class-wp-customize-posts-preview.php +++ b/php/class-wp-customize-posts-preview.php @@ -91,7 +91,6 @@ public function customize_preview_init() { add_action( 'parse_query', array( $this, 'ensure_page_for_posts_preview' ), 5 ); add_filter( 'customize_dynamic_partial_args', array( $this, 'filter_customize_dynamic_partial_args' ), 10, 2 ); add_filter( 'customize_dynamic_partial_class', array( $this, 'filter_customize_dynamic_partial_class' ), 10, 3 ); - add_filter( 'customize_posts_partial_schema', array( $this, 'filter_customize_posts_partial_schema' ) ); add_filter( 'the_posts', array( $this, 'filter_the_posts_to_tally_previewed_posts' ), 1000 ); add_filter( 'the_posts', array( $this, 'filter_the_posts_to_tally_orderby_keys' ), 10, 2 ); add_action( 'wp_footer', array( $this, 'export_preview_data' ), 10 ); @@ -1222,20 +1221,6 @@ function filter_customize_dynamic_partial_class( $partial_class, $partial_id, $p return $partial_class; } - /** - * Prevent fallback_refresh for select post fields. - * - * @todo There should be some more sophisticated logic for determining whether fallback_refresh is done. - * - * @param array $schema Schema. - * @return array Schema. - */ - function filter_customize_posts_partial_schema( $schema ) { - $schema['post_title']['fallback_refresh'] = false; - $schema['post_excerpt']['fallback_refresh'] = false; - return $schema; - } - /** * Filters get_edit_post_link to short-circuits if post cannot be edited in Customizer. * @@ -1321,22 +1306,19 @@ public function get_post_field_partial_schema( $field_id = '' ) { ), 'post_excerpt' => array( 'selector' => '.entry-summary', + 'fallback_refresh' => true, ), 'comment_status[comments-area]' => array( 'selector' => '.comments-area', - 'body_selector' => true, - 'singular_only' => true, 'container_inclusive' => true, ), 'comment_status[comments-link]' => array( 'selector' => '.comments-link', - 'archive_only' => true, + 'fallback_dependent_selector' => 'body.archive', // Only do fallback when on archives. 'container_inclusive' => true, ), 'ping_status' => array( 'selector' => '.comments-area', - 'body_selector' => true, - 'singular_only' => true, 'container_inclusive' => true, ), 'post_author[byline]' => array( @@ -1359,6 +1341,15 @@ public function get_post_field_partial_schema( $field_id = '' ) { */ $schema = apply_filters( 'customize_posts_partial_schema', $schema ); + $deprecated_keys = array( 'body_selector', 'archive_only', 'singular_only' ); + foreach ( $schema as $_field_id => $_partial_args ) { + foreach ( $deprecated_keys as $deprecated_key ) { + if ( array_key_exists( $deprecated_key, $_partial_args ) ) { + _deprecated_argument( __FUNCTION__, '0.8.5', sprintf( __( 'The %s param has been removed from the partial schema. Consider fallback_dependent_selector if needed.', 'customize-posts' ), $deprecated_key ) ); + } + } + } + // Return specific schema based on the field_id & placement. if ( ! empty( $field_id ) ) { if ( isset( $schema[ $field_id ] ) ) { diff --git a/php/theme-support/class-customize-posts-twenty-eleven-support.php b/php/theme-support/class-customize-posts-twenty-eleven-support.php index f492a81..5a21de8 100644 --- a/php/theme-support/class-customize-posts-twenty-eleven-support.php +++ b/php/theme-support/class-customize-posts-twenty-eleven-support.php @@ -43,7 +43,7 @@ public function filter_partial_schema( $schema ) { $schema['post_author[biography]'] = array( 'selector' => '#author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); diff --git a/php/theme-support/class-customize-posts-twenty-fifteen-support.php b/php/theme-support/class-customize-posts-twenty-fifteen-support.php index 35c1e3b..304bc3c 100644 --- a/php/theme-support/class-customize-posts-twenty-fifteen-support.php +++ b/php/theme-support/class-customize-posts-twenty-fifteen-support.php @@ -41,7 +41,7 @@ public function add_support() { public function filter_partial_schema( $schema ) { $schema['post_author[biography]'] = array( 'selector' => '.author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); diff --git a/php/theme-support/class-customize-posts-twenty-seventeen-support.php b/php/theme-support/class-customize-posts-twenty-seventeen-support.php new file mode 100644 index 0000000..37bac3f --- /dev/null +++ b/php/theme-support/class-customize-posts-twenty-seventeen-support.php @@ -0,0 +1,45 @@ + '.author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); diff --git a/php/theme-support/class-customize-posts-twenty-ten-support.php b/php/theme-support/class-customize-posts-twenty-ten-support.php index 2d2d642..849f2ad 100644 --- a/php/theme-support/class-customize-posts-twenty-ten-support.php +++ b/php/theme-support/class-customize-posts-twenty-ten-support.php @@ -45,7 +45,7 @@ public function filter_partial_schema( $schema ) { $schema['post_author[biography]'] = array( 'selector' => '#entry-author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); diff --git a/php/theme-support/class-customize-posts-twenty-thirteen-support.php b/php/theme-support/class-customize-posts-twenty-thirteen-support.php index f3b97d1..457f34a 100644 --- a/php/theme-support/class-customize-posts-twenty-thirteen-support.php +++ b/php/theme-support/class-customize-posts-twenty-thirteen-support.php @@ -41,7 +41,7 @@ public function add_support() { public function filter_partial_schema( $schema ) { $schema['post_author[biography]'] = array( 'selector' => '.author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); diff --git a/php/theme-support/class-customize-posts-twenty-twelve-support.php b/php/theme-support/class-customize-posts-twenty-twelve-support.php index 94781c2..97cb80f 100644 --- a/php/theme-support/class-customize-posts-twenty-twelve-support.php +++ b/php/theme-support/class-customize-posts-twenty-twelve-support.php @@ -41,7 +41,7 @@ public function add_support() { public function filter_partial_schema( $schema ) { $schema['post_author[biography]'] = array( 'selector' => '.author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); diff --git a/tests/data/themes/dummy/functions.php b/tests/data/themes/dummy/functions.php index 2578469..ec13260 100644 --- a/tests/data/themes/dummy/functions.php +++ b/tests/data/themes/dummy/functions.php @@ -58,7 +58,7 @@ public function add_support() { public function filter_partial_schema( $schema ) { $schema['post_author[biography]'] = array( 'selector' => '.author-info', - 'singular_only' => true, + 'fallback_dependent_selector' => 'body.singular', 'container_inclusive' => true, 'render_callback' => array( $this, 'biography_render_callback' ), ); From 576b8ce64d1cc3fc05533b04f830210a2a1177d7 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 18 Dec 2016 10:22:29 -0800 Subject: [PATCH 2/2] Extend the fallback dependent selector idea to featured image partials --- js/customize-preview-featured-image.js | 69 ++++++++++++++++--- ...wp-customize-featured-image-controller.php | 18 ++++- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/js/customize-preview-featured-image.js b/js/customize-preview-featured-image.js index 44741c3..a2bfca4 100644 --- a/js/customize-preview-featured-image.js +++ b/js/customize-preview-featured-image.js @@ -7,8 +7,11 @@ var CustomizePreviewFeaturedImage = (function( api, $ ) { var component = { data: { - partialSelectorAttribute: '', - partialContainerInclusive: true + partialArgs: { + selector: '', + containerInclusive: true, + fallbackDependentSelector: '' + } } }; @@ -56,6 +59,37 @@ var CustomizePreviewFeaturedImage = (function( api, $ ) { */ component.FeaturedImagePartial = api.selectiveRefresh.partialConstructor.deferred.extend({ + idPattern: /^postmeta\[(.+?)]\[(\d+)]\[_thumbnail_id]$/, + + /** + * Initialize. + * + * @param {string} id Partial ID. + * @param {object} args Args. + * @param {object} args.params Params. + * @returns {void} + */ + initialize: function( id, args ) { + var partial = this, matches, postId, postType, params; + matches = id.match( partial.idPattern ); + postType = matches[1]; + postId = parseInt( matches[2], 10 ); + params = _.extend( + { + post_id: postId, + post_type: postType, + selector: component.data.partialArgs.selector.replace( /%d/g, String( postId ) ), + settings: [ id ], + primarySetting: id, + containerInclusive: component.data.partialArgs.containerInclusive, + fallbackDependentSelector: component.data.partialArgs.fallbackDependentSelector + }, + args ? args.params || {} : {} + ); + + api.selectiveRefresh.partialConstructor.deferred.prototype.initialize.call( partial, id, { params: params } ); + }, + /** * Force fallback (full page refresh) behavior when the featured image is removed. * @@ -93,6 +127,27 @@ var CustomizePreviewFeaturedImage = (function( api, $ ) { } else { return api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ); } + }, + + /** + * Handle fail to render partial. + * + * Skip performing fallback behavior if post does not appear on the current template. + * + * {@inheritdoc} + * + * @this {wp.customize.selectiveRefresh.partialConstructor.deferred} + * @returns {void} + */ + fallback: function postFieldPartialFallback() { + var partial = this, dependentSelector; + + dependentSelector = partial.params.fallbackDependentSelector.replace( /%d/g, String( partial.params.post_id ) ); + if ( 0 === $( dependentSelector ).length ) { + return; + } + + api.selectiveRefresh.partialConstructor.deferred.prototype.fallback.call( partial ); } }); @@ -103,22 +158,18 @@ var CustomizePreviewFeaturedImage = (function( api, $ ) { * @returns {component.FeaturedImagePartial|null} New or existing featured image partial, or null if not relevant setting. */ component.ensurePartialForSetting = function ensurePartialForSetting( setting ) { - var ensuredPartial, partialId, postId, matches = setting.id.match( /^postmeta\[.+?]\[(\d+)]\[_thumbnail_id]$/ ); - if ( ! matches ) { + var ensuredPartial, partialId; + if ( ! component.FeaturedImagePartial.prototype.idPattern.test( setting.id ) ) { return null; } partialId = setting.id; - postId = parseInt( matches[1], 10 ); ensuredPartial = api.selectiveRefresh.partial( partialId ); if ( ensuredPartial ) { return ensuredPartial; } ensuredPartial = new component.FeaturedImagePartial( partialId, { params: { - selector: '[' + component.data.partialSelectorAttribute + '=' + String( postId ) + ']', - settings: [ setting.id ], - primarySetting: setting.id, - containerInclusive: component.data.partialContainerInclusive + settings: [ setting.id ] } } ); api.selectiveRefresh.partial.add( partialId, ensuredPartial ); diff --git a/php/class-wp-customize-featured-image-controller.php b/php/class-wp-customize-featured-image-controller.php index 9d86251..0ec4ca8 100644 --- a/php/class-wp-customize-featured-image-controller.php +++ b/php/class-wp-customize-featured-image-controller.php @@ -25,6 +25,13 @@ class WP_Customize_Featured_Image_Controller extends WP_Customize_Postmeta_Contr */ const SELECTED_ATTRIBUTE = 'data-customize-featured-image-partial'; + /** + * Selector for finding featured images. + * + * @var string + */ + const SELECTOR = '[data-customize-featured-image-partial="%d"]'; + /** * The container_inclusive param for the partials. * @@ -117,9 +124,14 @@ public function enqueue_customize_pane_scripts() { public function enqueue_customize_preview_scripts() { $handle = 'customize-preview-featured-image'; wp_enqueue_script( $handle ); + + // @todo These arguments should be configurable for featured image partials just as they are for post field partials. $exports = array( - 'partialSelectorAttribute' => self::SELECTED_ATTRIBUTE, - 'partialContainerInclusive' => self::PARTIAL_CONTAINER_INCLUSIVE, + 'partialArgs' => array( + 'selector' => self::SELECTOR, + 'fallbackDependentSelector' => '.hentry.post-%d, body.page-id-%d, body.postid-%d', + 'containerInclusive' => self::PARTIAL_CONTAINER_INCLUSIVE, + ), ); wp_add_inline_script( $handle, sprintf( 'CustomizePreviewFeaturedImage.init( %s )', wp_json_encode( $exports ) ) ); } @@ -281,7 +293,7 @@ public function filter_customize_dynamic_partial_args( $partial_args, $partial_i $partial_args['settings'] = array( $setting_id ); $partial_args['primary_setting'] = $setting_id; $partial_args['type'] = 'featured_image'; - $partial_args['selector'] = '[' . self::SELECTED_ATTRIBUTE . '=' . $matches['post_id'] . ']'; + $partial_args['selector'] = sprintf( self::SELECTOR, $matches['post_id'] ); $partial_args['container_inclusive'] = self::PARTIAL_CONTAINER_INCLUSIVE; } return $partial_args;