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