* Initialize nav menus preview.
*/
self.init = function() {
- var self = this;
+ var self = this, synced = false;
+
+ /*
+ * Keep track of whether we synced to determine whether or not bindSettingListener
+ * should also initially fire the listener. This initial firing needs to wait until
+ * after all of the settings have been synced from the pane in order to prevent
+ * an infinite selective fallback-refresh. Note that this sync handler will be
+ * added after the sync handler in customize-preview.js, so it will be triggered
+ * after all of the settings are added.
+ */
+ api.preview.bind( 'sync', function() {
+ synced = true;
+ } );
if ( api.selectiveRefresh ) {
// Listen for changes to settings related to nav menus.
self.bindSettingListener( setting );
} );
api.bind( 'add', function( setting ) {
- self.bindSettingListener( setting, { fire: true } );
+
+ /*
+ * Handle case where an invalid nav menu item (one for which its associated object has been deleted)
+ * is synced from the controls into the preview. Since invalid nav menu items are filtered out from
+ * being exported to the frontend by the _is_valid_nav_menu_item filter in wp_get_nav_menu_items(),
+ * the customizer controls will have a nav_menu_item setting where the preview will have none, and
+ * this can trigger an infinite fallback refresh when the nav menu item lacks any valid items.
+ */
+ if ( setting.get() && ! setting.get()._invalid ) {
+ self.bindSettingListener( setting, { fire: synced } );
+ }
} );
api.bind( 'remove', function( setting ) {
self.unbindSettingListener( setting );
* @returns {boolean}
*/
isRelatedSetting: function( setting, newValue, oldValue ) {
- var partial = this, navMenuLocationSetting, navMenuId, isNavMenuItemSetting;
+ var partial = this, navMenuLocationSetting, navMenuId, isNavMenuItemSetting, _newValue, _oldValue, urlParser;
if ( _.isString( setting ) ) {
setting = api( setting );
}
*/
isNavMenuItemSetting = /^nav_menu_item\[/.test( setting.id );
if ( isNavMenuItemSetting && _.isObject( newValue ) && _.isObject( oldValue ) ) {
- delete newValue.type_label;
- delete oldValue.type_label;
- if ( _.isEqual( oldValue, newValue ) ) {
+ _newValue = _.clone( newValue );
+ _oldValue = _.clone( oldValue );
+ delete _newValue.type_label;
+ delete _oldValue.type_label;
+
+ // Normalize URL scheme when parent frame is HTTPS to prevent selective refresh upon initial page load.
+ if ( 'https' === api.preview.scheme.get() ) {
+ urlParser = document.createElement( 'a' );
+ urlParser.href = _newValue.url;
+ urlParser.protocol = 'https:';
+ _newValue.url = urlParser.href;
+ urlParser.href = _oldValue.url;
+ urlParser.protocol = 'https:';
+ _oldValue.url = urlParser.href;
+ }
+
+ // Prevent original_title differences from causing refreshes if title is present.
+ if ( newValue.title ) {
+ delete _oldValue.original_title;
+ delete _newValue.original_title;
+ }
+
+ if ( _.isEqual( _oldValue, _newValue ) ) {
return false;
}
}
self.highlightControls = function() {
var selector = '.menu-item';
+ // Skip adding highlights if not in the customizer preview iframe.
+ if ( ! api.settings.channel ) {
+ return;
+ }
+
// Focus on the menu item control when shift+clicking the menu item.
$( document ).on( 'click', selector, function( e ) {
var navMenuItemParts;