var Container, focus, api = wp.customize;
/**
+ * A Customizer Setting.
+ *
+ * A setting is WordPress data (theme mod, option, menu, etc.) that the user can
+ * draft changes to in the Customizer.
+ *
+ * @see PHP class WP_Customize_Setting.
+ *
* @class
* @augments wp.customize.Value
* @augments wp.customize.Class
*
- * @param options
- * - previewer - The Previewer instance to sync with.
- * - transport - The transport to use for previewing. Supports 'refresh' and 'postMessage'.
+ * @param {object} id The Setting ID.
+ * @param {object} value The initial value of the setting.
+ * @param {object} options.previewer The Previewer instance to sync with.
+ * @param {object} options.transport The transport to use for previewing. Supports 'refresh' and 'postMessage'.
+ * @param {object} options.dirty
*/
api.Setting = api.Value.extend({
initialize: function( id, value, options ) {
this.transport = this.transport || 'refresh';
this._dirty = options.dirty || false;
+ // Whenever the setting's value changes, refresh the preview.
this.bind( this.preview );
},
+
+ /**
+ * Refresh the preview, respective of the setting's refresh policy.
+ */
preview: function() {
switch ( this.transport ) {
case 'refresh':
* @since 4.1.0
*
* @param {Object} [params]
- * @param {Callback} [params.completeCallback]
+ * @param {Function} [params.completeCallback]
*/
focus = function ( params ) {
- var construct, completeCallback, focus;
+ var construct, completeCallback, focus, focusElement;
construct = this;
params = params || {};
focus = function () {
var focusContainer;
- if ( construct.expanded && construct.expanded() ) {
- focusContainer = construct.container.find( 'ul:first' );
+ if ( construct.extended( api.Panel ) && construct.expanded && construct.expanded() ) {
+ focusContainer = construct.container.find( 'ul.control-panel-content' );
+ } else if ( construct.extended( api.Section ) && construct.expanded && construct.expanded() ) {
+ focusContainer = construct.container.find( 'ul.accordion-section-content' );
} else {
focusContainer = construct.container;
}
- // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
- focusContainer.find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ).first().focus();
+ focusElement = focusContainer.find( '.control-focus:first' );
+ if ( 0 === focusElement.length ) {
+ // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
+ focusElement = focusContainer.find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ).first();
+ }
+ focusElement.focus();
};
if ( params.completeCallback ) {
completeCallback = params.completeCallback;
},
/**
- * Handle changes to the active state.
+ * Active state change handler.
*
- * This does not change the active state, it merely handles the behavior
- * for when it does change.
+ * Shows the container if it is active, hides it if not.
*
* To override by subclass, update the container's UI to reflect the provided active state.
*
* @param {Object} args.duration
* @param {Object} args.completeCallback
*/
- onChangeActive: function ( active, args ) {
- var duration, construct = this;
+ onChangeActive: function( active, args ) {
+ var duration, construct = this, expandedOtherPanel;
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
duration = ( 'resolved' === api.previewer.deferred.active.state() ? args.duration : 0 );
+
+ if ( construct.extended( api.Panel ) ) {
+ // If this is a panel is not currently expanded but another panel is expanded, do not animate.
+ api.panel.each(function ( panel ) {
+ if ( panel !== construct && panel.expanded() ) {
+ expandedOtherPanel = panel;
+ duration = 0;
+ }
+ });
+
+ // Collapse any expanded sections inside of this panel first before deactivating.
+ if ( ! active ) {
+ _.each( construct.sections(), function( section ) {
+ section.collapse( { duration: 0 } );
+ } );
+ }
+ }
+
if ( ! $.contains( document, construct.container[0] ) ) {
// jQuery.fn.slideUp is not hiding an element if it is not in the DOM
construct.container.toggle( active );
construct.container.stop( true, true ).slideUp( duration, args.completeCallback );
}
}
+
+ // Recalculate the margin-top immediately, not waiting for debounced reflow, to prevent momentary (100ms) vertical jiggle.
+ if ( expandedOtherPanel ) {
+ expandedOtherPanel._recalculateTopMargin();
+ }
},
/**
},
/**
- * @param {Boolean} expanded
- * @param {Object} [params]
- * @returns {Boolean} false if state already applied
+ * Handle the toggle logic for expand/collapse.
+ *
+ * @param {Boolean} expanded - The new state to apply.
+ * @param {Object} [params] - Object containing options for expand/collapse.
+ * @param {Function} [params.completeCallback] - Function to call when expansion/collapse is complete.
+ * @returns {Boolean} false if state already applied or active state is false
*/
- _toggleExpanded: function ( expanded, params ) {
- var self = this;
+ _toggleExpanded: function( expanded, params ) {
+ var instance = this, previousCompleteCallback;
params = params || {};
- var section = this, previousCompleteCallback = params.completeCallback;
- params.completeCallback = function () {
+ previousCompleteCallback = params.completeCallback;
+
+ // Short-circuit expand() if the instance is not active.
+ if ( expanded && ! instance.active() ) {
+ return false;
+ }
+
+ params.completeCallback = function() {
if ( previousCompleteCallback ) {
- previousCompleteCallback.apply( section, arguments );
+ previousCompleteCallback.apply( instance, arguments );
}
if ( expanded ) {
- section.container.trigger( 'expanded' );
+ instance.container.trigger( 'expanded' );
} else {
- section.container.trigger( 'collapsed' );
+ instance.container.trigger( 'collapsed' );
}
};
- if ( ( expanded && this.expanded.get() ) || ( ! expanded && ! this.expanded.get() ) ) {
+ if ( ( expanded && instance.expanded.get() ) || ( ! expanded && ! instance.expanded.get() ) ) {
params.unchanged = true;
- self.onChangeExpanded( self.expanded.get(), params );
+ instance.onChangeExpanded( instance.expanded.get(), params );
return false;
} else {
params.unchanged = false;
- this.expandedArgumentsQueue.push( params );
- this.expanded.set( expanded );
+ instance.expandedArgumentsQueue.push( params );
+ instance.expanded.set( expanded );
return true;
}
},
/**
* @param {Object} [params]
- * @returns {Boolean} false if already expanded
+ * @returns {Boolean} false if already expanded or if inactive.
*/
expand: function ( params ) {
return this._toggleExpanded( true, params );
/**
* @param {Object} [params]
- * @returns {Boolean} false if already collapsed
+ * @returns {Boolean} false if already collapsed.
*/
collapse: function ( params ) {
return this._toggleExpanded( false, params );
};
section.panel.bind( inject );
inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one
+
+ section.deferred.embedded.done(function() {
+ // Fix the top margin after reflow.
+ api.bind( 'pane-contents-reflowed', _.debounce( function() {
+ section._recalculateTopMargin();
+ }, 100 ) );
+ });
},
/**
// Fix the height after browser resize.
$( window ).on( 'resize.customizer-section', _.debounce( resizeContentHeight, 100 ) );
- // Fix the top margin after reflow.
- api.bind( 'pane-contents-reflowed', _.debounce( function() {
- var offset = ( content.offset().top - headerActionsHeight );
- if ( 0 < offset ) {
- content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - offset ) );
- }
- }, 100 ) );
+ setTimeout( _.bind( section._recalculateTopMargin, section ), 0 );
};
}
completeCallback: expand
});
} else {
+ api.panel.each( function( panel ) {
+ panel.collapse();
+ });
expand();
}
args.completeCallback();
}
}
+ },
+
+ /**
+ * Recalculate the top margin.
+ *
+ * @since 4.4.0
+ * @private
+ */
+ _recalculateTopMargin: function() {
+ var section = this, content, offset, headerActionsHeight;
+ content = section.container.find( '.accordion-section-content' );
+ if ( 0 === content.length ) {
+ return;
+ }
+ headerActionsHeight = $( '#customize-header-actions' ).height();
+ offset = ( content.offset().top - headerActionsHeight );
+ if ( 0 < offset ) {
+ content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - offset ) );
+ }
}
});
}
},
+ /**
+ * Recalculate the top margin.
+ *
+ * @since 4.4.0
+ * @private
+ */
+ _recalculateTopMargin: function() {
+ api.Panel.prototype._recalculateTopMargin.call( this );
+ },
+
/**
* Render control's screenshot if the control comes into view.
*
parentContainer.append( panel.container );
panel.renderContent();
}
+
+ api.bind( 'pane-contents-reflowed', _.debounce( function() {
+ panel._recalculateTopMargin();
+ }, 100 ) );
+
panel.deferred.embedded.resolve();
},
* @param {Boolean} expanded
* @param {Object} args
* @param {Boolean} args.unchanged
- * @param {Callback} args.completeCallback
+ * @param {Function} args.completeCallback
*/
onChangeExpanded: function ( expanded, args ) {
// Note: there is a second argument 'args' passed
var position, scroll,
panel = this,
- section = panel.container.closest( '.accordion-section' ), // This is actually the panel.
- overlay = section.closest( '.wp-full-overlay' ),
- container = section.closest( '.wp-full-overlay-sidebar-content' ),
+ accordionSection = panel.container.closest( '.accordion-section' ),
+ overlay = accordionSection.closest( '.wp-full-overlay' ),
+ container = accordionSection.closest( '.wp-full-overlay-sidebar-content' ),
siblings = container.find( '.open' ),
topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ),
- backBtn = section.find( '.customize-panel-back' ),
- panelTitle = section.find( '.accordion-section-title' ).first(),
- content = section.find( '.control-panel-content' ),
+ backBtn = accordionSection.find( '.customize-panel-back' ),
+ panelTitle = accordionSection.find( '.accordion-section-title' ).first(),
+ content = accordionSection.find( '.control-panel-content' ),
headerActionsHeight = $( '#customize-header-actions' ).height();
if ( expanded ) {
// Collapse any sibling sections/panels
api.section.each( function ( section ) {
- if ( ! section.panel() ) {
+ if ( panel.id !== section.panel() ) {
section.collapse( { duration: 0 } );
}
});
position = content.offset().top;
scroll = container.scrollTop();
content.css( 'margin-top', ( headerActionsHeight - position - scroll ) );
- section.addClass( 'current-panel' );
+ accordionSection.addClass( 'current-panel' );
overlay.addClass( 'in-sub-panel' );
container.scrollTop( 0 );
if ( args.completeCallback ) {
topPanel.attr( 'tabindex', '-1' );
backBtn.attr( 'tabindex', '0' );
backBtn.focus();
-
- // Fix the top margin after reflow.
- api.bind( 'pane-contents-reflowed', _.debounce( function() {
- content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - ( content.offset().top - headerActionsHeight ) ) );
- }, 100 ) );
+ panel._recalculateTopMargin();
} else {
siblings.removeClass( 'open' );
- section.removeClass( 'current-panel' );
+ accordionSection.removeClass( 'current-panel' );
overlay.removeClass( 'in-sub-panel' );
content.delay( 180 ).hide( 0, function() {
content.css( 'margin-top', 'inherit' ); // Reset
}
},
+ /**
+ * Recalculate the top margin.
+ *
+ * @since 4.4.0
+ * @private
+ */
+ _recalculateTopMargin: function() {
+ var panel = this, headerActionsHeight, content, accordionSection;
+ headerActionsHeight = $( '#customize-header-actions' ).height();
+ accordionSection = panel.container.closest( '.accordion-section' );
+ content = accordionSection.find( '.control-panel-content' );
+ content.css( 'margin-top', ( parseInt( content.css( 'margin-top' ), 10 ) - ( content.offset().top - headerActionsHeight ) ) );
+ },
+
/**
* Render the panel from its JS template, if it exists.
*
* @class
* @augments wp.customize.Class
*
- * @param {string} id Unique identifier for the control instance.
- * @param {object} options Options hash for the control instance.
+ * @param {string} id Unique identifier for the control instance.
+ * @param {object} options Options hash for the control instance.
* @param {object} options.params
- * @param {object} options.params.type Type of control (e.g. text, radio, dropdown-pages, etc.)
- * @param {string} options.params.content The HTML content for the control.
- * @param {string} options.params.priority Order of priority to show the control within the section.
+ * @param {object} options.params.type Type of control (e.g. text, radio, dropdown-pages, etc.)
+ * @param {string} options.params.content The HTML content for the control.
+ * @param {string} options.params.priority Order of priority to show the control within the section.
* @param {string} options.params.active
- * @param {string} options.params.section
+ * @param {string} options.params.section The ID of the section the control belongs to.
+ * @param {string} options.params.settings.default The ID of the setting the control relates to.
+ * @param {string} options.params.settings.data
* @param {string} options.params.label
* @param {string} options.params.description
* @param {string} options.params.instanceNumber Order in which this instance was created in relation to other instances.
api.utils.bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
- // Associate this control with its settings when they are created
+ /*
+ * After all settings related to the control are available,
+ * make them available on the control and embed the control into the page.
+ */
settings = $.map( control.params.settings, function( value ) {
return value;
});
- api.apply( api, settings.concat( function () {
- var key;
+ if ( 0 === settings.length ) {
+ control.setting = null;
control.settings = {};
- for ( key in control.params.settings ) {
- control.settings[ key ] = api( control.params.settings[ key ] );
- }
+ control.embed();
+ } else {
+ api.apply( api, settings.concat( function() {
+ var key;
- control.setting = control.settings['default'] || null;
+ control.settings = {};
+ for ( key in control.params.settings ) {
+ control.settings[ key ] = api( control.params.settings[ key ] );
+ }
- control.embed();
- }) );
+ control.setting = control.settings['default'] || null;
+ control.embed();
+ }) );
+ }
+
+ // After the control is embedded on the page, invoke the "ready" method.
control.deferred.embedded.done( function () {
control.ready();
});
// Watch for changes to the section state
inject = function ( sectionId ) {
var parentContainer;
- if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the frontend
+ if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the front end.
return;
}
// Wait for the section to be registered
* @param {Callback} args.completeCallback
*/
onChangeActive: function ( active, args ) {
- if ( ! $.contains( document, this.container ) ) {
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
+ if ( ! $.contains( document, this.container[0] ) ) {
// jQuery.fn.slideUp is not hiding an element if it is not in the DOM
this.container.toggle( active );
if ( args.completeCallback ) {
control.setting.set( picker.wpColorPicker('color') );
},
clear: function() {
- control.setting.set( false );
+ control.setting.set( '' );
}
});
control.pausePlayer();
});
- // Re-render whenever the control's setting changes.
- control.setting.bind( function () { control.renderContent(); } );
+ control.setting.bind( function( value ) {
+
+ // Send attachment information to the preview for possible use in `postMessage` transport.
+ wp.media.attachment( value ).fetch().done( function() {
+ wp.customize.previewer.send( control.setting.id + '-attachment-data', this.attributes );
+ } );
+
+ // Re-render whenever the control's setting changes.
+ control.renderContent();
+ } );
},
pausePlayer: function () {
xInit = parseInt( control.params.width, 10 ),
yInit = parseInt( control.params.height, 10 ),
ratio = xInit / yInit,
- xImg = realWidth,
- yImg = realHeight,
+ xImg = xInit,
+ yImg = yInit,
x1, y1, imgSelectOptions;
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
- if ( xImg / yImg > ratio ) {
- yInit = yImg;
+ if ( realWidth / realHeight > ratio ) {
+ yInit = realHeight;
xInit = yInit * ratio;
} else {
- xInit = xImg;
+ xInit = realWidth;
yInit = xInit / ratio;
}
- x1 = ( xImg - xInit ) / 2;
- y1 = ( yImg - yInit ) / 2;
+ x1 = ( realWidth - xInit ) / 2;
+ y1 = ( realHeight - yInit ) / 2;
imgSelectOptions = {
handles: true,
persistent: true,
imageWidth: realWidth,
imageHeight: realHeight,
+ minWidth: xImg > xInit ? xInit : xImg,
+ minHeight: yImg > yInit ? yInit : yImg,
x1: x1,
y1: y1,
x2: xInit + x1,
if ( flexHeight === false && flexWidth === false ) {
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
}
- if ( flexHeight === false ) {
- imgSelectOptions.maxHeight = yInit;
+
+ if ( true === flexHeight ) {
+ delete imgSelectOptions.minHeight;
+ imgSelectOptions.maxWidth = realWidth;
}
- if ( flexWidth === false ) {
- imgSelectOptions.maxWidth = xInit;
+
+ if ( true === flexWidth ) {
+ delete imgSelectOptions.minWidth;
+ imgSelectOptions.maxHeight = realHeight;
}
return imgSelectOptions;
* @param {object} attachment
*/
setImageFromAttachment: function( attachment ) {
- var icon = typeof attachment.sizes['site_icon-32'] !== 'undefined' ? attachment.sizes['site_icon-32'] : attachment.sizes.thumbnail;
+ var sizes = [ 'site_icon-32', 'thumbnail', 'full' ],
+ icon;
+
+ _.each( sizes, function( size ) {
+ if ( ! icon && ! _.isUndefined ( attachment.sizes[ size ] ) ) {
+ icon = attachment.sizes[ size ];
+ }
+ } );
this.params.attachment = attachment;
// Set the Customizer setting; the callback takes care of rendering.
this.setting( attachment.id );
-
// Update the icon in-browser.
$( 'link[sizes="32x32"]' ).attr( 'href', icon.url );
},
api.HeaderTool.UploadsList,
api.HeaderTool.DefaultsList
]);
+
+ // Ensure custom-header-crop Ajax requests bootstrap the Customizer to activate the previewed theme.
+ wp.media.controller.Cropper.prototype.defaults.doCropArgs.wp_customize = 'on';
+ wp.media.controller.Cropper.prototype.defaults.doCropArgs.theme = api.settings.theme.stylesheet;
},
/**
* @param {object} croppedImage Cropped attachment data.
*/
onCropped: function(croppedImage) {
- var url = croppedImage.post_content,
+ var url = croppedImage.url,
attachmentId = croppedImage.attachment_id,
w = croppedImage.width,
h = croppedImage.height;
api.panel = new api.Values({ defaultConstructor: api.Panel });
/**
+ * An object that fetches a preview in the background of the document, which
+ * allows for seamless replacement of an existing preview.
+ *
* @class
* @augments wp.customize.Messenger
* @augments wp.customize.Class
api.PreviewFrame = api.Messenger.extend({
sensitivity: 2000,
+ /**
+ * Initialize the PreviewFrame.
+ *
+ * @param {object} params.container
+ * @param {object} params.signature
+ * @param {object} params.previewUrl
+ * @param {object} params.query
+ * @param {object} options
+ */
initialize: function( params, options ) {
var deferred = $.Deferred();
- // This is the promise object.
+ /*
+ * Make the instance of the PreviewFrame the promise object
+ * so other objects can easily interact with it.
+ */
deferred.promise( this );
this.container = params.container;
this.run( deferred );
},
+ /**
+ * Run the preview request.
+ *
+ * @param {object} deferred jQuery Deferred object to be resolved with
+ * the request.
+ */
run: function( deferred ) {
var self = this,
loaded = false,
iframe = $( '<iframe />', { 'src': self.previewUrl(), 'title': api.l10n.previewIframeTitle } ).hide();
iframe.appendTo( self.container );
- iframe.load( function() {
+ iframe.on( 'load', function() {
self.triedLogin = true;
iframe.remove();
refreshBuffer: 250,
/**
- * Requires params:
- * - container - a selector or jQuery element
- * - previewUrl - the URL of preview frame
+ * @param {array} params.allowedUrls
+ * @param {string} params.container A selector or jQuery element for the preview
+ * frame to be placed.
+ * @param {string} params.form
+ * @param {string} params.previewUrl The URL to preview.
+ * @param {string} params.signature
+ * @param {object} options
*/
initialize: function( params, options ) {
var self = this,
// Limit the URL to internal, front-end links.
//
- // If the frontend and the admin are served from the same domain, load the
+ // If the front end and the admin are served from the same domain, load the
// preview over ssl if the Customizer is being loaded over ssl. This avoids
- // insecure content warnings. This is not attempted if the admin and frontend
- // are on different domains to avoid the case where the frontend doesn't have
+ // insecure content warnings. This is not attempted if the admin and front end
+ // are on different domains to avoid the case where the front end doesn't have
// ssl certs.
this.add( 'previewUrl', params.previewUrl ).setter( function( to ) {
} );
},
+ /**
+ * Query string data sent with each preview request.
+ *
+ * @abstract
+ */
query: function() {},
abort: function() {
}
},
+ /**
+ * Refresh the preview.
+ */
refresh: function() {
var self = this;
},
cheatin: function() {
- $( document.body ).empty().addClass('cheatin').append( '<p>' + api.l10n.cheatin + '</p>' );
+ $( document.body ).empty().addClass( 'cheatin' ).append(
+ '<h1>' + api.l10n.cheatin + '</h1>' +
+ '<p>' + api.l10n.notAllowed + '</p>'
+ );
},
refreshNonces: function() {
overlay = body.children( '.wp-full-overlay' ),
title = $( '#customize-info .panel-title.site-title' ),
closeBtn = $( '.customize-controls-close' ),
- saveBtn = $( '#save' );
+ saveBtn = $( '#save' ),
+ footerActions = $( '#customize-footer-actions' );
// Prevent the form from saving when enter is pressed on an input or select element.
$('#customize-controls').on( 'keydown', function( e ) {
nonce: api.settings.nonce,
+ /**
+ * Build the query to send along with the Preview request.
+ *
+ * @return {object}
+ */
query: function() {
var dirtyCustomized = {};
api.each( function ( value, key ) {
value._dirty = false;
} );
+ api.previewer.send( 'saved', response );
+
api.trigger( 'saved', response );
} );
};
api.bind( 'nonce-refresh', function( nonce ) {
$.extend( api.settings.nonce, nonce );
$.extend( api.previewer.nonce, nonce );
+ api.previewer.send( 'nonce-refresh', nonce );
});
// Create Settings
});
// Focus the autofocused element
- _.each( [ 'panel', 'section', 'control' ], function ( type ) {
- var instance, id = api.settings.autofocus[ type ];
- if ( id && api[ type ]( id ) ) {
- instance = api[ type ]( id );
- // Wait until the element is embedded in the DOM
- instance.deferred.embedded.done( function () {
- // Wait until the preview has activated and so active panels, sections, controls have been set
- api.previewer.deferred.active.done( function () {
+ _.each( [ 'panel', 'section', 'control' ], function( type ) {
+ var id = api.settings.autofocus[ type ];
+ if ( ! id ) {
+ return;
+ }
+
+ /*
+ * Defer focus until:
+ * 1. The panel, section, or control exists (especially for dynamically-created ones).
+ * 2. The instance is embedded in the document (and so is focusable).
+ * 3. The preview has finished loading so that the active states have been set.
+ */
+ api[ type ]( id, function( instance ) {
+ instance.deferred.embedded.done( function() {
+ api.previewer.deferred.active.done( function() {
instance.focus();
});
});
- }
+ });
});
/**
event.preventDefault();
});
+ // Previewed device bindings.
+ api.previewedDevice = new api.Value();
+
+ // Set the default device.
+ api.bind( 'ready', function() {
+ _.find( api.settings.previewableDevices, function( value, key ) {
+ if ( true === value['default'] ) {
+ api.previewedDevice.set( key );
+ return true;
+ }
+ } );
+ } );
+
+ // Set the toggled device.
+ footerActions.find( '.devices button' ).on( 'click', function( event ) {
+ api.previewedDevice.set( $( event.currentTarget ).data( 'device' ) );
+ });
+
+ // Bind device changes.
+ api.previewedDevice.bind( function( newDevice ) {
+ var overlay = $( '.wp-full-overlay' ),
+ devices = '';
+
+ footerActions.find( '.devices button' )
+ .removeClass( 'active' )
+ .attr( 'aria-pressed', false );
+
+ footerActions.find( '.devices .preview-' + newDevice )
+ .addClass( 'active' )
+ .attr( 'aria-pressed', true );
+
+ $.each( api.settings.previewableDevices, function( device ) {
+ devices += ' preview-' + device;
+ } );
+
+ overlay
+ .removeClass( devices )
+ .addClass( 'preview-' + newDevice );
+ } );
+
// Bind site title display to the corresponding field.
if ( title.length ) {
- $( '#customize-control-blogname input' ).on( 'input', function() {
- title.text( this.value );
+ api( 'blogname', function( setting ) {
+ var updateTitle = function() {
+ title.text( $.trim( setting() ) || api.l10n.untitledBlogName );
+ };
+ setting.bind( updateTitle );
+ updateTitle();
} );
}
- // Create a potential postMessage connection with the parent frame.
+ /*
+ * Create a postMessage connection with a parent frame,
+ * in case the Customizer frame was opened with the Customize loader.
+ *
+ * @see wp.customize.Loader
+ */
parent = new api.Messenger({
url: api.settings.url.parent,
channel: 'loader'
});
- // If we receive a 'back' event, we're inside an iframe.
- // Send any clicks to the 'Return' link to the parent page.
+ /*
+ * If we receive a 'back' event, we're inside an iframe.
+ * Send any clicks to the 'Return' link to the parent page.
+ */
parent.bind( 'back', function() {
closeBtn.on( 'click.customize-controls-close', function( event ) {
event.preventDefault();
});
} );
- // When activated, let the loader handle redirecting the page.
- // If no loader exists, redirect the page ourselves (if a url exists).
+ /*
+ * When activated, let the loader handle redirecting the page.
+ * If no loader exists, redirect the page ourselves (if a url exists).
+ */
api.bind( 'activated', function() {
if ( parent.targetWindow() )
parent.send( 'activated', api.settings.url.activated );
});
});
+ // Focus on the control that is associated with the given setting.
+ api.previewer.bind( 'focus-control-for-setting', function( settingId ) {
+ var matchedControl;
+ api.control.each( function( control ) {
+ var settingIds = _.pluck( control.settings, 'id' );
+ if ( -1 !== _.indexOf( settingIds, settingId ) ) {
+ matchedControl = control;
+ }
+ } );
+
+ if ( matchedControl ) {
+ matchedControl.focus();
+ }
+ } );
+
+ // Refresh the preview when it requests.
+ api.previewer.bind( 'refresh', function() {
+ api.previewer.refresh();
+ });
+
api.trigger( 'ready' );
// Make sure left column gets focus