wp/wp-includes/js/customize-base.js
changeset 5 5e2f62d02dcd
parent 0 d970ebf37754
child 7 cf61fcea0001
equal deleted inserted replaced
4:346c88efed21 5:5e2f62d02dcd
     1 window.wp = window.wp || {};
     1 window.wp = window.wp || {};
     2 
     2 
     3 (function( exports, $ ){
     3 (function( exports, $ ){
     4 	var api, extend, ctor, inherits,
     4 	var api = {}, ctor, inherits,
     5 		slice = Array.prototype.slice;
     5 		slice = Array.prototype.slice;
     6 
       
     7 	/* =====================================================================
       
     8 	 * Micro-inheritance - thank you, backbone.js.
       
     9 	 * ===================================================================== */
       
    10 
       
    11 	extend = function( protoProps, classProps ) {
       
    12 		var child = inherits( this, protoProps, classProps );
       
    13 		child.extend = this.extend;
       
    14 		return child;
       
    15 	};
       
    16 
     6 
    17 	// Shared empty constructor function to aid in prototype-chain creation.
     7 	// Shared empty constructor function to aid in prototype-chain creation.
    18 	ctor = function() {};
     8 	ctor = function() {};
    19 
     9 
    20 	// Helper function to correctly set up the prototype chain, for subclasses.
    10 	/**
    21 	// Similar to `goog.inherits`, but uses a hash of prototype properties and
    11 	 * Helper function to correctly set up the prototype chain, for subclasses.
    22 	// class properties to be extended.
    12 	 * Similar to `goog.inherits`, but uses a hash of prototype properties and
       
    13 	 * class properties to be extended.
       
    14 	 *
       
    15 	 * @param  object parent      Parent class constructor to inherit from.
       
    16 	 * @param  object protoProps  Properties to apply to the prototype for use as class instance properties.
       
    17 	 * @param  object staticProps Properties to apply directly to the class constructor.
       
    18 	 * @return child              The subclassed constructor.
       
    19 	 */
    23 	inherits = function( parent, protoProps, staticProps ) {
    20 	inherits = function( parent, protoProps, staticProps ) {
    24 		var child;
    21 		var child;
    25 
    22 
    26 		// The constructor function for the new subclass is either defined by you
    23 		// The constructor function for the new subclass is either defined by you
    27 		// (the "constructor" property in your `extend` definition), or defaulted
    24 		// (the "constructor" property in your `extend` definition), or defaulted
    63 		child.__super__ = parent.prototype;
    60 		child.__super__ = parent.prototype;
    64 
    61 
    65 		return child;
    62 		return child;
    66 	};
    63 	};
    67 
    64 
    68 	api = {};
    65 	/**
    69 
    66 	 * Base class for object inheritance.
    70 	/* =====================================================================
    67 	 */
    71 	 * Base class.
       
    72 	 * ===================================================================== */
       
    73 
       
    74 	api.Class = function( applicator, argsArray, options ) {
    68 	api.Class = function( applicator, argsArray, options ) {
    75 		var magic, args = arguments;
    69 		var magic, args = arguments;
    76 
    70 
    77 		if ( applicator && argsArray && api.Class.applicator === applicator ) {
    71 		if ( applicator && argsArray && api.Class.applicator === applicator ) {
    78 			args = argsArray;
    72 			args = argsArray;
    88 			$.extend( magic, this );
    82 			$.extend( magic, this );
    89 		}
    83 		}
    90 
    84 
    91 		magic.initialize.apply( magic, args );
    85 		magic.initialize.apply( magic, args );
    92 		return magic;
    86 		return magic;
       
    87 	};
       
    88 
       
    89 	/**
       
    90 	 * Creates a subclass of the class.
       
    91 	 *
       
    92 	 * @param  object protoProps  Properties to apply to the prototype.
       
    93 	 * @param  object staticProps Properties to apply directly to the class.
       
    94 	 * @return child              The subclass.
       
    95 	 */
       
    96 	api.Class.extend = function( protoProps, classProps ) {
       
    97 		var child = inherits( this, protoProps, classProps );
       
    98 		child.extend = this.extend;
       
    99 		return child;
    93 	};
   100 	};
    94 
   101 
    95 	api.Class.applicator = {};
   102 	api.Class.applicator = {};
    96 
   103 
    97 	api.Class.prototype.initialize = function() {};
   104 	api.Class.prototype.initialize = function() {};
   114 			proto = proto.constructor.__super__;
   121 			proto = proto.constructor.__super__;
   115 		}
   122 		}
   116 		return false;
   123 		return false;
   117 	};
   124 	};
   118 
   125 
   119 	api.Class.extend = extend;
   126 	/**
   120 
   127 	 * An events manager object, offering the ability to bind to and trigger events.
   121 	/* =====================================================================
   128 	 *
   122 	 * Events mixin.
   129 	 * Used as a mixin.
   123 	 * ===================================================================== */
   130 	 */
   124 
       
   125 	api.Events = {
   131 	api.Events = {
   126 		trigger: function( id ) {
   132 		trigger: function( id ) {
   127 			if ( this.topics && this.topics[ id ] )
   133 			if ( this.topics && this.topics[ id ] )
   128 				this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
   134 				this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
   129 			return this;
   135 			return this;
   130 		},
   136 		},
   131 
   137 
   132 		bind: function( id, callback ) {
   138 		bind: function( id ) {
   133 			this.topics = this.topics || {};
   139 			this.topics = this.topics || {};
   134 			this.topics[ id ] = this.topics[ id ] || $.Callbacks();
   140 			this.topics[ id ] = this.topics[ id ] || $.Callbacks();
   135 			this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
   141 			this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
   136 			return this;
   142 			return this;
   137 		},
   143 		},
   138 
   144 
   139 		unbind: function( id, callback ) {
   145 		unbind: function( id ) {
   140 			if ( this.topics && this.topics[ id ] )
   146 			if ( this.topics && this.topics[ id ] )
   141 				this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
   147 				this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
   142 			return this;
   148 			return this;
   143 		}
   149 		}
   144 	};
   150 	};
   145 
   151 
   146 	/* =====================================================================
   152 	/**
   147 	 * Observable values that support two-way binding.
   153 	 * Observable values that support two-way binding.
   148 	 * ===================================================================== */
   154 	 *
   149 
   155 	 * @constuctor
       
   156 	 */
   150 	api.Value = api.Class.extend({
   157 	api.Value = api.Class.extend({
   151 		initialize: function( initial, options ) {
   158 		initialize: function( initial, options ) {
   152 			this._value = initial; // @todo: potentially change this to a this.set() call.
   159 			this._value = initial; // @todo: potentially change this to a this.set() call.
   153 			this.callbacks = $.Callbacks();
   160 			this.callbacks = $.Callbacks();
       
   161 			this._dirty = false;
   154 
   162 
   155 			$.extend( this, options || {} );
   163 			$.extend( this, options || {} );
   156 
   164 
   157 			this.set = $.proxy( this.set, this );
   165 			this.set = $.proxy( this.set, this );
   158 		},
   166 		},
   174 
   182 
   175 			to = this._setter.apply( this, arguments );
   183 			to = this._setter.apply( this, arguments );
   176 			to = this.validate( to );
   184 			to = this.validate( to );
   177 
   185 
   178 			// Bail if the sanitized value is null or unchanged.
   186 			// Bail if the sanitized value is null or unchanged.
   179 			if ( null === to || this._value === to )
   187 			if ( null === to || _.isEqual( from, to ) ) {
   180 				return this;
   188 				return this;
       
   189 			}
   181 
   190 
   182 			this._value = to;
   191 			this._value = to;
       
   192 			this._dirty = true;
   183 
   193 
   184 			this.callbacks.fireWith( this, [ to, from ] );
   194 			this.callbacks.fireWith( this, [ to, from ] );
   185 
   195 
   186 			return this;
   196 			return this;
   187 		},
   197 		},
   207 
   217 
   208 		validate: function( value ) {
   218 		validate: function( value ) {
   209 			return value;
   219 			return value;
   210 		},
   220 		},
   211 
   221 
   212 		bind: function( callback ) {
   222 		bind: function() {
   213 			this.callbacks.add.apply( this.callbacks, arguments );
   223 			this.callbacks.add.apply( this.callbacks, arguments );
   214 			return this;
   224 			return this;
   215 		},
   225 		},
   216 
   226 
   217 		unbind: function( callback ) {
   227 		unbind: function() {
   218 			this.callbacks.remove.apply( this.callbacks, arguments );
   228 			this.callbacks.remove.apply( this.callbacks, arguments );
   219 			return this;
   229 			return this;
   220 		},
   230 		},
   221 
   231 
   222 		link: function() { // values*
   232 		link: function() { // values*
   252 			});
   262 			});
   253 			return this;
   263 			return this;
   254 		}
   264 		}
   255 	});
   265 	});
   256 
   266 
   257 	/* =====================================================================
   267 	/**
   258 	 * A collection of observable values.
   268 	 * A collection of observable values.
   259 	 * ===================================================================== */
   269 	 *
   260 
   270 	 * @constuctor
       
   271 	 * @augments wp.customize.Class
       
   272 	 * @mixes wp.customize.Events
       
   273 	 */
   261 	api.Values = api.Class.extend({
   274 	api.Values = api.Class.extend({
   262 		defaultConstructor: api.Value,
   275 		defaultConstructor: api.Value,
   263 
   276 
   264 		initialize: function( options ) {
   277 		initialize: function( options ) {
   265 			$.extend( this, options || {} );
   278 			$.extend( this, options || {} );
   377 		}
   390 		}
   378 	});
   391 	});
   379 
   392 
   380 	$.extend( api.Values.prototype, api.Events );
   393 	$.extend( api.Values.prototype, api.Events );
   381 
   394 
   382 	/* =====================================================================
   395 
   383 	 * An observable value that syncs with an element.
   396 	/**
   384 	 *
   397 	 * Cast a string to a jQuery collection if it isn't already.
   385 	 * Handles inputs, selects, and textareas by default.
   398 	 *
   386 	 * ===================================================================== */
   399 	 * @param {string|jQuery collection} element
   387 
   400 	 */
   388 	api.ensure = function( element ) {
   401 	api.ensure = function( element ) {
   389 		return typeof element == 'string' ? $( element ) : element;
   402 		return typeof element == 'string' ? $( element ) : element;
   390 	};
   403 	};
   391 
   404 
       
   405 	/**
       
   406 	 * An observable value that syncs with an element.
       
   407 	 *
       
   408 	 * Handles inputs, selects, and textareas by default.
       
   409 	 *
       
   410 	 * @constuctor
       
   411 	 * @augments wp.customize.Value
       
   412 	 * @augments wp.customize.Class
       
   413 	 */
   392 	api.Element = api.Value.extend({
   414 	api.Element = api.Value.extend({
   393 		initialize: function( element, options ) {
   415 		initialize: function( element, options ) {
   394 			var self = this,
   416 			var self = this,
   395 				synchronizer = api.Element.synchronizer.html,
   417 				synchronizer = api.Element.synchronizer.html,
   396 				type, update, refresh;
   418 				type, update, refresh;
   402 				this.events += 'change';
   424 				this.events += 'change';
   403 				synchronizer = api.Element.synchronizer.val;
   425 				synchronizer = api.Element.synchronizer.val;
   404 
   426 
   405 				if ( this.element.is('input') ) {
   427 				if ( this.element.is('input') ) {
   406 					type = this.element.prop('type');
   428 					type = this.element.prop('type');
   407 					if ( api.Element.synchronizer[ type ] )
   429 					if ( api.Element.synchronizer[ type ] ) {
   408 						synchronizer = api.Element.synchronizer[ type ];
   430 						synchronizer = api.Element.synchronizer[ type ];
   409 					if ( 'text' === type || 'password' === type )
   431 					}
       
   432 					if ( 'text' === type || 'password' === type ) {
   410 						this.events += ' keyup';
   433 						this.events += ' keyup';
       
   434 					} else if ( 'range' === type ) {
       
   435 						this.events += ' input propertychange';
       
   436 					}
   411 				} else if ( this.element.is('textarea') ) {
   437 				} else if ( this.element.is('textarea') ) {
   412 					this.events += ' keyup';
   438 					this.events += ' keyup';
   413 				}
   439 				}
   414 			}
   440 			}
   415 
   441 
   440 		update: function() {}
   466 		update: function() {}
   441 	});
   467 	});
   442 
   468 
   443 	api.Element.synchronizer = {};
   469 	api.Element.synchronizer = {};
   444 
   470 
   445 	$.each( [ 'html', 'val' ], function( i, method ) {
   471 	$.each( [ 'html', 'val' ], function( index, method ) {
   446 		api.Element.synchronizer[ method ] = {
   472 		api.Element.synchronizer[ method ] = {
   447 			update: function( to ) {
   473 			update: function( to ) {
   448 				this.element[ method ]( to );
   474 				this.element[ method ]( to );
   449 			},
   475 			},
   450 			refresh: function() {
   476 			refresh: function() {
   471 		refresh: function() {
   497 		refresh: function() {
   472 			return this.element.filter( ':checked' ).val();
   498 			return this.element.filter( ':checked' ).val();
   473 		}
   499 		}
   474 	};
   500 	};
   475 
   501 
   476 	/* =====================================================================
   502 	$.support.postMessage = !! window.postMessage;
       
   503 
       
   504 	/**
   477 	 * Messenger for postMessage.
   505 	 * Messenger for postMessage.
   478 	 * ===================================================================== */
   506 	 *
   479 
   507 	 * @constuctor
   480 	$.support.postMessage = !! window.postMessage;
   508 	 * @augments wp.customize.Class
   481 
   509 	 * @mixes wp.customize.Events
       
   510 	 */
   482 	api.Messenger = api.Class.extend({
   511 	api.Messenger = api.Class.extend({
       
   512 		/**
       
   513 		 * Create a new Value.
       
   514 		 *
       
   515 		 * @param  {string} key     Unique identifier.
       
   516 		 * @param  {mixed}  initial Initial value.
       
   517 		 * @param  {mixed}  options Options hash. Optional.
       
   518 		 * @return {Value}          Class instance of the Value.
       
   519 		 */
   483 		add: function( key, initial, options ) {
   520 		add: function( key, initial, options ) {
   484 			return this[ key ] = new api.Value( initial, options );
   521 			return this[ key ] = new api.Value( initial, options );
   485 		},
   522 		},
   486 
   523 
   487 		/**
   524 		/**
   499 
   536 
   500 			$.extend( this, options || {} );
   537 			$.extend( this, options || {} );
   501 
   538 
   502 			this.add( 'channel', params.channel );
   539 			this.add( 'channel', params.channel );
   503 			this.add( 'url', params.url || '' );
   540 			this.add( 'url', params.url || '' );
   504 			this.add( 'targetWindow', params.targetWindow || defaultTarget );
       
   505 			this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
   541 			this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
   506 				return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
   542 				return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
   507 			});
   543 			});
       
   544 
       
   545 			// first add with no value
       
   546 			this.add( 'targetWindow', null );
       
   547 			// This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios.
       
   548 			this.targetWindow.set = function( to ) {
       
   549 				var from = this._value;
       
   550 
       
   551 				to = this._setter.apply( this, arguments );
       
   552 				to = this.validate( to );
       
   553 
       
   554 				if ( null === to || from === to ) {
       
   555 					return this;
       
   556 				}
       
   557 
       
   558 				this._value = to;
       
   559 				this._dirty = true;
       
   560 
       
   561 				this.callbacks.fireWith( this, [ to, from ] );
       
   562 
       
   563 				return this;
       
   564 			};
       
   565 			// now set it
       
   566 			this.targetWindow( params.targetWindow || defaultTarget );
       
   567 
   508 
   568 
   509 			// Since we want jQuery to treat the receive function as unique
   569 			// Since we want jQuery to treat the receive function as unique
   510 			// to this instance, we give the function a new guid.
   570 			// to this instance, we give the function a new guid.
   511 			//
   571 			//
   512 			// This will prevent every Messenger's receive function from being
   572 			// This will prevent every Messenger's receive function from being
   531 
   591 
   532 			// Check to make sure the origin is valid.
   592 			// Check to make sure the origin is valid.
   533 			if ( this.origin() && event.origin !== this.origin() )
   593 			if ( this.origin() && event.origin !== this.origin() )
   534 				return;
   594 				return;
   535 
   595 
       
   596 			// Ensure we have a string that's JSON.parse-able
       
   597 			if ( typeof event.data !== 'string' || event.data[0] !== '{' ) {
       
   598 				return;
       
   599 			}
       
   600 
   536 			message = JSON.parse( event.data );
   601 			message = JSON.parse( event.data );
   537 
   602 
   538 			// Check required message properties.
   603 			// Check required message properties.
   539 			if ( ! message || ! message.id || typeof message.data === 'undefined' )
   604 			if ( ! message || ! message.id || typeof message.data === 'undefined' )
   540 				return;
   605 				return;
   563 	});
   628 	});
   564 
   629 
   565 	// Add the Events mixin to api.Messenger.
   630 	// Add the Events mixin to api.Messenger.
   566 	$.extend( api.Messenger.prototype, api.Events );
   631 	$.extend( api.Messenger.prototype, api.Events );
   567 
   632 
   568 	/* =====================================================================
   633 	// Core customize object.
   569 	 * Core customize object.
       
   570 	 * ===================================================================== */
       
   571 
       
   572 	api = $.extend( new api.Values(), api );
   634 	api = $.extend( new api.Values(), api );
   573 	api.get = function() {
   635 	api.get = function() {
   574 		var result = {};
   636 		var result = {};
   575 
   637 
   576 		this.each( function( obj, key ) {
   638 		this.each( function( obj, key ) {
   578 		});
   640 		});
   579 
   641 
   580 		return result;
   642 		return result;
   581 	};
   643 	};
   582 
   644 
   583 	// Expose the API to the world.
   645 	// Expose the API publicly on window.wp.customize
   584 	exports.customize = api;
   646 	exports.customize = api;
   585 })( wp, jQuery );
   647 })( wp, jQuery );