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