PageRenderTime 29ms CodeModel.GetById 16ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-includes/js/customize-loader.js

https://bitbucket.org/skyarch-iijima/wordpress
JavaScript | 286 lines | 169 code | 46 blank | 71 comment | 37 complexity | a2c8b624e5fad7d54a01eb87471d66b0 MD5 | raw file
  1/* global _wpCustomizeLoaderSettings */
  2/**
  3 * Expose a public API that allows the customizer to be
  4 * loaded on any page.
  5 *
  6 * @namespace wp
  7 */
  8window.wp = window.wp || {};
  9
 10(function( exports, $ ){
 11	var api = wp.customize,
 12		Loader;
 13
 14	$.extend( $.support, {
 15		history: !! ( window.history && history.pushState ),
 16		hashchange: ('onhashchange' in window) && (document.documentMode === undefined || document.documentMode > 7)
 17	});
 18
 19	/**
 20	 * Allows the Customizer to be overlayed on any page.
 21	 *
 22	 * By default, any element in the body with the load-customize class will open
 23	 * an iframe overlay with the URL specified.
 24	 *
 25	 *     e.g. <a class="load-customize" href="<?php echo wp_customize_url(); ?>">Open Customizer</a>
 26	 *
 27	 * @memberOf wp.customize
 28	 *
 29	 * @class
 30	 * @augments wp.customize.Events
 31	 */
 32	Loader = $.extend( {}, api.Events,/** @lends wp.customize.Loader.prototype */{
 33		/**
 34		 * Setup the Loader; triggered on document#ready.
 35		 */
 36		initialize: function() {
 37			this.body = $( document.body );
 38
 39			// Ensure the loader is supported.
 40			// Check for settings, postMessage support, and whether we require CORS support.
 41			if ( ! Loader.settings || ! $.support.postMessage || ( ! $.support.cors && Loader.settings.isCrossDomain ) ) {
 42				return;
 43			}
 44
 45			this.window  = $( window );
 46			this.element = $( '<div id="customize-container" />' ).appendTo( this.body );
 47
 48			// Bind events for opening and closing the overlay.
 49			this.bind( 'open', this.overlay.show );
 50			this.bind( 'close', this.overlay.hide );
 51
 52			// Any element in the body with the `load-customize` class opens
 53			// the Customizer.
 54			$('#wpbody').on( 'click', '.load-customize', function( event ) {
 55				event.preventDefault();
 56
 57				// Store a reference to the link that opened the Customizer.
 58				Loader.link = $(this);
 59				// Load the theme.
 60				Loader.open( Loader.link.attr('href') );
 61			});
 62
 63			// Add navigation listeners.
 64			if ( $.support.history ) {
 65				this.window.on( 'popstate', Loader.popstate );
 66			}
 67
 68			if ( $.support.hashchange ) {
 69				this.window.on( 'hashchange', Loader.hashchange );
 70				this.window.triggerHandler( 'hashchange' );
 71			}
 72		},
 73
 74		popstate: function( e ) {
 75			var state = e.originalEvent.state;
 76			if ( state && state.customize ) {
 77				Loader.open( state.customize );
 78			} else if ( Loader.active ) {
 79				Loader.close();
 80			}
 81		},
 82
 83		hashchange: function() {
 84			var hash = window.location.toString().split('#')[1];
 85
 86			if ( hash && 0 === hash.indexOf( 'wp_customize=on' ) ) {
 87				Loader.open( Loader.settings.url + '?' + hash );
 88			}
 89
 90			if ( ! hash && ! $.support.history ) {
 91				Loader.close();
 92			}
 93		},
 94
 95		beforeunload: function () {
 96			if ( ! Loader.saved() ) {
 97				return Loader.settings.l10n.saveAlert;
 98			}
 99		},
100
101		/**
102		 * Open the Customizer overlay for a specific URL.
103		 *
104		 * @param  string src URL to load in the Customizer.
105		 */
106		open: function( src ) {
107
108			if ( this.active ) {
109				return;
110			}
111
112			// Load the full page on mobile devices.
113			if ( Loader.settings.browser.mobile ) {
114				return window.location = src;
115			}
116
117			// Store the document title prior to opening the Live Preview
118			this.originalDocumentTitle = document.title;
119
120			this.active = true;
121			this.body.addClass('customize-loading');
122
123			/*
124			 * Track the dirtiness state (whether the drafted changes have been published)
125			 * of the Customizer in the iframe. This is used to decide whether to display
126			 * an AYS alert if the user tries to close the window before saving changes.
127			 */
128			this.saved = new api.Value( true );
129
130			this.iframe = $( '<iframe />', { 'src': src, 'title': Loader.settings.l10n.mainIframeTitle } ).appendTo( this.element );
131			this.iframe.one( 'load', this.loaded );
132
133			// Create a postMessage connection with the iframe.
134			this.messenger = new api.Messenger({
135				url: src,
136				channel: 'loader',
137				targetWindow: this.iframe[0].contentWindow
138			});
139
140			// Expose the changeset UUID on the parent window's URL so that the customized state can survive a refresh.
141			if ( history.replaceState ) {
142				this.messenger.bind( 'changeset-uuid', function( changesetUuid ) {
143					var urlParser = document.createElement( 'a' );
144					urlParser.href = location.href;
145					urlParser.search = $.param( _.extend(
146						api.utils.parseQueryString( urlParser.search.substr( 1 ) ),
147						{ changeset_uuid: changesetUuid }
148					) );
149					history.replaceState( { customize: urlParser.href }, '', urlParser.href );
150				} );
151			}
152
153			// Wait for the connection from the iframe before sending any postMessage events.
154			this.messenger.bind( 'ready', function() {
155				Loader.messenger.send( 'back' );
156			});
157
158			this.messenger.bind( 'close', function() {
159				if ( $.support.history ) {
160					history.back();
161				} else if ( $.support.hashchange ) {
162					window.location.hash = '';
163				} else {
164					Loader.close();
165				}
166			});
167
168			// Prompt AYS dialog when navigating away
169			$( window ).on( 'beforeunload', this.beforeunload );
170
171			this.messenger.bind( 'saved', function () {
172				Loader.saved( true );
173			} );
174			this.messenger.bind( 'change', function () {
175				Loader.saved( false );
176			} );
177
178			this.messenger.bind( 'title', function( newTitle ){
179				window.document.title = newTitle;
180			});
181
182			this.pushState( src );
183
184			this.trigger( 'open' );
185		},
186
187		pushState: function ( src ) {
188			var hash = src.split( '?' )[1];
189
190			// Ensure we don't call pushState if the user hit the forward button.
191			if ( $.support.history && window.location.href !== src ) {
192				history.pushState( { customize: src }, '', src );
193			} else if ( ! $.support.history && $.support.hashchange && hash ) {
194				window.location.hash = 'wp_customize=on&' + hash;
195			}
196
197			this.trigger( 'open' );
198		},
199
200		/**
201		 * Callback after the Customizer has been opened.
202		 */
203		opened: function() {
204			Loader.body.addClass( 'customize-active full-overlay-active' ).attr( 'aria-busy', 'true' );
205		},
206
207		/**
208		 * Close the Customizer overlay.
209		 */
210		close: function() {
211			var self = this, onConfirmClose;
212			if ( ! self.active ) {
213				return;
214			}
215
216			onConfirmClose = function( confirmed ) {
217				if ( confirmed ) {
218					self.active = false;
219					self.trigger( 'close' );
220
221					// Restore document title prior to opening the Live Preview
222					if ( self.originalDocumentTitle ) {
223						document.title = self.originalDocumentTitle;
224					}
225				} else {
226
227					// Go forward since Customizer is exited by history.back()
228					history.forward();
229				}
230				self.messenger.unbind( 'confirmed-close', onConfirmClose );
231			};
232			self.messenger.bind( 'confirmed-close', onConfirmClose );
233
234			Loader.messenger.send( 'confirm-close' );
235		},
236
237		/**
238		 * Callback after the Customizer has been closed.
239		 */
240		closed: function() {
241			Loader.iframe.remove();
242			Loader.messenger.destroy();
243			Loader.iframe    = null;
244			Loader.messenger = null;
245			Loader.saved     = null;
246			Loader.body.removeClass( 'customize-active full-overlay-active' ).removeClass( 'customize-loading' );
247			$( window ).off( 'beforeunload', Loader.beforeunload );
248			/*
249			 * Return focus to the link that opened the Customizer overlay after
250			 * the body element visibility is restored.
251			 */
252			if ( Loader.link ) {
253				Loader.link.focus();
254			}
255		},
256
257		/**
258		 * Callback for the `load` event on the Customizer iframe.
259		 */
260		loaded: function() {
261			Loader.body.removeClass( 'customize-loading' ).attr( 'aria-busy', 'false' );
262		},
263
264		/**
265		 * Overlay hide/show utility methods.
266		 */
267		overlay: {
268			show: function() {
269				this.element.fadeIn( 200, Loader.opened );
270			},
271
272			hide: function() {
273				this.element.fadeOut( 200, Loader.closed );
274			}
275		}
276	});
277
278	// Bootstrap the Loader on document#ready.
279	$( function() {
280		Loader.settings = _wpCustomizeLoaderSettings;
281		Loader.initialize();
282	});
283
284	// Expose the API publicly on window.wp.customize.Loader
285	api.Loader = Loader;
286})( wp, jQuery );