]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/customize-base.js
WordPress 4.7-scripts
[autoinstalls/wordpress.git] / wp-includes / js / customize-base.js
1 window.wp = window.wp || {};
2
3 (function( exports, $ ){
4         var api = {}, ctor, inherits,
5                 slice = Array.prototype.slice;
6
7         // Shared empty constructor function to aid in prototype-chain creation.
8         ctor = function() {};
9
10         /**
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.
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          */
20         inherits = function( parent, protoProps, staticProps ) {
21                 var child;
22
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;
28                 } else {
29                         child = function() {
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 );
35                                 return result;
36                         };
37                 }
38
39                 // Inherit class (static) properties from parent.
40                 $.extend( child, parent );
41
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();
46
47                 // Add prototype properties (instance properties) to the subclass,
48                 // if supplied.
49                 if ( protoProps )
50                         $.extend( child.prototype, protoProps );
51
52                 // Add static properties to the constructor function, if supplied.
53                 if ( staticProps )
54                         $.extend( child, staticProps );
55
56                 // Correctly set child's `prototype.constructor`.
57                 child.prototype.constructor = child;
58
59                 // Set a convenience property in case the parent's prototype is needed later.
60                 child.__super__ = parent.prototype;
61
62                 return child;
63         };
64
65         /**
66          * Base class for object inheritance.
67          */
68         api.Class = function( applicator, argsArray, options ) {
69                 var magic, args = arguments;
70
71                 if ( applicator && argsArray && api.Class.applicator === applicator ) {
72                         args = argsArray;
73                         $.extend( this, options || {} );
74                 }
75
76                 magic = this;
77
78                 /*
79                  * If the class has a method called "instance",
80                  * the return value from the class' constructor will be a function that
81                  * calls the "instance" method.
82                  *
83                  * It is also an object that has properties and methods inside it.
84                  */
85                 if ( this.instance ) {
86                         magic = function() {
87                                 return magic.instance.apply( magic, arguments );
88                         };
89
90                         $.extend( magic, this );
91                 }
92
93                 magic.initialize.apply( magic, args );
94                 return magic;
95         };
96
97         /**
98          * Creates a subclass of the class.
99          *
100          * @param  object protoProps  Properties to apply to the prototype.
101          * @param  object staticProps Properties to apply directly to the class.
102          * @return child              The subclass.
103          */
104         api.Class.extend = function( protoProps, classProps ) {
105                 var child = inherits( this, protoProps, classProps );
106                 child.extend = this.extend;
107                 return child;
108         };
109
110         api.Class.applicator = {};
111
112         /**
113          * Initialize a class instance.
114          *
115          * Override this function in a subclass as needed.
116          */
117         api.Class.prototype.initialize = function() {};
118
119         /*
120          * Checks whether a given instance extended a constructor.
121          *
122          * The magic surrounding the instance parameter causes the instanceof
123          * keyword to return inaccurate results; it defaults to the function's
124          * prototype instead of the constructor chain. Hence this function.
125          */
126         api.Class.prototype.extended = function( constructor ) {
127                 var proto = this;
128
129                 while ( typeof proto.constructor !== 'undefined' ) {
130                         if ( proto.constructor === constructor )
131                                 return true;
132                         if ( typeof proto.constructor.__super__ === 'undefined' )
133                                 return false;
134                         proto = proto.constructor.__super__;
135                 }
136                 return false;
137         };
138
139         /**
140          * An events manager object, offering the ability to bind to and trigger events.
141          *
142          * Used as a mixin.
143          */
144         api.Events = {
145                 trigger: function( id ) {
146                         if ( this.topics && this.topics[ id ] )
147                                 this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
148                         return this;
149                 },
150
151                 bind: function( id ) {
152                         this.topics = this.topics || {};
153                         this.topics[ id ] = this.topics[ id ] || $.Callbacks();
154                         this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
155                         return this;
156                 },
157
158                 unbind: function( id ) {
159                         if ( this.topics && this.topics[ id ] )
160                                 this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
161                         return this;
162                 }
163         };
164
165         /**
166          * Observable values that support two-way binding.
167          *
168          * @constructor
169          */
170         api.Value = api.Class.extend({
171                 /**
172                  * @param {mixed}  initial The initial value.
173                  * @param {object} options
174                  */
175                 initialize: function( initial, options ) {
176                         this._value = initial; // @todo: potentially change this to a this.set() call.
177                         this.callbacks = $.Callbacks();
178                         this._dirty = false;
179
180                         $.extend( this, options || {} );
181
182                         this.set = $.proxy( this.set, this );
183                 },
184
185                 /*
186                  * Magic. Returns a function that will become the instance.
187                  * Set to null to prevent the instance from extending a function.
188                  */
189                 instance: function() {
190                         return arguments.length ? this.set.apply( this, arguments ) : this.get();
191                 },
192
193                 /**
194                  * Get the value.
195                  *
196                  * @return {mixed}
197                  */
198                 get: function() {
199                         return this._value;
200                 },
201
202                 /**
203                  * Set the value and trigger all bound callbacks.
204                  *
205                  * @param {object} to New value.
206                  */
207                 set: function( to ) {
208                         var from = this._value;
209
210                         to = this._setter.apply( this, arguments );
211                         to = this.validate( to );
212
213                         // Bail if the sanitized value is null or unchanged.
214                         if ( null === to || _.isEqual( from, to ) ) {
215                                 return this;
216                         }
217
218                         this._value = to;
219                         this._dirty = true;
220
221                         this.callbacks.fireWith( this, [ to, from ] );
222
223                         return this;
224                 },
225
226                 _setter: function( to ) {
227                         return to;
228                 },
229
230                 setter: function( callback ) {
231                         var from = this.get();
232                         this._setter = callback;
233                         // Temporarily clear value so setter can decide if it's valid.
234                         this._value = null;
235                         this.set( from );
236                         return this;
237                 },
238
239                 resetSetter: function() {
240                         this._setter = this.constructor.prototype._setter;
241                         this.set( this.get() );
242                         return this;
243                 },
244
245                 validate: function( value ) {
246                         return value;
247                 },
248
249                 /**
250                  * Bind a function to be invoked whenever the value changes.
251                  *
252                  * @param {...Function} A function, or multiple functions, to add to the callback stack.
253                  */
254                 bind: function() {
255                         this.callbacks.add.apply( this.callbacks, arguments );
256                         return this;
257                 },
258
259                 /**
260                  * Unbind a previously bound function.
261                  *
262                  * @param {...Function} A function, or multiple functions, to remove from the callback stack.
263                  */
264                 unbind: function() {
265                         this.callbacks.remove.apply( this.callbacks, arguments );
266                         return this;
267                 },
268
269                 link: function() { // values*
270                         var set = this.set;
271                         $.each( arguments, function() {
272                                 this.bind( set );
273                         });
274                         return this;
275                 },
276
277                 unlink: function() { // values*
278                         var set = this.set;
279                         $.each( arguments, function() {
280                                 this.unbind( set );
281                         });
282                         return this;
283                 },
284
285                 sync: function() { // values*
286                         var that = this;
287                         $.each( arguments, function() {
288                                 that.link( this );
289                                 this.link( that );
290                         });
291                         return this;
292                 },
293
294                 unsync: function() { // values*
295                         var that = this;
296                         $.each( arguments, function() {
297                                 that.unlink( this );
298                                 this.unlink( that );
299                         });
300                         return this;
301                 }
302         });
303
304         /**
305          * A collection of observable values.
306          *
307          * @constructor
308          * @augments wp.customize.Class
309          * @mixes wp.customize.Events
310          */
311         api.Values = api.Class.extend({
312
313                 /**
314                  * The default constructor for items of the collection.
315                  *
316                  * @type {object}
317                  */
318                 defaultConstructor: api.Value,
319
320                 initialize: function( options ) {
321                         $.extend( this, options || {} );
322
323                         this._value = {};
324                         this._deferreds = {};
325                 },
326
327                 /**
328                  * Get the instance of an item from the collection if only ID is specified.
329                  *
330                  * If more than one argument is supplied, all are expected to be IDs and
331                  * the last to be a function callback that will be invoked when the requested
332                  * items are available.
333                  *
334                  * @see {api.Values.when}
335                  *
336                  * @param  {string}   id ID of the item.
337                  * @param  {...}         Zero or more IDs of items to wait for and a callback
338                  *                       function to invoke when they're available. Optional.
339                  * @return {mixed}    The item instance if only one ID was supplied.
340                  *                    A Deferred Promise object if a callback function is supplied.
341                  */
342                 instance: function( id ) {
343                         if ( arguments.length === 1 )
344                                 return this.value( id );
345
346                         return this.when.apply( this, arguments );
347                 },
348
349                 /**
350                  * Get the instance of an item.
351                  *
352                  * @param  {string} id The ID of the item.
353                  * @return {[type]}    [description]
354                  */
355                 value: function( id ) {
356                         return this._value[ id ];
357                 },
358
359                 /**
360                  * Whether the collection has an item with the given ID.
361                  *
362                  * @param  {string}  id The ID of the item to look for.
363                  * @return {Boolean}
364                  */
365                 has: function( id ) {
366                         return typeof this._value[ id ] !== 'undefined';
367                 },
368
369                 /**
370                  * Add an item to the collection.
371                  *
372                  * @param {string} id    The ID of the item.
373                  * @param {mixed}  value The item instance.
374                  * @return {mixed} The new item's instance.
375                  */
376                 add: function( id, value ) {
377                         if ( this.has( id ) )
378                                 return this.value( id );
379
380                         this._value[ id ] = value;
381                         value.parent = this;
382
383                         // Propagate a 'change' event on an item up to the collection.
384                         if ( value.extended( api.Value ) )
385                                 value.bind( this._change );
386
387                         this.trigger( 'add', value );
388
389                         // If a deferred object exists for this item,
390                         // resolve it.
391                         if ( this._deferreds[ id ] )
392                                 this._deferreds[ id ].resolve();
393
394                         return this._value[ id ];
395                 },
396
397                 /**
398                  * Create a new item of the collection using the collection's default constructor
399                  * and store it in the collection.
400                  *
401                  * @param  {string} id    The ID of the item.
402                  * @param  {mixed}  value Any extra arguments are passed into the item's initialize method.
403                  * @return {mixed}  The new item's instance.
404                  */
405                 create: function( id ) {
406                         return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
407                 },
408
409                 /**
410                  * Iterate over all items in the collection invoking the provided callback.
411                  *
412                  * @param  {Function} callback Function to invoke.
413                  * @param  {object}   context  Object context to invoke the function with. Optional.
414                  */
415                 each: function( callback, context ) {
416                         context = typeof context === 'undefined' ? this : context;
417
418                         $.each( this._value, function( key, obj ) {
419                                 callback.call( context, obj, key );
420                         });
421                 },
422
423                 /**
424                  * Remove an item from the collection.
425                  *
426                  * @param  {string} id The ID of the item to remove.
427                  */
428                 remove: function( id ) {
429                         var value;
430
431                         if ( this.has( id ) ) {
432                                 value = this.value( id );
433                                 this.trigger( 'remove', value );
434                                 if ( value.extended( api.Value ) )
435                                         value.unbind( this._change );
436                                 delete value.parent;
437                         }
438
439                         delete this._value[ id ];
440                         delete this._deferreds[ id ];
441                 },
442
443                 /**
444                  * Runs a callback once all requested values exist.
445                  *
446                  * when( ids*, [callback] );
447                  *
448                  * For example:
449                  *     when( id1, id2, id3, function( value1, value2, value3 ) {} );
450                  *
451                  * @returns $.Deferred.promise();
452                  */
453                 when: function() {
454                         var self = this,
455                                 ids  = slice.call( arguments ),
456                                 dfd  = $.Deferred();
457
458                         // If the last argument is a callback, bind it to .done()
459                         if ( $.isFunction( ids[ ids.length - 1 ] ) )
460                                 dfd.done( ids.pop() );
461
462                         /*
463                          * Create a stack of deferred objects for each item that is not
464                          * yet available, and invoke the supplied callback when they are.
465                          */
466                         $.when.apply( $, $.map( ids, function( id ) {
467                                 if ( self.has( id ) )
468                                         return;
469
470                                 /*
471                                  * The requested item is not available yet, create a deferred
472                                  * object to resolve when it becomes available.
473                                  */
474                                 return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred();
475                         })).done( function() {
476                                 var values = $.map( ids, function( id ) {
477                                                 return self( id );
478                                         });
479
480                                 // If a value is missing, we've used at least one expired deferred.
481                                 // Call Values.when again to generate a new deferred.
482                                 if ( values.length !== ids.length ) {
483                                         // ids.push( callback );
484                                         self.when.apply( self, ids ).done( function() {
485                                                 dfd.resolveWith( self, values );
486                                         });
487                                         return;
488                                 }
489
490                                 dfd.resolveWith( self, values );
491                         });
492
493                         return dfd.promise();
494                 },
495
496                 /**
497                  * A helper function to propagate a 'change' event from an item
498                  * to the collection itself.
499                  */
500                 _change: function() {
501                         this.parent.trigger( 'change', this );
502                 }
503         });
504
505         // Create a global events bus on the Customizer.
506         $.extend( api.Values.prototype, api.Events );
507
508
509         /**
510          * Cast a string to a jQuery collection if it isn't already.
511          *
512          * @param {string|jQuery collection} element
513          */
514         api.ensure = function( element ) {
515                 return typeof element == 'string' ? $( element ) : element;
516         };
517
518         /**
519          * An observable value that syncs with an element.
520          *
521          * Handles inputs, selects, and textareas by default.
522          *
523          * @constructor
524          * @augments wp.customize.Value
525          * @augments wp.customize.Class
526          */
527         api.Element = api.Value.extend({
528                 initialize: function( element, options ) {
529                         var self = this,
530                                 synchronizer = api.Element.synchronizer.html,
531                                 type, update, refresh;
532
533                         this.element = api.ensure( element );
534                         this.events = '';
535
536                         if ( this.element.is('input, select, textarea') ) {
537                                 this.events += 'change';
538                                 synchronizer = api.Element.synchronizer.val;
539
540                                 if ( this.element.is('input') ) {
541                                         type = this.element.prop('type');
542                                         if ( api.Element.synchronizer[ type ] ) {
543                                                 synchronizer = api.Element.synchronizer[ type ];
544                                         }
545                                         if ( 'text' === type || 'password' === type ) {
546                                                 this.events += ' keyup';
547                                         } else if ( 'range' === type ) {
548                                                 this.events += ' input propertychange';
549                                         }
550                                 } else if ( this.element.is('textarea') ) {
551                                         this.events += ' keyup';
552                                 }
553                         }
554
555                         api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
556                         this._value = this.get();
557
558                         update  = this.update;
559                         refresh = this.refresh;
560
561                         this.update = function( to ) {
562                                 if ( to !== refresh.call( self ) )
563                                         update.apply( this, arguments );
564                         };
565                         this.refresh = function() {
566                                 self.set( refresh.call( self ) );
567                         };
568
569                         this.bind( this.update );
570                         this.element.bind( this.events, this.refresh );
571                 },
572
573                 find: function( selector ) {
574                         return $( selector, this.element );
575                 },
576
577                 refresh: function() {},
578
579                 update: function() {}
580         });
581
582         api.Element.synchronizer = {};
583
584         $.each( [ 'html', 'val' ], function( index, method ) {
585                 api.Element.synchronizer[ method ] = {
586                         update: function( to ) {
587                                 this.element[ method ]( to );
588                         },
589                         refresh: function() {
590                                 return this.element[ method ]();
591                         }
592                 };
593         });
594
595         api.Element.synchronizer.checkbox = {
596                 update: function( to ) {
597                         this.element.prop( 'checked', to );
598                 },
599                 refresh: function() {
600                         return this.element.prop( 'checked' );
601                 }
602         };
603
604         api.Element.synchronizer.radio = {
605                 update: function( to ) {
606                         this.element.filter( function() {
607                                 return this.value === to;
608                         }).prop( 'checked', true );
609                 },
610                 refresh: function() {
611                         return this.element.filter( ':checked' ).val();
612                 }
613         };
614
615         $.support.postMessage = !! window.postMessage;
616
617         /**
618          * A communicator for sending data from one window to another over postMessage.
619          *
620          * @constructor
621          * @augments wp.customize.Class
622          * @mixes wp.customize.Events
623          */
624         api.Messenger = api.Class.extend({
625                 /**
626                  * Create a new Value.
627                  *
628                  * @param  {string} key     Unique identifier.
629                  * @param  {mixed}  initial Initial value.
630                  * @param  {mixed}  options Options hash. Optional.
631                  * @return {Value}          Class instance of the Value.
632                  */
633                 add: function( key, initial, options ) {
634                         return this[ key ] = new api.Value( initial, options );
635                 },
636
637                 /**
638                  * Initialize Messenger.
639                  *
640                  * @param  {object} params - Parameters to configure the messenger.
641                  *         {string} params.url - The URL to communicate with.
642                  *         {window} params.targetWindow - The window instance to communicate with. Default window.parent.
643                  *         {string} params.channel - If provided, will send the channel with each message and only accept messages a matching channel.
644                  * @param  {object} options - Extend any instance parameter or method with this object.
645                  */
646                 initialize: function( params, options ) {
647                         // Target the parent frame by default, but only if a parent frame exists.
648                         var defaultTarget = window.parent === window ? null : window.parent;
649
650                         $.extend( this, options || {} );
651
652                         this.add( 'channel', params.channel );
653                         this.add( 'url', params.url || '' );
654                         this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
655                                 var urlParser = document.createElement( 'a' );
656                                 urlParser.href = to;
657                                 // Port stripping needed by IE since it adds to host but not to event.origin.
658                                 return urlParser.protocol + '//' + urlParser.host.replace( /:80$/, '' );
659                         });
660
661                         // first add with no value
662                         this.add( 'targetWindow', null );
663                         // This avoids SecurityErrors when setting a window object in x-origin iframe'd scenarios.
664                         this.targetWindow.set = function( to ) {
665                                 var from = this._value;
666
667                                 to = this._setter.apply( this, arguments );
668                                 to = this.validate( to );
669
670                                 if ( null === to || from === to ) {
671                                         return this;
672                                 }
673
674                                 this._value = to;
675                                 this._dirty = true;
676
677                                 this.callbacks.fireWith( this, [ to, from ] );
678
679                                 return this;
680                         };
681                         // now set it
682                         this.targetWindow( params.targetWindow || defaultTarget );
683
684
685                         // Since we want jQuery to treat the receive function as unique
686                         // to this instance, we give the function a new guid.
687                         //
688                         // This will prevent every Messenger's receive function from being
689                         // unbound when calling $.off( 'message', this.receive );
690                         this.receive = $.proxy( this.receive, this );
691                         this.receive.guid = $.guid++;
692
693                         $( window ).on( 'message', this.receive );
694                 },
695
696                 destroy: function() {
697                         $( window ).off( 'message', this.receive );
698                 },
699
700                 /**
701                  * Receive data from the other window.
702                  *
703                  * @param  {jQuery.Event} event Event with embedded data.
704                  */
705                 receive: function( event ) {
706                         var message;
707
708                         event = event.originalEvent;
709
710                         if ( ! this.targetWindow || ! this.targetWindow() ) {
711                                 return;
712                         }
713
714                         // Check to make sure the origin is valid.
715                         if ( this.origin() && event.origin !== this.origin() )
716                                 return;
717
718                         // Ensure we have a string that's JSON.parse-able
719                         if ( typeof event.data !== 'string' || event.data[0] !== '{' ) {
720                                 return;
721                         }
722
723                         message = JSON.parse( event.data );
724
725                         // Check required message properties.
726                         if ( ! message || ! message.id || typeof message.data === 'undefined' )
727                                 return;
728
729                         // Check if channel names match.
730                         if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
731                                 return;
732
733                         this.trigger( message.id, message.data );
734                 },
735
736                 /**
737                  * Send data to the other window.
738                  *
739                  * @param  {string} id   The event name.
740                  * @param  {object} data Data.
741                  */
742                 send: function( id, data ) {
743                         var message;
744
745                         data = typeof data === 'undefined' ? null : data;
746
747                         if ( ! this.url() || ! this.targetWindow() )
748                                 return;
749
750                         message = { id: id, data: data };
751                         if ( this.channel() )
752                                 message.channel = this.channel();
753
754                         this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
755                 }
756         });
757
758         // Add the Events mixin to api.Messenger.
759         $.extend( api.Messenger.prototype, api.Events );
760
761         /**
762          * Notification.
763          *
764          * @class
765          * @augments wp.customize.Class
766          * @since 4.6.0
767          *
768          * @param {string}  code - The error code.
769          * @param {object}  params - Params.
770          * @param {string}  params.message=null - The error message.
771          * @param {string}  [params.type=error] - The notification type.
772          * @param {boolean} [params.fromServer=false] - Whether the notification was server-sent.
773          * @param {string}  [params.setting=null] - The setting ID that the notification is related to.
774          * @param {*}       [params.data=null] - Any additional data.
775          */
776         api.Notification = api.Class.extend({
777                 initialize: function( code, params ) {
778                         var _params;
779                         this.code = code;
780                         _params = _.extend(
781                                 {
782                                         message: null,
783                                         type: 'error',
784                                         fromServer: false,
785                                         data: null,
786                                         setting: null
787                                 },
788                                 params
789                         );
790                         delete _params.code;
791                         _.extend( this, _params );
792                 }
793         });
794
795         // The main API object is also a collection of all customizer settings.
796         api = $.extend( new api.Values(), api );
797
798         /**
799          * Get all customize settings.
800          *
801          * @return {object}
802          */
803         api.get = function() {
804                 var result = {};
805
806                 this.each( function( obj, key ) {
807                         result[ key ] = obj.get();
808                 });
809
810                 return result;
811         };
812
813         /**
814          * Utility function namespace
815          */
816         api.utils = {};
817
818         /**
819          * Parse query string.
820          *
821          * @since 4.7.0
822          * @access public
823          *
824          * @param {string} queryString Query string.
825          * @returns {object} Parsed query string.
826          */
827         api.utils.parseQueryString = function parseQueryString( queryString ) {
828                 var queryParams = {};
829                 _.each( queryString.split( '&' ), function( pair ) {
830                         var parts, key, value;
831                         parts = pair.split( '=', 2 );
832                         if ( ! parts[0] ) {
833                                 return;
834                         }
835                         key = decodeURIComponent( parts[0].replace( /\+/g, ' ' ) );
836                         key = key.replace( / /g, '_' ); // What PHP does.
837                         if ( _.isUndefined( parts[1] ) ) {
838                                 value = null;
839                         } else {
840                                 value = decodeURIComponent( parts[1].replace( /\+/g, ' ' ) );
841                         }
842                         queryParams[ key ] = value;
843                 } );
844                 return queryParams;
845         };
846
847         // Expose the API publicly on window.wp.customize
848         exports.customize = api;
849 })( wp, jQuery );