2 * Script run inside a Customizer preview frame.
4 (function( exports, $ ){
5 var api = wp.customize,
9 * Returns a debounced version of the function.
11 * @todo Require Underscore.js for this file and retire this.
13 debounce = function( fn, delay, context ) {
18 context = context || this;
20 clearTimeout( timeout );
21 timeout = setTimeout( function() {
23 fn.apply( context, args );
30 * @augments wp.customize.Messenger
31 * @augments wp.customize.Class
32 * @mixes wp.customize.Events
34 api.Preview = api.Messenger.extend({
36 * @param {object} params - Parameters to configure the messenger.
37 * @param {object} options - Extend any instance parameter or method with this object.
39 initialize: function( params, options ) {
42 api.Messenger.prototype.initialize.call( this, params, options );
44 this.body = $( document.body );
45 this.body.on( 'click.preview', 'a', function( event ) {
46 var link, isInternalJumpLink;
48 isInternalJumpLink = ( '#' === link.attr( 'href' ).substr( 0, 1 ) );
49 event.preventDefault();
51 if ( isInternalJumpLink && '#' !== link.attr( 'href' ) ) {
52 $( link.attr( 'href' ) ).each( function() {
53 this.scrollIntoView();
58 * Note the shift key is checked so shift+click on widgets or
59 * nav menu items can just result on focusing on the corresponding
60 * control instead of also navigating to the URL linked to.
62 if ( event.shiftKey || isInternalJumpLink ) {
65 self.send( 'scroll', 0 );
66 self.send( 'url', link.prop( 'href' ) );
69 // You cannot submit forms.
70 // @todo: Allow form submissions by mixing $_POST data with the customize setting $_POST data.
71 this.body.on( 'submit.preview', 'form', function( event ) {
72 event.preventDefault();
75 this.window = $( window );
76 this.window.on( 'scroll.preview', debounce( function() {
77 self.send( 'scroll', self.window.scrollTop() );
80 this.bind( 'scroll', function( distance ) {
81 self.window.scrollTop( distance );
89 api.settings = window._wpCustomizeSettings;
90 if ( ! api.settings ) {
94 api.preview = new api.Preview({
95 url: window.location.href,
96 channel: api.settings.channel
100 * Create/update a setting value.
102 * @param {string} id - Setting ID.
103 * @param {*} value - Setting value.
104 * @param {boolean} [createDirty] - Whether to create a setting as dirty. Defaults to false.
106 setValue = function( id, value, createDirty ) {
107 var setting = api( id );
109 setting.set( value );
111 createDirty = createDirty || false;
112 setting = api.create( id, value, {
116 // Mark dynamically-created settings as dirty so they will get posted.
118 setting._dirty = true;
123 api.preview.bind( 'settings', function( values ) {
124 $.each( values, setValue );
127 api.preview.trigger( 'settings', api.settings.values );
129 $.each( api.settings._dirty, function( i, id ) {
130 var setting = api( id );
132 setting._dirty = true;
136 api.preview.bind( 'setting', function( args ) {
137 var createDirty = true;
138 setValue.apply( null, args.concat( createDirty ) );
141 api.preview.bind( 'sync', function( events ) {
142 $.each( events, function( event, args ) {
143 api.preview.trigger( event, args );
145 api.preview.send( 'synced' );
148 api.preview.bind( 'active', function() {
149 api.preview.send( 'nonce', api.settings.nonce );
151 api.preview.send( 'documentTitle', document.title );
154 api.preview.bind( 'saved', function( response ) {
155 api.trigger( 'saved', response );
158 api.bind( 'saved', function() {
159 api.each( function( setting ) {
160 setting._dirty = false;
164 api.preview.bind( 'nonce-refresh', function( nonce ) {
165 $.extend( api.settings.nonce, nonce );
169 * Send a message to the parent customize frame with a list of which
170 * containers and controls are active.
172 api.preview.send( 'ready', {
173 activePanels: api.settings.activePanels,
174 activeSections: api.settings.activeSections,
175 activeControls: api.settings.activeControls
178 // Display a loading indicator when preview is reloading, and remove on failure.
179 api.preview.bind( 'loading-initiated', function () {
180 $( 'body' ).addClass( 'wp-customizer-unloading' );
182 api.preview.bind( 'loading-failed', function () {
183 $( 'body' ).removeClass( 'wp-customizer-unloading' );
186 /* Custom Backgrounds */
187 bg = $.map(['color', 'image', 'position_x', 'repeat', 'attachment'], function( prop ) {
188 return 'background_' + prop;
191 api.when.apply( api, bg ).done( function( color, image, position_x, repeat, attachment ) {
192 var body = $(document.body),
194 style = $('#custom-background-css'),
197 update = function() {
200 // The body will support custom backgrounds if either
201 // the color or image are set.
203 // See get_body_class() in /wp-includes/post-template.php
204 body.toggleClass( 'custom-background', !! ( color() || image() ) );
207 css += 'background-color: ' + color() + ';';
210 css += 'background-image: url("' + image() + '");';
211 css += 'background-position: top ' + position_x() + ';';
212 css += 'background-repeat: ' + repeat() + ';';
213 css += 'background-attachment: ' + attachment() + ';';
216 // Refresh the stylesheet by removing and recreating it.
218 style = $('<style type="text/css" id="custom-background-css">body.custom-background { ' + css + ' }</style>').appendTo( head );
221 $.each( arguments, function() {
229 * Toggle the wp-custom-logo body class when a logo is added or removed.
233 api( 'custom_logo', function( setting ) {
234 $( 'body' ).toggleClass( 'wp-custom-logo', !! setting.get() );
235 setting.bind( function( attachmentId ) {
236 $( 'body' ).toggleClass( 'wp-custom-logo', !! attachmentId );
240 api.trigger( 'preview-ready' );