|
- window.wp = window.wp || {};
-
- (function( exports, $ ){
- var api = {}, ctor, inherits,
- slice = Array.prototype.slice;
-
- // Shared empty constructor function to aid in prototype-chain creation.
- ctor = function() {};
-
- /**
- * Helper function to correctly set up the prototype chain, for subclasses.
- * Similar to `goog.inherits`, but uses a hash of prototype properties and
- * class properties to be extended.
- *
- * @param object parent Parent class constructor to inherit from.
- * @param object protoProps Properties to apply to the prototype for use as class instance properties.
- * @param object staticProps Properties to apply directly to the class constructor.
- * @return child The subclassed constructor.
- */
- inherits = function( parent, protoProps, staticProps ) {
- var child;
-
- // The constructor function for the new subclass is either defined by you
- // (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call `super()`.
- if ( protoProps && protoProps.hasOwnProperty( 'constructor' ) ) {
- child = protoProps.constructor;
- } else {
- child = function() {
- // Storing the result `super()` before returning the value
- // prevents a bug in Opera where, if the constructor returns
- // a function, Opera will reject the return value in favor of
- // the original object. This causes all sorts of trouble.
- var result = parent.apply( this, arguments );
- return result;
- };
- }
-
- // Inherit class (static) properties from parent.
- $.extend( child, parent );
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if ( protoProps )
- $.extend( child.prototype, protoProps );
-
- // Add static properties to the constructor function, if supplied.
- if ( staticProps )
- $.extend( child, staticProps );
-
- // Correctly set child's `prototype.constructor`.
- child.prototype.constructor = child;
-
- // Set a convenience property in case the parent's prototype is needed later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- /**
- * Base class for object inheritance.
- */
- api.Class = function( applicator, argsArray, options ) {
- var magic, args = arguments;
-
- if ( applicator && argsArray && api.Class.applicator === applicator ) {
- args = argsArray;
- $.extend( this, options || {} );
- }
-
- 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 );
- };
-
- $.extend( magic, this );
- }
-
- magic.initialize.apply( magic, args );
- return magic;
- };
-
- /**
- * Creates a subclass of the class.
- *
- * @param object protoProps Properties to apply to the prototype.
- * @param object staticProps Properties to apply directly to the class.
- * @return child The subclass.
- */
- api.Class.extend = function( protoProps, classProps ) {
- var child = inherits( this, protoProps, classProps );
- child.extend = this.extend;
- return child;
- };
-
- api.Class.applicator = {};
-
- /**
- * Initialize a class instance.
- *
- * Override this function in a subclass as needed.
- */
- api.Class.prototype.initialize = function() {};
-
- /*
- * Checks whether a given instance extended a constructor.
- *
- * The magic surrounding the instance parameter causes the instanceof
- * keyword to return inaccurate results; it defaults to the function's
- * prototype instead of the constructor chain. Hence this function.
- */
- api.Class.prototype.extended = function( constructor ) {
- var proto = this;
-
- while ( typeof proto.constructor !== 'undefined' ) {
- if ( proto.constructor === constructor )
- return true;
- if ( typeof proto.constructor.__super__ === 'undefined' )
- return false;
- proto = proto.constructor.__super__;
- }
- return false;
- };
-
- /**
- * An events manager object, offering the ability to bind to and trigger events.
- *
- * Used as a mixin.
- */
- api.Events = {
- trigger: function( id ) {
- if ( this.topics && this.topics[ id ] )
- this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
- return this;
- },
-
- bind: function( id ) {
- this.topics = this.topics || {};
- this.topics[ id ] = this.topics[ id ] || $.Callbacks();
- this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
- return this;
- },
-
- unbind: function( id ) {
- if ( this.topics && this.topics[ id ] )
- this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
- return this;
- }
- };
-
- /**
- * Observable values that support two-way binding.
- *
- * @constructor
- */
- 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 || {} );
-
- this.set = $.proxy( this.set, this );
- },
-
- /*
- * Magic. Returns a function that will become the instance.
- * Set to null to prevent the instance from extending a function.
- */
- instance: function() {
- 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._setter.apply( this, arguments );
- to = this.validate( to );
-
- // Bail if the sanitized value is null or unchanged.
- if ( null === to || _.isEqual( from, to ) ) {
- return this;
- }
-
- this._value = to;
- this._dirty = true;
-
- this.callbacks.fireWith( this, [ to, from ] );
-
- return this;
- },
-
- _setter: function( to ) {
- return to;
- },
-
- setter: function( callback ) {
- var from = this.get();
- this._setter = callback;
- // Temporarily clear value so setter can decide if it's valid.
- this._value = null;
- this.set( from );
- return this;
- },
-
- resetSetter: function() {
- this._setter = this.constructor.prototype._setter;
- this.set( this.get() );
- return this;
- },
-
- validate: function( value ) {
- 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;
- },
-
- link: function() { // values*
- var set = this.set;
- $.each( arguments, function() {
- this.bind( set );
- });
- return this;
- },
-
- unlink: function() { // values*
- var set = this.set;
- $.each( arguments, function() {
- this.unbind( set );
- });
- return this;
- },
-
- sync: function() { // values*
- var that = this;
- $.each( arguments, function() {
- that.link( this );
- this.link( that );
- });
- return this;
- },
-
- unsync: function() { // values*
- var that = this;
- $.each( arguments, function() {
- that.unlink( this );
- this.unlink( that );
- });
- return this;
- }
- });
-
- /**
- * A collection of observable values.
- *
- * @constructor
- * @augments wp.customize.Class
- * @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 ) {
- $.extend( this, options || {} );
-
- this._value = {};
- 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;
-
- $.each( this._value, function( key, obj ) {
- callback.call( context, obj, key );
- });
- },
-
- /**
- * Remove an item from the collection.
- *
- * @param {string} id The ID of the item to remove.
- */
- remove: function( id ) {
- var value;
-
- if ( this.has( id ) ) {
- value = this.value( id );
- this.trigger( 'remove', value );
- if ( value.extended( api.Value ) )
- value.unbind( this._change );
- delete value.parent;
- }
-
- delete this._value[ id ];
- delete this._deferreds[ id ];
- },
-
- /**
- * Runs a callback once all requested values exist.
- *
- * when( ids*, [callback] );
- *
- * For example:
- * when( id1, id2, id3, function( value1, value2, value3 ) {} );
- *
- * @returns $.Deferred.promise();
- */
- when: function() {
- var self = this,
- ids = slice.call( arguments ),
- dfd = $.Deferred();
-
- // If the last argument is a callback, bind it to .done()
- 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 self( id );
- });
-
- // If a value is missing, we've used at least one expired deferred.
- // Call Values.when again to generate a new deferred.
- if ( values.length !== ids.length ) {
- // ids.push( callback );
- self.when.apply( self, ids ).done( function() {
- dfd.resolveWith( self, values );
- });
- return;
- }
-
- dfd.resolveWith( self, values );
- });
-
- 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 );
-
-
- /**
- * Cast a string to a jQuery collection if it isn't already.
- *
- * @param {string|jQuery collection} element
- */
- api.ensure = function( element ) {
- return typeof element == 'string' ? $( element ) : element;
- };
-
- /**
- * An observable value that syncs with an element.
- *
- * Handles inputs, selects, and textareas by default.
- *
- * @constructor
- * @augments wp.customize.Value
- * @augments wp.customize.Class
- */
- api.Element = api.Value.extend({
- initialize: function( element, options ) {
- var self = this,
- synchronizer = api.Element.synchronizer.html,
- type, update, refresh;
-
- this.element = api.ensure( element );
- this.events = '';
-
- if ( this.element.is('input, select, textarea') ) {
- this.events += 'change';
- synchronizer = api.Element.synchronizer.val;
-
- if ( this.element.is('input') ) {
- type = this.element.prop('type');
- if ( api.Element.synchronizer[ type ] ) {
- synchronizer = api.Element.synchronizer[ 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';
- }
- }
-
- api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
- this._value = this.get();
-
- update = this.update;
- refresh = this.refresh;
-
- this.update = function( to ) {
- if ( to !== refresh.call( self ) )
- update.apply( this, arguments );
- };
- this.refresh = function() {
- self.set( refresh.call( self ) );
- };
-
- this.bind( this.update );
- this.element.bind( this.events, this.refresh );
- },
-
- find: function( selector ) {
- return $( selector, this.element );
- },
-
- refresh: function() {},
-
- update: function() {}
- });
-
- api.Element.synchronizer = {};
-
- $.each( [ 'html', 'val' ], function( index, method ) {
- api.Element.synchronizer[ method ] = {
- update: function( to ) {
- this.element[ method ]( to );
- },
- refresh: function() {
- return this.element[ method ]();
- }
- };
- });
-
- api.Element.synchronizer.checkbox = {
- update: function( to ) {
- this.element.prop( 'checked', to );
- },
- refresh: function() {
- return this.element.prop( 'checked' );
- }
- };
-
- api.Element.synchronizer.radio = {
- update: function( to ) {
- this.element.filter( function() {
- return this.value === to;
- }).prop( 'checked', true );
- },
- refresh: function() {
- return this.element.filter( ':checked' ).val();
- }
- };
-
- $.support.postMessage = !! window.postMessage;
-
- /**
- * A communicator for sending data from one window to another over postMessage.
- *
- * @constructor
- * @augments wp.customize.Class
- * @mixes wp.customize.Events
- */
- api.Messenger = api.Class.extend({
- /**
- * Create a new Value.
- *
- * @param {string} key Unique identifier.
- * @param {mixed} initial Initial value.
- * @param {mixed} options Options hash. Optional.
- * @return {Value} Class instance of the Value.
- */
- add: function( key, initial, options ) {
- return this[ key ] = new api.Value( initial, options );
- },
-
- /**
- * Initialize Messenger.
- *
- * @param {object} params - Parameters to configure the messenger.
- * {string} params.url - The URL to communicate with.
- * {window} params.targetWindow - The window instance to communicate with. Default window.parent.
- * {string} params.channel - If provided, will send the channel with each message and only accept messages a matching channel.
- * @param {object} options - Extend any instance parameter or method with this object.
- */
- initialize: function( params, options ) {
- // Target the parent frame by default, but only if a parent frame exists.
- var defaultTarget = window.parent === window ? null : window.parent;
-
- $.extend( this, options || {} );
-
- this.add( 'channel', params.channel );
- this.add( 'url', params.url || '' );
- this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
- var urlParser = document.createElement( 'a' );
- urlParser.href = to;
- // Port stripping needed by IE since it adds to host but not to event.origin.
- return urlParser.protocol + '//' + urlParser.host.replace( /:80$/, '' );
- });
-
- // 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.
- //
- // This will prevent every Messenger's receive function from being
- // unbound when calling $.off( 'message', this.receive );
- this.receive = $.proxy( this.receive, this );
- this.receive.guid = $.guid++;
-
- $( window ).on( 'message', this.receive );
- },
-
- destroy: function() {
- $( 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 || ! this.targetWindow() ) {
- return;
- }
-
- // Check to make sure the origin is valid.
- if ( this.origin() && event.origin !== this.origin() )
- return;
-
- // Ensure we have a string that's JSON.parse-able
- if ( typeof event.data !== 'string' || event.data[0] !== '{' ) {
- return;
- }
-
- message = JSON.parse( event.data );
-
- // Check required message properties.
- if ( ! message || ! message.id || typeof message.data === 'undefined' )
- return;
-
- // Check if channel names match.
- if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
- return;
-
- 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;
-
- data = typeof data === 'undefined' ? null : data;
-
- if ( ! this.url() || ! this.targetWindow() )
- return;
-
- message = { id: id, data: data };
- if ( this.channel() )
- message.channel = this.channel();
-
- this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
- }
- });
-
- // Add the Events mixin to api.Messenger.
- $.extend( api.Messenger.prototype, api.Events );
-
- /**
- * Notification.
- *
- * @class
- * @augments wp.customize.Class
- * @since 4.6.0
- *
- * @param {string} code - The error code.
- * @param {object} params - Params.
- * @param {string} params.message=null - The error message.
- * @param {string} [params.type=error] - The notification type.
- * @param {boolean} [params.fromServer=false] - Whether the notification was server-sent.
- * @param {string} [params.setting=null] - The setting ID that the notification is related to.
- * @param {*} [params.data=null] - Any additional data.
- */
- api.Notification = api.Class.extend({
- initialize: function( code, params ) {
- var _params;
- this.code = code;
- _params = _.extend(
- {
- message: null,
- type: 'error',
- fromServer: false,
- data: null,
- setting: null
- },
- params
- );
- delete _params.code;
- _.extend( this, _params );
- }
- });
-
- // 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 = {};
-
- this.each( function( obj, key ) {
- result[ key ] = obj.get();
- });
-
- return result;
- };
-
- /**
- * Utility function namespace
- */
- api.utils = {};
-
- /**
- * Parse query string.
- *
- * @since 4.7.0
- * @access public
- *
- * @param {string} queryString Query string.
- * @returns {object} Parsed query string.
- */
- api.utils.parseQueryString = function parseQueryString( queryString ) {
- var queryParams = {};
- _.each( queryString.split( '&' ), function( pair ) {
- var parts, key, value;
- parts = pair.split( '=', 2 );
- if ( ! parts[0] ) {
- return;
- }
- key = decodeURIComponent( parts[0].replace( /\+/g, ' ' ) );
- key = key.replace( / /g, '_' ); // What PHP does.
- if ( _.isUndefined( parts[1] ) ) {
- value = null;
- } else {
- value = decodeURIComponent( parts[1].replace( /\+/g, ' ' ) );
- }
- queryParams[ key ] = value;
- } );
- return queryParams;
- };
-
- // Expose the API publicly on window.wp.customize
- exports.customize = api;
- })( wp, jQuery );
|