}
magic = this;
+
+ /*
+ * If the class has a method called "instance",
+ * the return value from the class' constructor will be a function that
+ * calls the "instance" method.
+ *
+ * It is also an object that has properties and methods inside it.
+ */
if ( this.instance ) {
magic = function() {
return magic.instance.apply( magic, arguments );
api.Class.applicator = {};
+ /**
+ * Initialize a class instance.
+ *
+ * Override this function in a subclass as needed.
+ */
api.Class.prototype.initialize = function() {};
/*
* @constuctor
*/
api.Value = api.Class.extend({
+ /**
+ * @param {mixed} initial The initial value.
+ * @param {object} options
+ */
initialize: function( initial, options ) {
this._value = initial; // @todo: potentially change this to a this.set() call.
this.callbacks = $.Callbacks();
+ this._dirty = false;
$.extend( this, options || {} );
return arguments.length ? this.set.apply( this, arguments ) : this.get();
},
+ /**
+ * Get the value.
+ *
+ * @return {mixed}
+ */
get: function() {
return this._value;
},
+ /**
+ * Set the value and trigger all bound callbacks.
+ *
+ * @param {object} to New value.
+ */
set: function( to ) {
var from = this._value;
to = this.validate( to );
// Bail if the sanitized value is null or unchanged.
- if ( null === to || this._value === to )
+ if ( null === to || _.isEqual( from, to ) ) {
return this;
+ }
this._value = to;
+ this._dirty = true;
this.callbacks.fireWith( this, [ to, from ] );
return value;
},
+ /**
+ * Bind a function to be invoked whenever the value changes.
+ *
+ * @param {...Function} A function, or multiple functions, to add to the callback stack.
+ */
bind: function() {
this.callbacks.add.apply( this.callbacks, arguments );
return this;
},
+ /**
+ * Unbind a previously bound function.
+ *
+ * @param {...Function} A function, or multiple functions, to remove from the callback stack.
+ */
unbind: function() {
this.callbacks.remove.apply( this.callbacks, arguments );
return this;
* @mixes wp.customize.Events
*/
api.Values = api.Class.extend({
+
+ /**
+ * The default constructor for items of the collection.
+ *
+ * @type {object}
+ */
defaultConstructor: api.Value,
initialize: function( options ) {
this._deferreds = {};
},
+ /**
+ * Get the instance of an item from the collection if only ID is specified.
+ *
+ * If more than one argument is supplied, all are expected to be IDs and
+ * the last to be a function callback that will be invoked when the requested
+ * items are available.
+ *
+ * @see {api.Values.when}
+ *
+ * @param {string} id ID of the item.
+ * @param {...} Zero or more IDs of items to wait for and a callback
+ * function to invoke when they're available. Optional.
+ * @return {mixed} The item instance if only one ID was supplied.
+ * A Deferred Promise object if a callback function is supplied.
+ */
instance: function( id ) {
if ( arguments.length === 1 )
return this.value( id );
return this.when.apply( this, arguments );
},
+ /**
+ * Get the instance of an item.
+ *
+ * @param {string} id The ID of the item.
+ * @return {[type]} [description]
+ */
value: function( id ) {
return this._value[ id ];
},
+ /**
+ * Whether the collection has an item with the given ID.
+ *
+ * @param {string} id The ID of the item to look for.
+ * @return {Boolean}
+ */
has: function( id ) {
return typeof this._value[ id ] !== 'undefined';
},
+ /**
+ * Add an item to the collection.
+ *
+ * @param {string} id The ID of the item.
+ * @param {mixed} value The item instance.
+ * @return {mixed} The new item's instance.
+ */
add: function( id, value ) {
if ( this.has( id ) )
return this.value( id );
this._value[ id ] = value;
value.parent = this;
+
+ // Propagate a 'change' event on an item up to the collection.
if ( value.extended( api.Value ) )
value.bind( this._change );
this.trigger( 'add', value );
+ // If a deferred object exists for this item,
+ // resolve it.
if ( this._deferreds[ id ] )
this._deferreds[ id ].resolve();
return this._value[ id ];
},
+ /**
+ * Create a new item of the collection using the collection's default constructor
+ * and store it in the collection.
+ *
+ * @param {string} id The ID of the item.
+ * @param {mixed} value Any extra arguments are passed into the item's initialize method.
+ * @return {mixed} The new item's instance.
+ */
create: function( id ) {
return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
},
+ /**
+ * Iterate over all items in the collection invoking the provided callback.
+ *
+ * @param {Function} callback Function to invoke.
+ * @param {object} context Object context to invoke the function with. Optional.
+ */
each: function( callback, context ) {
context = typeof context === 'undefined' ? this : context;
});
},
+ /**
+ * Remove an item from the collection.
+ *
+ * @param {string} id The ID of the item to remove.
+ */
remove: function( id ) {
var value;
if ( $.isFunction( ids[ ids.length - 1 ] ) )
dfd.done( ids.pop() );
+ /*
+ * Create a stack of deferred objects for each item that is not
+ * yet available, and invoke the supplied callback when they are.
+ */
$.when.apply( $, $.map( ids, function( id ) {
if ( self.has( id ) )
return;
+ /*
+ * The requested item is not available yet, create a deferred
+ * object to resolve when it becomes available.
+ */
return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred();
})).done( function() {
var values = $.map( ids, function( id ) {
return dfd.promise();
},
+ /**
+ * A helper function to propagate a 'change' event from an item
+ * to the collection itself.
+ */
_change: function() {
this.parent.trigger( 'change', this );
}
});
+ // Create a global events bus on the Customizer.
$.extend( api.Values.prototype, api.Events );
if ( this.element.is('input') ) {
type = this.element.prop('type');
- if ( api.Element.synchronizer[ type ] )
+ if ( api.Element.synchronizer[ type ] ) {
synchronizer = api.Element.synchronizer[ type ];
- if ( 'text' === type || 'password' === type )
+ }
+ if ( 'text' === type || 'password' === type ) {
this.events += ' keyup';
+ } else if ( 'range' === type ) {
+ this.events += ' input propertychange';
+ }
} else if ( this.element.is('textarea') ) {
this.events += ' keyup';
}
$.support.postMessage = !! window.postMessage;
/**
- * Messenger for postMessage.
+ * A communicator for sending data from one window to another over postMessage.
*
* @constuctor
* @augments wp.customize.Class
this.add( 'channel', params.channel );
this.add( 'url', params.url || '' );
- this.add( 'targetWindow', params.targetWindow || defaultTarget );
this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
});
+ // first add with no value
+ this.add( 'targetWindow', null );
+ // This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios.
+ this.targetWindow.set = function( to ) {
+ var from = this._value;
+
+ to = this._setter.apply( this, arguments );
+ to = this.validate( to );
+
+ if ( null === to || from === to ) {
+ return this;
+ }
+
+ this._value = to;
+ this._dirty = true;
+
+ this.callbacks.fireWith( this, [ to, from ] );
+
+ return this;
+ };
+ // now set it
+ this.targetWindow( params.targetWindow || defaultTarget );
+
+
// Since we want jQuery to treat the receive function as unique
// to this instance, we give the function a new guid.
//
$( window ).off( 'message', this.receive );
},
+ /**
+ * Receive data from the other window.
+ *
+ * @param {jQuery.Event} event Event with embedded data.
+ */
receive: function( event ) {
var message;
event = event.originalEvent;
- if ( ! this.targetWindow() )
+ if ( ! this.targetWindow || ! this.targetWindow() ) {
return;
+ }
// Check to make sure the origin is valid.
if ( this.origin() && event.origin !== this.origin() )
this.trigger( message.id, message.data );
},
+ /**
+ * Send data to the other window.
+ *
+ * @param {string} id The event name.
+ * @param {object} data Data.
+ */
send: function( id, data ) {
var message;
// Add the Events mixin to api.Messenger.
$.extend( api.Messenger.prototype, api.Events );
- // Core customize object.
+ /**
+ * Notification.
+ *
+ * @class
+ * @augments wp.customize.Class
+ * @since 4.6.0
+ *
+ * @param {string} code The error code.
+ * @param {object} params Params.
+ * @param {string} params.message The error message.
+ * @param {string} [params.type=error] The notification type.
+ * @param {*} [params.data] Any additional data.
+ */
+ api.Notification = api.Class.extend({
+ initialize: function( code, params ) {
+ this.code = code;
+ this.message = params.message;
+ this.type = params.type || 'error';
+ this.data = params.data || null;
+ }
+ });
+
+ // The main API object is also a collection of all customizer settings.
api = $.extend( new api.Values(), api );
+
+ /**
+ * Get all customize settings.
+ *
+ * @return {object}
+ */
api.get = function() {
var result = {};