wp/wp-includes/js/wp-custom-header.js
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     1 /* global YT */
       
     2 (function( window, settings ) {
       
     3 
       
     4 	var NativeHandler, YouTubeHandler;
       
     5 
       
     6 	/** @namespace wp */
       
     7 	window.wp = window.wp || {};
       
     8 
       
     9 	// Fail gracefully in unsupported browsers.
       
    10 	if ( ! ( 'addEventListener' in window ) ) {
       
    11 		return;
       
    12 	}
       
    13 
       
    14 	/**
       
    15 	 * Trigger an event.
       
    16 	 *
       
    17 	 * @param {Element} target HTML element to dispatch the event on.
       
    18 	 * @param {string} name Event name.
       
    19 	 */
       
    20 	function trigger( target, name ) {
       
    21 		var evt;
       
    22 
       
    23 		if ( 'function' === typeof window.Event ) {
       
    24 			evt = new Event( name );
       
    25 		} else {
       
    26 			evt = document.createEvent( 'Event' );
       
    27 			evt.initEvent( name, true, true );
       
    28 		}
       
    29 
       
    30 		target.dispatchEvent( evt );
       
    31 	}
       
    32 
       
    33 	/**
       
    34 	 * Create a custom header instance.
       
    35 	 *
       
    36 	 * @memberOf wp
       
    37 	 *
       
    38 	 * @class
       
    39 	 */
       
    40 	function CustomHeader() {
       
    41 		this.handlers = {
       
    42 			nativeVideo: new NativeHandler(),
       
    43 			youtube: new YouTubeHandler()
       
    44 		};
       
    45 	}
       
    46 
       
    47 	CustomHeader.prototype = {
       
    48 		/**
       
    49 		 * Initalize the custom header.
       
    50 		 *
       
    51 		 * If the environment supports video, loops through registered handlers
       
    52 		 * until one is found that can handle the video.
       
    53 		 */
       
    54 		initialize: function() {
       
    55 			if ( this.supportsVideo() ) {
       
    56 				for ( var id in this.handlers ) {
       
    57 					var handler = this.handlers[ id ];
       
    58 
       
    59 					if ( 'test' in handler && handler.test( settings ) ) {
       
    60 						this.activeHandler = handler.initialize.call( handler, settings );
       
    61 
       
    62 						// Dispatch custom event when the video is loaded.
       
    63 						trigger( document, 'wp-custom-header-video-loaded' );
       
    64 						break;
       
    65 					}
       
    66 				}
       
    67 			}
       
    68 		},
       
    69 
       
    70 		/**
       
    71 		 * Determines if the current environment supports video.
       
    72 		 *
       
    73 		 * Themes and plugins can override this method to change the criteria.
       
    74 		 *
       
    75 		 * @return {boolean}
       
    76 		 */
       
    77 		supportsVideo: function() {
       
    78 			// Don't load video on small screens. @todo: consider bandwidth and other factors.
       
    79 			if ( window.innerWidth < settings.minWidth || window.innerHeight < settings.minHeight ) {
       
    80 				return false;
       
    81 			}
       
    82 
       
    83 			return true;
       
    84 		},
       
    85 
       
    86 		/**
       
    87 		 * Base handler for custom handlers to extend.
       
    88 		 *
       
    89 		 * @type {BaseHandler}
       
    90 		 */
       
    91 		BaseVideoHandler: BaseHandler
       
    92 	};
       
    93 
       
    94 	/**
       
    95 	 * Create a video handler instance.
       
    96 	 *
       
    97 	 * @memberOf wp
       
    98 	 *
       
    99 	 * @class
       
   100 	 */
       
   101 	function BaseHandler() {}
       
   102 
       
   103 	BaseHandler.prototype = {
       
   104 		/**
       
   105 		 * Initialize the video handler.
       
   106 		 *
       
   107 		 * @param {object} settings Video settings.
       
   108 		 */
       
   109 		initialize: function( settings ) {
       
   110 			var handler = this,
       
   111 				button = document.createElement( 'button' );
       
   112 
       
   113 			this.settings = settings;
       
   114 			this.container = document.getElementById( 'wp-custom-header' );
       
   115 			this.button = button;
       
   116 
       
   117 			button.setAttribute( 'type', 'button' );
       
   118 			button.setAttribute( 'id', 'wp-custom-header-video-button' );
       
   119 			button.setAttribute( 'class', 'wp-custom-header-video-button wp-custom-header-video-play' );
       
   120 			button.innerHTML = settings.l10n.play;
       
   121 
       
   122 			// Toggle video playback when the button is clicked.
       
   123 			button.addEventListener( 'click', function() {
       
   124 				if ( handler.isPaused() ) {
       
   125 					handler.play();
       
   126 				} else {
       
   127 					handler.pause();
       
   128 				}
       
   129 			});
       
   130 
       
   131 			// Update the button class and text when the video state changes.
       
   132 			this.container.addEventListener( 'play', function() {
       
   133 				button.className = 'wp-custom-header-video-button wp-custom-header-video-play';
       
   134 				button.innerHTML = settings.l10n.pause;
       
   135 				if ( 'a11y' in window.wp ) {
       
   136 					window.wp.a11y.speak( settings.l10n.playSpeak);
       
   137 				}
       
   138 			});
       
   139 
       
   140 			this.container.addEventListener( 'pause', function() {
       
   141 				button.className = 'wp-custom-header-video-button wp-custom-header-video-pause';
       
   142 				button.innerHTML = settings.l10n.play;
       
   143 				if ( 'a11y' in window.wp ) {
       
   144 					window.wp.a11y.speak( settings.l10n.pauseSpeak);
       
   145 				}
       
   146 			});
       
   147 
       
   148 			this.ready();
       
   149 		},
       
   150 
       
   151 		/**
       
   152 		 * Ready method called after a handler is initialized.
       
   153 		 *
       
   154 		 * @abstract
       
   155 		 */
       
   156 		ready: function() {},
       
   157 
       
   158 		/**
       
   159 		 * Whether the video is paused.
       
   160 		 *
       
   161 		 * @abstract
       
   162 		 * @return {boolean}
       
   163 		 */
       
   164 		isPaused: function() {},
       
   165 
       
   166 		/**
       
   167 		 * Pause the video.
       
   168 		 *
       
   169 		 * @abstract
       
   170 		 */
       
   171 		pause: function() {},
       
   172 
       
   173 		/**
       
   174 		 * Play the video.
       
   175 		 *
       
   176 		 * @abstract
       
   177 		 */
       
   178 		play: function() {},
       
   179 
       
   180 		/**
       
   181 		 * Append a video node to the header container.
       
   182 		 *
       
   183 		 * @param {Element} node HTML element.
       
   184 		 */
       
   185 		setVideo: function( node ) {
       
   186 			var editShortcutNode,
       
   187 				editShortcut = this.container.getElementsByClassName( 'customize-partial-edit-shortcut' );
       
   188 
       
   189 			if ( editShortcut.length ) {
       
   190 				editShortcutNode = this.container.removeChild( editShortcut[0] );
       
   191 			}
       
   192 
       
   193 			this.container.innerHTML = '';
       
   194 			this.container.appendChild( node );
       
   195 
       
   196 			if ( editShortcutNode ) {
       
   197 				this.container.appendChild( editShortcutNode );
       
   198 			}
       
   199 		},
       
   200 
       
   201 		/**
       
   202 		 * Show the video controls.
       
   203 		 *
       
   204 		 * Appends a play/pause button to header container.
       
   205 		 */
       
   206 		showControls: function() {
       
   207 			if ( ! this.container.contains( this.button ) ) {
       
   208 				this.container.appendChild( this.button );
       
   209 			}
       
   210 		},
       
   211 
       
   212 		/**
       
   213 		 * Whether the handler can process a video.
       
   214 		 *
       
   215 		 * @abstract
       
   216 		 * @param {object} settings Video settings.
       
   217 		 * @return {boolean}
       
   218 		 */
       
   219 		test: function() {
       
   220 			return false;
       
   221 		},
       
   222 
       
   223 		/**
       
   224 		 * Trigger an event on the header container.
       
   225 		 *
       
   226 		 * @param {string} name Event name.
       
   227 		 */
       
   228 		trigger: function( name ) {
       
   229 			trigger( this.container, name );
       
   230 		}
       
   231 	};
       
   232 
       
   233 	/**
       
   234 	 * Create a custom handler.
       
   235 	 *
       
   236 	 * @memberOf wp
       
   237 	 *
       
   238 	 * @param {object} protoProps Properties to apply to the prototype.
       
   239 	 * @return CustomHandler The subclass.
       
   240 	 */
       
   241 	BaseHandler.extend = function( protoProps ) {
       
   242 		var prop;
       
   243 
       
   244 		function CustomHandler() {
       
   245 			var result = BaseHandler.apply( this, arguments );
       
   246 			return result;
       
   247 		}
       
   248 
       
   249 		CustomHandler.prototype = Object.create( BaseHandler.prototype );
       
   250 		CustomHandler.prototype.constructor = CustomHandler;
       
   251 
       
   252 		for ( prop in protoProps ) {
       
   253 			CustomHandler.prototype[ prop ] = protoProps[ prop ];
       
   254 		}
       
   255 
       
   256 		return CustomHandler;
       
   257 	};
       
   258 
       
   259 	/**
       
   260 	 * Native video handler.
       
   261 	 *
       
   262 	 * @memberOf wp
       
   263 	 *
       
   264 	 * @class
       
   265 	 */
       
   266 	NativeHandler = BaseHandler.extend(/** @lends wp.NativeHandler.prototype */{
       
   267 		/**
       
   268 		 * Whether the native handler supports a video.
       
   269 		 *
       
   270 		 * @param {object} settings Video settings.
       
   271 		 * @return {boolean}
       
   272 		 */
       
   273 		test: function( settings ) {
       
   274 			var video = document.createElement( 'video' );
       
   275 			return video.canPlayType( settings.mimeType );
       
   276 		},
       
   277 
       
   278 		/**
       
   279 		 * Set up a native video element.
       
   280 		 */
       
   281 		ready: function() {
       
   282 			var handler = this,
       
   283 				video = document.createElement( 'video' );
       
   284 
       
   285 			video.id = 'wp-custom-header-video';
       
   286 			video.autoplay = 'autoplay';
       
   287 			video.loop = 'loop';
       
   288 			video.muted = 'muted';
       
   289 			video.width = this.settings.width;
       
   290 			video.height = this.settings.height;
       
   291 
       
   292 			video.addEventListener( 'play', function() {
       
   293 				handler.trigger( 'play' );
       
   294 			});
       
   295 
       
   296 			video.addEventListener( 'pause', function() {
       
   297 				handler.trigger( 'pause' );
       
   298 			});
       
   299 
       
   300 			video.addEventListener( 'canplay', function() {
       
   301 				handler.showControls();
       
   302 			});
       
   303 
       
   304 			this.video = video;
       
   305 			handler.setVideo( video );
       
   306 			video.src = this.settings.videoUrl;
       
   307 		},
       
   308 
       
   309 		/**
       
   310 		 * Whether the video is paused.
       
   311 		 *
       
   312 		 * @return {boolean}
       
   313 		 */
       
   314 		isPaused: function() {
       
   315 			return this.video.paused;
       
   316 		},
       
   317 
       
   318 		/**
       
   319 		 * Pause the video.
       
   320 		 */
       
   321 		pause: function() {
       
   322 			this.video.pause();
       
   323 		},
       
   324 
       
   325 		/**
       
   326 		 * Play the video.
       
   327 		 */
       
   328 		play: function() {
       
   329 			this.video.play();
       
   330 		}
       
   331 	});
       
   332 
       
   333 	/**
       
   334 	 * YouTube video handler.
       
   335 	 *
       
   336 	 * @memberOf wp
       
   337 	 *
       
   338 	 * @class wp.YouTubeHandler
       
   339 	 */
       
   340 	YouTubeHandler = BaseHandler.extend(/** @lends wp.YouTubeHandler.prototype */{
       
   341 		/**
       
   342 		 * Whether the handler supports a video.
       
   343 		 *
       
   344 		 * @param {object} settings Video settings.
       
   345 		 * @return {boolean}
       
   346 		 */
       
   347 		test: function( settings ) {
       
   348 			return 'video/x-youtube' === settings.mimeType;
       
   349 		},
       
   350 
       
   351 		/**
       
   352 		 * Set up a YouTube iframe.
       
   353 		 *
       
   354 		 * Loads the YouTube IFrame API if the 'YT' global doesn't exist.
       
   355 		 */
       
   356 		ready: function() {
       
   357 			var handler = this;
       
   358 
       
   359 			if ( 'YT' in window ) {
       
   360 				YT.ready( handler.loadVideo.bind( handler ) );
       
   361 			} else {
       
   362 				var tag = document.createElement( 'script' );
       
   363 				tag.src = 'https://www.youtube.com/iframe_api';
       
   364 				tag.onload = function () {
       
   365 					YT.ready( handler.loadVideo.bind( handler ) );
       
   366 				};
       
   367 
       
   368 				document.getElementsByTagName( 'head' )[0].appendChild( tag );
       
   369 			}
       
   370 		},
       
   371 
       
   372 		/**
       
   373 		 * Load a YouTube video.
       
   374 		 */
       
   375 		loadVideo: function() {
       
   376 			var handler = this,
       
   377 				video = document.createElement( 'div' ),
       
   378 				// @link http://stackoverflow.com/a/27728417
       
   379 				VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
       
   380 
       
   381 			video.id = 'wp-custom-header-video';
       
   382 			handler.setVideo( video );
       
   383 
       
   384 			handler.player = new YT.Player( video, {
       
   385 				height: this.settings.height,
       
   386 				width: this.settings.width,
       
   387 				videoId: this.settings.videoUrl.match( VIDEO_ID_REGEX )[1],
       
   388 				events: {
       
   389 					onReady: function( e ) {
       
   390 						e.target.mute();
       
   391 						handler.showControls();
       
   392 					},
       
   393 					onStateChange: function( e ) {
       
   394 						if ( YT.PlayerState.PLAYING === e.data ) {
       
   395 							handler.trigger( 'play' );
       
   396 						} else if ( YT.PlayerState.PAUSED === e.data ) {
       
   397 							handler.trigger( 'pause' );
       
   398 						} else if ( YT.PlayerState.ENDED === e.data ) {
       
   399 							e.target.playVideo();
       
   400 						}
       
   401 					}
       
   402 				},
       
   403 				playerVars: {
       
   404 					autoplay: 1,
       
   405 					controls: 0,
       
   406 					disablekb: 1,
       
   407 					fs: 0,
       
   408 					iv_load_policy: 3,
       
   409 					loop: 1,
       
   410 					modestbranding: 1,
       
   411 					playsinline: 1,
       
   412 					rel: 0,
       
   413 					showinfo: 0
       
   414 				}
       
   415 			});
       
   416 		},
       
   417 
       
   418 		/**
       
   419 		 * Whether the video is paused.
       
   420 		 *
       
   421 		 * @return {boolean}
       
   422 		 */
       
   423 		isPaused: function() {
       
   424 			return YT.PlayerState.PAUSED === this.player.getPlayerState();
       
   425 		},
       
   426 
       
   427 		/**
       
   428 		 * Pause the video.
       
   429 		 */
       
   430 		pause: function() {
       
   431 			this.player.pauseVideo();
       
   432 		},
       
   433 
       
   434 		/**
       
   435 		 * Play the video.
       
   436 		 */
       
   437 		play: function() {
       
   438 			this.player.playVideo();
       
   439 		}
       
   440 	});
       
   441 
       
   442 	// Initialize the custom header when the DOM is ready.
       
   443 	window.wp.customHeader = new CustomHeader();
       
   444 	document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize.bind( window.wp.customHeader ), false );
       
   445 
       
   446 	// Selective refresh support in the Customizer.
       
   447 	if ( 'customize' in window.wp ) {
       
   448 		window.wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
       
   449 			if ( 'custom_header_settings' in response ) {
       
   450 				settings = response.custom_header_settings;
       
   451 			}
       
   452 		});
       
   453 
       
   454 		window.wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
       
   455 			if ( 'custom_header' === placement.partial.id ) {
       
   456 				window.wp.customHeader.initialize();
       
   457 			}
       
   458 		});
       
   459 	}
       
   460 
       
   461 })( window, window._wpCustomHeaderSettings || {} );