1
window.wp = window.wp || {};
3
(function( exports, $ ){
4
var api = {}, ctor, inherits,
5
slice = Array.prototype.slice;
7
// Shared empty constructor function to aid in prototype-chain creation.
11
* Helper function to correctly set up the prototype chain, for subclasses.
12
* Similar to `goog.inherits`, but uses a hash of prototype properties and
13
* class properties to be extended.
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.
20
inherits = function( parent, protoProps, staticProps ) {
23
// The constructor function for the new subclass is either defined by you
24
// (the "constructor" property in your `extend` definition), or defaulted
25
// by us to simply call `super()`.
26
if ( protoProps && protoProps.hasOwnProperty( 'constructor' ) ) {
27
child = protoProps.constructor;
30
// Storing the result `super()` before returning the value
31
// prevents a bug in Opera where, if the constructor returns
32
// a function, Opera will reject the return value in favor of
33
// the original object. This causes all sorts of trouble.
34
var result = parent.apply( this, arguments );
39
// Inherit class (static) properties from parent.
40
$.extend( child, parent );
42
// Set the prototype chain to inherit from `parent`, without calling
43
// `parent`'s constructor function.
44
ctor.prototype = parent.prototype;
45
child.prototype = new ctor();
47
// Add prototype properties (instance properties) to the subclass,
50
$.extend( child.prototype, protoProps );
52
// Add static properties to the constructor function, if supplied.
54
$.extend( child, staticProps );
56
// Correctly set child's `prototype.constructor`.
57
child.prototype.constructor = child;
59
// Set a convenience property in case the parent's prototype is needed later.
60
child.__super__ = parent.prototype;
66
* Base class for object inheritance.
68
api.Class = function( applicator, argsArray, options ) {
69
var magic, args = arguments;
71
if ( applicator && argsArray && api.Class.applicator === applicator ) {
73
$.extend( this, options || {} );
77
if ( this.instance ) {
79
return magic.instance.apply( magic, arguments );
82
$.extend( magic, this );
85
magic.initialize.apply( magic, args );
90
* Creates a subclass of the class.
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.
96
api.Class.extend = function( protoProps, classProps ) {
97
var child = inherits( this, protoProps, classProps );
98
child.extend = this.extend;
102
api.Class.applicator = {};
104
api.Class.prototype.initialize = function() {};
107
* Checks whether a given instance extended a constructor.
109
* The magic surrounding the instance parameter causes the instanceof
110
* keyword to return inaccurate results; it defaults to the function's
111
* prototype instead of the constructor chain. Hence this function.
113
api.Class.prototype.extended = function( constructor ) {
116
while ( typeof proto.constructor !== 'undefined' ) {
117
if ( proto.constructor === constructor )
119
if ( typeof proto.constructor.__super__ === 'undefined' )
121
proto = proto.constructor.__super__;
127
* An events manager object, offering the ability to bind to and trigger events.
132
trigger: function( id ) {
133
if ( this.topics && this.topics[ id ] )
134
this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
138
bind: function( id ) {
139
this.topics = this.topics || {};
140
this.topics[ id ] = this.topics[ id ] || $.Callbacks();
141
this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
145
unbind: function( id ) {
146
if ( this.topics && this.topics[ id ] )
147
this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
153
* Observable values that support two-way binding.
157
api.Value = api.Class.extend({
158
initialize: function( initial, options ) {
159
this._value = initial; // @todo: potentially change this to a this.set() call.
160
this.callbacks = $.Callbacks();
162
$.extend( this, options || {} );
164
this.set = $.proxy( this.set, this );
168
* Magic. Returns a function that will become the instance.
169
* Set to null to prevent the instance from extending a function.
171
instance: function() {
172
return arguments.length ? this.set.apply( this, arguments ) : this.get();
179
set: function( to ) {
180
var from = this._value;
182
to = this._setter.apply( this, arguments );
183
to = this.validate( to );
185
// Bail if the sanitized value is null or unchanged.
186
if ( null === to || this._value === to )
191
this.callbacks.fireWith( this, [ to, from ] );
196
_setter: function( to ) {
200
setter: function( callback ) {
201
var from = this.get();
202
this._setter = callback;
203
// Temporarily clear value so setter can decide if it's valid.
209
resetSetter: function() {
210
this._setter = this.constructor.prototype._setter;
211
this.set( this.get() );
215
validate: function( value ) {
220
this.callbacks.add.apply( this.callbacks, arguments );
225
this.callbacks.remove.apply( this.callbacks, arguments );
229
link: function() { // values*
231
$.each( arguments, function() {
237
unlink: function() { // values*
239
$.each( arguments, function() {
245
sync: function() { // values*
247
$.each( arguments, function() {
254
unsync: function() { // values*
256
$.each( arguments, function() {
265
* A collection of observable values.
268
* @augments wp.customize.Class
269
* @mixes wp.customize.Events
271
api.Values = api.Class.extend({
272
defaultConstructor: api.Value,
274
initialize: function( options ) {
275
$.extend( this, options || {} );
278
this._deferreds = {};
281
instance: function( id ) {
282
if ( arguments.length === 1 )
283
return this.value( id );
285
return this.when.apply( this, arguments );
288
value: function( id ) {
289
return this._value[ id ];
292
has: function( id ) {
293
return typeof this._value[ id ] !== 'undefined';
296
add: function( id, value ) {
297
if ( this.has( id ) )
298
return this.value( id );
300
this._value[ id ] = value;
302
if ( value.extended( api.Value ) )
303
value.bind( this._change );
305
this.trigger( 'add', value );
307
if ( this._deferreds[ id ] )
308
this._deferreds[ id ].resolve();
310
return this._value[ id ];
313
create: function( id ) {
314
return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
317
each: function( callback, context ) {
318
context = typeof context === 'undefined' ? this : context;
320
$.each( this._value, function( key, obj ) {
321
callback.call( context, obj, key );
325
remove: function( id ) {
328
if ( this.has( id ) ) {
329
value = this.value( id );
330
this.trigger( 'remove', value );
331
if ( value.extended( api.Value ) )
332
value.unbind( this._change );
336
delete this._value[ id ];
337
delete this._deferreds[ id ];
341
* Runs a callback once all requested values exist.
343
* when( ids*, [callback] );
346
* when( id1, id2, id3, function( value1, value2, value3 ) {} );
348
* @returns $.Deferred.promise();
352
ids = slice.call( arguments ),
355
// If the last argument is a callback, bind it to .done()
356
if ( $.isFunction( ids[ ids.length - 1 ] ) )
357
dfd.done( ids.pop() );
359
$.when.apply( $, $.map( ids, function( id ) {
360
if ( self.has( id ) )
363
return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred();
364
})).done( function() {
365
var values = $.map( ids, function( id ) {
369
// If a value is missing, we've used at least one expired deferred.
370
// Call Values.when again to generate a new deferred.
371
if ( values.length !== ids.length ) {
372
// ids.push( callback );
373
self.when.apply( self, ids ).done( function() {
374
dfd.resolveWith( self, values );
379
dfd.resolveWith( self, values );
382
return dfd.promise();
385
_change: function() {
386
this.parent.trigger( 'change', this );
390
$.extend( api.Values.prototype, api.Events );
394
* Cast a string to a jQuery collection if it isn't already.
396
* @param {string|jQuery collection} element
398
api.ensure = function( element ) {
399
return typeof element == 'string' ? $( element ) : element;
403
* An observable value that syncs with an element.
405
* Handles inputs, selects, and textareas by default.
408
* @augments wp.customize.Value
409
* @augments wp.customize.Class
411
api.Element = api.Value.extend({
412
initialize: function( element, options ) {
414
synchronizer = api.Element.synchronizer.html,
415
type, update, refresh;
417
this.element = api.ensure( element );
420
if ( this.element.is('input, select, textarea') ) {
421
this.events += 'change';
422
synchronizer = api.Element.synchronizer.val;
424
if ( this.element.is('input') ) {
425
type = this.element.prop('type');
426
if ( api.Element.synchronizer[ type ] )
427
synchronizer = api.Element.synchronizer[ type ];
428
if ( 'text' === type || 'password' === type )
429
this.events += ' keyup';
430
} else if ( this.element.is('textarea') ) {
431
this.events += ' keyup';
435
api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
436
this._value = this.get();
438
update = this.update;
439
refresh = this.refresh;
441
this.update = function( to ) {
442
if ( to !== refresh.call( self ) )
443
update.apply( this, arguments );
445
this.refresh = function() {
446
self.set( refresh.call( self ) );
449
this.bind( this.update );
450
this.element.bind( this.events, this.refresh );
453
find: function( selector ) {
454
return $( selector, this.element );
457
refresh: function() {},
459
update: function() {}
462
api.Element.synchronizer = {};
464
$.each( [ 'html', 'val' ], function( index, method ) {
465
api.Element.synchronizer[ method ] = {
466
update: function( to ) {
467
this.element[ method ]( to );
469
refresh: function() {
470
return this.element[ method ]();
475
api.Element.synchronizer.checkbox = {
476
update: function( to ) {
477
this.element.prop( 'checked', to );
479
refresh: function() {
480
return this.element.prop( 'checked' );
484
api.Element.synchronizer.radio = {
485
update: function( to ) {
486
this.element.filter( function() {
487
return this.value === to;
488
}).prop( 'checked', true );
490
refresh: function() {
491
return this.element.filter( ':checked' ).val();
495
$.support.postMessage = !! window.postMessage;
498
* Messenger for postMessage.
501
* @augments wp.customize.Class
502
* @mixes wp.customize.Events
504
api.Messenger = api.Class.extend({
506
* Create a new Value.
508
* @param {string} key Unique identifier.
509
* @param {mixed} initial Initial value.
510
* @param {mixed} options Options hash. Optional.
511
* @return {Value} Class instance of the Value.
513
add: function( key, initial, options ) {
514
return this[ key ] = new api.Value( initial, options );
518
* Initialize Messenger.
520
* @param {object} params Parameters to configure the messenger.
521
* {string} .url The URL to communicate with.
522
* {window} .targetWindow The window instance to communicate with. Default window.parent.
523
* {string} .channel If provided, will send the channel with each message and only accept messages a matching channel.
524
* @param {object} options Extend any instance parameter or method with this object.
526
initialize: function( params, options ) {
527
// Target the parent frame by default, but only if a parent frame exists.
528
var defaultTarget = window.parent == window ? null : window.parent;
530
$.extend( this, options || {} );
532
this.add( 'channel', params.channel );
533
this.add( 'url', params.url || '' );
534
this.add( 'targetWindow', params.targetWindow || defaultTarget );
535
this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
536
return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
539
// Since we want jQuery to treat the receive function as unique
540
// to this instance, we give the function a new guid.
542
// This will prevent every Messenger's receive function from being
543
// unbound when calling $.off( 'message', this.receive );
544
this.receive = $.proxy( this.receive, this );
545
this.receive.guid = $.guid++;
547
$( window ).on( 'message', this.receive );
550
destroy: function() {
551
$( window ).off( 'message', this.receive );
554
receive: function( event ) {
557
event = event.originalEvent;
559
if ( ! this.targetWindow() )
562
// Check to make sure the origin is valid.
563
if ( this.origin() && event.origin !== this.origin() )
566
// Ensure we have a string that's JSON.parse-able
567
if ( typeof event.data !== 'string' || event.data[0] !== '{' ) {
571
message = JSON.parse( event.data );
573
// Check required message properties.
574
if ( ! message || ! message.id || typeof message.data === 'undefined' )
577
// Check if channel names match.
578
if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
581
this.trigger( message.id, message.data );
584
send: function( id, data ) {
587
data = typeof data === 'undefined' ? null : data;
589
if ( ! this.url() || ! this.targetWindow() )
592
message = { id: id, data: data };
593
if ( this.channel() )
594
message.channel = this.channel();
596
this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
600
// Add the Events mixin to api.Messenger.
601
$.extend( api.Messenger.prototype, api.Events );
603
// Core customize object.
604
api = $.extend( new api.Values(), api );
605
api.get = function() {
608
this.each( function( obj, key ) {
609
result[ key ] = obj.get();
615
// Expose the API publicly on window.wp.customize
616
exports.customize = api;