web/wp-includes/js/customize-base.dev.js
changeset 204 09a1c134465b
parent 203 f507feede89a
child 205 a4f7897e21a9
equal deleted inserted replaced
203:f507feede89a 204:09a1c134465b
     1 if ( typeof wp === 'undefined' )
       
     2 	var wp = {};
       
     3 
       
     4 (function( exports, $ ){
       
     5 	var api, extend, ctor, inherits,
       
     6 		slice = Array.prototype.slice;
       
     7 
       
     8 	/* =====================================================================
       
     9 	 * Micro-inheritance - thank you, backbone.js.
       
    10 	 * ===================================================================== */
       
    11 
       
    12 	extend = function( protoProps, classProps ) {
       
    13 		var child = inherits( this, protoProps, classProps );
       
    14 		child.extend = this.extend;
       
    15 		return child;
       
    16 	};
       
    17 
       
    18 	// Shared empty constructor function to aid in prototype-chain creation.
       
    19 	ctor = function() {};
       
    20 
       
    21 	// Helper function to correctly set up the prototype chain, for subclasses.
       
    22 	// Similar to `goog.inherits`, but uses a hash of prototype properties and
       
    23 	// class properties to be extended.
       
    24 	inherits = function( parent, protoProps, staticProps ) {
       
    25 		var child;
       
    26 
       
    27 		// The constructor function for the new subclass is either defined by you
       
    28 		// (the "constructor" property in your `extend` definition), or defaulted
       
    29 		// by us to simply call `super()`.
       
    30 		if ( protoProps && protoProps.hasOwnProperty( 'constructor' ) ) {
       
    31 			child = protoProps.constructor;
       
    32 		} else {
       
    33 			child = function() {
       
    34 				// Storing the result `super()` before returning the value
       
    35 				// prevents a bug in Opera where, if the constructor returns
       
    36 				// a function, Opera will reject the return value in favor of
       
    37 				// the original object. This causes all sorts of trouble.
       
    38 				var result = parent.apply( this, arguments );
       
    39 				return result;
       
    40 			};
       
    41 		}
       
    42 
       
    43 		// Inherit class (static) properties from parent.
       
    44 		$.extend( child, parent );
       
    45 
       
    46 		// Set the prototype chain to inherit from `parent`, without calling
       
    47 		// `parent`'s constructor function.
       
    48 		ctor.prototype  = parent.prototype;
       
    49 		child.prototype = new ctor();
       
    50 
       
    51 		// Add prototype properties (instance properties) to the subclass,
       
    52 		// if supplied.
       
    53 		if ( protoProps )
       
    54 			$.extend( child.prototype, protoProps );
       
    55 
       
    56 		// Add static properties to the constructor function, if supplied.
       
    57 		if ( staticProps )
       
    58 			$.extend( child, staticProps );
       
    59 
       
    60 		// Correctly set child's `prototype.constructor`.
       
    61 		child.prototype.constructor = child;
       
    62 
       
    63 		// Set a convenience property in case the parent's prototype is needed later.
       
    64 		child.__super__ = parent.prototype;
       
    65 
       
    66 		return child;
       
    67 	};
       
    68 
       
    69 	api = {};
       
    70 
       
    71 	/* =====================================================================
       
    72 	 * Base class.
       
    73 	 * ===================================================================== */
       
    74 
       
    75 	api.Class = function( applicator, argsArray, options ) {
       
    76 		var magic, args = arguments;
       
    77 
       
    78 		if ( applicator && argsArray && api.Class.applicator === applicator ) {
       
    79 			args = argsArray;
       
    80 			$.extend( this, options || {} );
       
    81 		}
       
    82 
       
    83 		magic = this;
       
    84 		if ( this.instance ) {
       
    85 			magic = function() {
       
    86 				return magic.instance.apply( magic, arguments );
       
    87 			};
       
    88 
       
    89 			$.extend( magic, this );
       
    90 		}
       
    91 
       
    92 		magic.initialize.apply( magic, args );
       
    93 		return magic;
       
    94 	};
       
    95 
       
    96 	api.Class.applicator = {};
       
    97 
       
    98 	api.Class.prototype.initialize = function() {};
       
    99 
       
   100 	/*
       
   101 	 * Checks whether a given instance extended a constructor.
       
   102 	 *
       
   103 	 * The magic surrounding the instance parameter causes the instanceof
       
   104 	 * keyword to return inaccurate results; it defaults to the function's
       
   105 	 * prototype instead of the constructor chain. Hence this function.
       
   106 	 */
       
   107 	api.Class.prototype.extended = function( constructor ) {
       
   108 		var proto = this;
       
   109 
       
   110 		while ( typeof proto.constructor !== 'undefined' ) {
       
   111 			if ( proto.constructor === constructor )
       
   112 				return true;
       
   113 			if ( typeof proto.constructor.__super__ === 'undefined' )
       
   114 				return false;
       
   115 			proto = proto.constructor.__super__;
       
   116 		}
       
   117 		return false;
       
   118 	};
       
   119 
       
   120 	api.Class.extend = extend;
       
   121 
       
   122 	/* =====================================================================
       
   123 	 * Events mixin.
       
   124 	 * ===================================================================== */
       
   125 
       
   126 	api.Events = {
       
   127 		trigger: function( id ) {
       
   128 			if ( this.topics && this.topics[ id ] )
       
   129 				this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
       
   130 			return this;
       
   131 		},
       
   132 
       
   133 		bind: function( id, callback ) {
       
   134 			this.topics = this.topics || {};
       
   135 			this.topics[ id ] = this.topics[ id ] || $.Callbacks();
       
   136 			this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
       
   137 			return this;
       
   138 		},
       
   139 
       
   140 		unbind: function( id, callback ) {
       
   141 			if ( this.topics && this.topics[ id ] )
       
   142 				this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
       
   143 			return this;
       
   144 		}
       
   145 	};
       
   146 
       
   147 	/* =====================================================================
       
   148 	 * Observable values that support two-way binding.
       
   149 	 * ===================================================================== */
       
   150 
       
   151 	api.Value = api.Class.extend({
       
   152 		initialize: function( initial, options ) {
       
   153 			this._value = initial; // @todo: potentially change this to a this.set() call.
       
   154 			this.callbacks = $.Callbacks();
       
   155 
       
   156 			$.extend( this, options || {} );
       
   157 
       
   158 			this.set = $.proxy( this.set, this );
       
   159 		},
       
   160 
       
   161 		/*
       
   162 		 * Magic. Returns a function that will become the instance.
       
   163 		 * Set to null to prevent the instance from extending a function.
       
   164 		 */
       
   165 		instance: function() {
       
   166 			return arguments.length ? this.set.apply( this, arguments ) : this.get();
       
   167 		},
       
   168 
       
   169 		get: function() {
       
   170 			return this._value;
       
   171 		},
       
   172 
       
   173 		set: function( to ) {
       
   174 			var from = this._value;
       
   175 
       
   176 			to = this._setter.apply( this, arguments );
       
   177 			to = this.validate( to );
       
   178 
       
   179 			// Bail if the sanitized value is null or unchanged.
       
   180 			if ( null === to || this._value === to )
       
   181 				return this;
       
   182 
       
   183 			this._value = to;
       
   184 
       
   185 			this.callbacks.fireWith( this, [ to, from ] );
       
   186 
       
   187 			return this;
       
   188 		},
       
   189 
       
   190 		_setter: function( to ) {
       
   191 			return to;
       
   192 		},
       
   193 
       
   194 		setter: function( callback ) {
       
   195 			var from = this.get();
       
   196 			this._setter = callback;
       
   197 			// Temporarily clear value so setter can decide if it's valid.
       
   198 			this._value = null;
       
   199 			this.set( from );
       
   200 			return this;
       
   201 		},
       
   202 
       
   203 		resetSetter: function() {
       
   204 			this._setter = this.constructor.prototype._setter;
       
   205 			this.set( this.get() );
       
   206 			return this;
       
   207 		},
       
   208 
       
   209 		validate: function( value ) {
       
   210 			return value;
       
   211 		},
       
   212 
       
   213 		bind: function( callback ) {
       
   214 			this.callbacks.add.apply( this.callbacks, arguments );
       
   215 			return this;
       
   216 		},
       
   217 
       
   218 		unbind: function( callback ) {
       
   219 			this.callbacks.remove.apply( this.callbacks, arguments );
       
   220 			return this;
       
   221 		},
       
   222 
       
   223 		link: function() { // values*
       
   224 			var set = this.set;
       
   225 			$.each( arguments, function() {
       
   226 				this.bind( set );
       
   227 			});
       
   228 			return this;
       
   229 		},
       
   230 
       
   231 		unlink: function() { // values*
       
   232 			var set = this.set;
       
   233 			$.each( arguments, function() {
       
   234 				this.unbind( set );
       
   235 			});
       
   236 			return this;
       
   237 		},
       
   238 
       
   239 		sync: function() { // values*
       
   240 			var that = this;
       
   241 			$.each( arguments, function() {
       
   242 				that.link( this );
       
   243 				this.link( that );
       
   244 			});
       
   245 			return this;
       
   246 		},
       
   247 
       
   248 		unsync: function() { // values*
       
   249 			var that = this;
       
   250 			$.each( arguments, function() {
       
   251 				that.unlink( this );
       
   252 				this.unlink( that );
       
   253 			});
       
   254 			return this;
       
   255 		}
       
   256 	});
       
   257 
       
   258 	/* =====================================================================
       
   259 	 * A collection of observable values.
       
   260 	 * ===================================================================== */
       
   261 
       
   262 	api.Values = api.Class.extend({
       
   263 		defaultConstructor: api.Value,
       
   264 
       
   265 		initialize: function( options ) {
       
   266 			$.extend( this, options || {} );
       
   267 
       
   268 			this._value = {};
       
   269 			this._deferreds = {};
       
   270 		},
       
   271 
       
   272 		instance: function( id ) {
       
   273 			if ( arguments.length === 1 )
       
   274 				return this.value( id );
       
   275 
       
   276 			return this.when.apply( this, arguments );
       
   277 		},
       
   278 
       
   279 		value: function( id ) {
       
   280 			return this._value[ id ];
       
   281 		},
       
   282 
       
   283 		has: function( id ) {
       
   284 			return typeof this._value[ id ] !== 'undefined';
       
   285 		},
       
   286 
       
   287 		add: function( id, value ) {
       
   288 			if ( this.has( id ) )
       
   289 				return this.value( id );
       
   290 
       
   291 			this._value[ id ] = value;
       
   292 			value.parent = this;
       
   293 			if ( value.extended( api.Value ) )
       
   294 				value.bind( this._change );
       
   295 
       
   296 			this.trigger( 'add', value );
       
   297 
       
   298 			if ( this._deferreds[ id ] )
       
   299 				this._deferreds[ id ].resolve();
       
   300 
       
   301 			return this._value[ id ];
       
   302 		},
       
   303 
       
   304 		create: function( id ) {
       
   305 			return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
       
   306 		},
       
   307 
       
   308 		each: function( callback, context ) {
       
   309 			context = typeof context === 'undefined' ? this : context;
       
   310 
       
   311 			$.each( this._value, function( key, obj ) {
       
   312 				callback.call( context, obj, key );
       
   313 			});
       
   314 		},
       
   315 
       
   316 		remove: function( id ) {
       
   317 			var value;
       
   318 
       
   319 			if ( this.has( id ) ) {
       
   320 				value = this.value( id );
       
   321 				this.trigger( 'remove', value );
       
   322 				if ( value.extended( api.Value ) )
       
   323 					value.unbind( this._change );
       
   324 				delete value.parent;
       
   325 			}
       
   326 
       
   327 			delete this._value[ id ];
       
   328 			delete this._deferreds[ id ];
       
   329 		},
       
   330 
       
   331 		/**
       
   332 		 * Runs a callback once all requested values exist.
       
   333 		 *
       
   334 		 * when( ids*, [callback] );
       
   335 		 *
       
   336 		 * For example:
       
   337 		 *     when( id1, id2, id3, function( value1, value2, value3 ) {} );
       
   338 		 *
       
   339 		 * @returns $.Deferred.promise();
       
   340 		 */
       
   341 		when: function() {
       
   342 			var self = this,
       
   343 				ids  = slice.call( arguments ),
       
   344 				dfd  = $.Deferred();
       
   345 
       
   346 			// If the last argument is a callback, bind it to .done()
       
   347 			if ( $.isFunction( ids[ ids.length - 1 ] ) )
       
   348 				dfd.done( ids.pop() );
       
   349 
       
   350 			$.when.apply( $, $.map( ids, function( id ) {
       
   351 				if ( self.has( id ) )
       
   352 					return;
       
   353 
       
   354 				return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred();
       
   355 			})).done( function() {
       
   356 				var values = $.map( ids, function( id ) {
       
   357 						return self( id );
       
   358 					});
       
   359 
       
   360 				// If a value is missing, we've used at least one expired deferred.
       
   361 				// Call Values.when again to generate a new deferred.
       
   362 				if ( values.length !== ids.length ) {
       
   363 					// ids.push( callback );
       
   364 					self.when.apply( self, ids ).done( function() {
       
   365 						dfd.resolveWith( self, values );
       
   366 					});
       
   367 					return;
       
   368 				}
       
   369 
       
   370 				dfd.resolveWith( self, values );
       
   371 			});
       
   372 
       
   373 			return dfd.promise();
       
   374 		},
       
   375 
       
   376 		_change: function() {
       
   377 			this.parent.trigger( 'change', this );
       
   378 		}
       
   379 	});
       
   380 
       
   381 	$.extend( api.Values.prototype, api.Events );
       
   382 
       
   383 	/* =====================================================================
       
   384 	 * An observable value that syncs with an element.
       
   385 	 *
       
   386 	 * Handles inputs, selects, and textareas by default.
       
   387 	 * ===================================================================== */
       
   388 
       
   389 	api.ensure = function( element ) {
       
   390 		return typeof element == 'string' ? $( element ) : element;
       
   391 	};
       
   392 
       
   393 	api.Element = api.Value.extend({
       
   394 		initialize: function( element, options ) {
       
   395 			var self = this,
       
   396 				synchronizer = api.Element.synchronizer.html,
       
   397 				type, update, refresh;
       
   398 
       
   399 			this.element = api.ensure( element );
       
   400 			this.events = '';
       
   401 
       
   402 			if ( this.element.is('input, select, textarea') ) {
       
   403 				this.events += 'change';
       
   404 				synchronizer = api.Element.synchronizer.val;
       
   405 
       
   406 				if ( this.element.is('input') ) {
       
   407 					type = this.element.prop('type');
       
   408 					if ( api.Element.synchronizer[ type ] )
       
   409 						synchronizer = api.Element.synchronizer[ type ];
       
   410 					if ( 'text' === type || 'password' === type )
       
   411 						this.events += ' keyup';
       
   412 				} else if ( this.element.is('textarea') ) {
       
   413 					this.events += ' keyup';
       
   414 				}
       
   415 			}
       
   416 
       
   417 			api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
       
   418 			this._value = this.get();
       
   419 
       
   420 			update  = this.update;
       
   421 			refresh = this.refresh;
       
   422 
       
   423 			this.update = function( to ) {
       
   424 				if ( to !== refresh.call( self ) )
       
   425 					update.apply( this, arguments );
       
   426 			};
       
   427 			this.refresh = function() {
       
   428 				self.set( refresh.call( self ) );
       
   429 			};
       
   430 
       
   431 			this.bind( this.update );
       
   432 			this.element.bind( this.events, this.refresh );
       
   433 		},
       
   434 
       
   435 		find: function( selector ) {
       
   436 			return $( selector, this.element );
       
   437 		},
       
   438 
       
   439 		refresh: function() {},
       
   440 
       
   441 		update: function() {}
       
   442 	});
       
   443 
       
   444 	api.Element.synchronizer = {};
       
   445 
       
   446 	$.each( [ 'html', 'val' ], function( i, method ) {
       
   447 		api.Element.synchronizer[ method ] = {
       
   448 			update: function( to ) {
       
   449 				this.element[ method ]( to );
       
   450 			},
       
   451 			refresh: function() {
       
   452 				return this.element[ method ]();
       
   453 			}
       
   454 		};
       
   455 	});
       
   456 
       
   457 	api.Element.synchronizer.checkbox = {
       
   458 		update: function( to ) {
       
   459 			this.element.prop( 'checked', to );
       
   460 		},
       
   461 		refresh: function() {
       
   462 			return this.element.prop( 'checked' );
       
   463 		}
       
   464 	};
       
   465 
       
   466 	api.Element.synchronizer.radio = {
       
   467 		update: function( to ) {
       
   468 			this.element.filter( function() {
       
   469 				return this.value === to;
       
   470 			}).prop( 'checked', true );
       
   471 		},
       
   472 		refresh: function() {
       
   473 			return this.element.filter( ':checked' ).val();
       
   474 		}
       
   475 	};
       
   476 
       
   477 	/* =====================================================================
       
   478 	 * Messenger for postMessage.
       
   479 	 * ===================================================================== */
       
   480 
       
   481 	$.support.postMessage = !! window.postMessage;
       
   482 
       
   483 	api.Messenger = api.Class.extend({
       
   484 		add: function( key, initial, options ) {
       
   485 			return this[ key ] = new api.Value( initial, options );
       
   486 		},
       
   487 
       
   488 		/**
       
   489 		 * Initialize Messenger.
       
   490 		 *
       
   491 		 * @param  {object} params        Parameters to configure the messenger.
       
   492 		 *         {string} .url          The URL to communicate with.
       
   493 		 *         {window} .targetWindow The window instance to communicate with. Default window.parent.
       
   494 		 *         {string} .channel      If provided, will send the channel with each message and only accept messages a matching channel.
       
   495 		 * @param  {object} options       Extend any instance parameter or method with this object.
       
   496 		 */
       
   497 		initialize: function( params, options ) {
       
   498 			// Target the parent frame by default, but only if a parent frame exists.
       
   499 			var defaultTarget = window.parent == window ? null : window.parent;
       
   500 
       
   501 			$.extend( this, options || {} );
       
   502 
       
   503 			this.add( 'channel', params.channel );
       
   504 			this.add( 'url', params.url || '' );
       
   505 			this.add( 'targetWindow', params.targetWindow || defaultTarget );
       
   506 			this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
       
   507 				return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
       
   508 			});
       
   509 
       
   510 			// Since we want jQuery to treat the receive function as unique
       
   511 			// to this instance, we give the function a new guid.
       
   512 			//
       
   513 			// This will prevent every Messenger's receive function from being
       
   514 			// unbound when calling $.off( 'message', this.receive );
       
   515 			this.receive = $.proxy( this.receive, this );
       
   516 			this.receive.guid = $.guid++;
       
   517 
       
   518 			$( window ).on( 'message', this.receive );
       
   519 		},
       
   520 
       
   521 		destroy: function() {
       
   522 			$( window ).off( 'message', this.receive );
       
   523 		},
       
   524 
       
   525 		receive: function( event ) {
       
   526 			var message;
       
   527 
       
   528 			event = event.originalEvent;
       
   529 
       
   530 			if ( ! this.targetWindow() )
       
   531 				return;
       
   532 
       
   533 			// Check to make sure the origin is valid.
       
   534 			if ( this.origin() && event.origin !== this.origin() )
       
   535 				return;
       
   536 
       
   537 			message = JSON.parse( event.data );
       
   538 
       
   539 			// Check required message properties.
       
   540 			if ( ! message || ! message.id || typeof message.data === 'undefined' )
       
   541 				return;
       
   542 
       
   543 			// Check if channel names match.
       
   544 			if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
       
   545 				return;
       
   546 
       
   547 			this.trigger( message.id, message.data );
       
   548 		},
       
   549 
       
   550 		send: function( id, data ) {
       
   551 			var message;
       
   552 
       
   553 			data = typeof data === 'undefined' ? null : data;
       
   554 
       
   555 			if ( ! this.url() || ! this.targetWindow() )
       
   556 				return;
       
   557 
       
   558 			message = { id: id, data: data };
       
   559 			if ( this.channel() )
       
   560 				message.channel = this.channel();
       
   561 
       
   562 			this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
       
   563 		}
       
   564 	});
       
   565 
       
   566 	// Add the Events mixin to api.Messenger.
       
   567 	$.extend( api.Messenger.prototype, api.Events );
       
   568 
       
   569 	/* =====================================================================
       
   570 	 * Core customize object.
       
   571 	 * ===================================================================== */
       
   572 
       
   573 	api = $.extend( new api.Values(), api );
       
   574 	api.get = function() {
       
   575 		var result = {};
       
   576 
       
   577 		this.each( function( obj, key ) {
       
   578 			result[ key ] = obj.get();
       
   579 		});
       
   580 
       
   581 		return result;
       
   582 	};
       
   583 
       
   584 	// Expose the API to the world.
       
   585 	exports.customize = api;
       
   586 })( wp, jQuery );