~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/js/customize-base.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
                if ( this.instance ) {
 
78
                        magic = function() {
 
79
                                return magic.instance.apply( magic, arguments );
 
80
                        };
 
81
 
 
82
                        $.extend( magic, this );
 
83
                }
 
84
 
 
85
                magic.initialize.apply( magic, args );
 
86
                return magic;
 
87
        };
 
88
 
 
89
        /**
 
90
         * Creates a subclass of the class.
 
91
         *
 
92
         * @param  object protoProps  Properties to apply to the prototype.
 
93
         * @param  object staticProps Properties to apply directly to the class.
 
94
         * @return child              The subclass.
 
95
         */
 
96
        api.Class.extend = function( protoProps, classProps ) {
 
97
                var child = inherits( this, protoProps, classProps );
 
98
                child.extend = this.extend;
 
99
                return child;
 
100
        };
 
101
 
 
102
        api.Class.applicator = {};
 
103
 
 
104
        api.Class.prototype.initialize = function() {};
 
105
 
 
106
        /*
 
107
         * Checks whether a given instance extended a constructor.
 
108
         *
 
109
         * The magic surrounding the instance parameter causes the instanceof
 
110
         * keyword to return inaccurate results; it defaults to the function's
 
111
         * prototype instead of the constructor chain. Hence this function.
 
112
         */
 
113
        api.Class.prototype.extended = function( constructor ) {
 
114
                var proto = this;
 
115
 
 
116
                while ( typeof proto.constructor !== 'undefined' ) {
 
117
                        if ( proto.constructor === constructor )
 
118
                                return true;
 
119
                        if ( typeof proto.constructor.__super__ === 'undefined' )
 
120
                                return false;
 
121
                        proto = proto.constructor.__super__;
 
122
                }
 
123
                return false;
 
124
        };
 
125
 
 
126
        /**
 
127
         * An events manager object, offering the ability to bind to and trigger events.
 
128
         *
 
129
         * Used as a mixin.
 
130
         */
 
131
        api.Events = {
 
132
                trigger: function( id ) {
 
133
                        if ( this.topics && this.topics[ id ] )
 
134
                                this.topics[ id ].fireWith( this, slice.call( arguments, 1 ) );
 
135
                        return this;
 
136
                },
 
137
 
 
138
                bind: function( id ) {
 
139
                        this.topics = this.topics || {};
 
140
                        this.topics[ id ] = this.topics[ id ] || $.Callbacks();
 
141
                        this.topics[ id ].add.apply( this.topics[ id ], slice.call( arguments, 1 ) );
 
142
                        return this;
 
143
                },
 
144
 
 
145
                unbind: function( id ) {
 
146
                        if ( this.topics && this.topics[ id ] )
 
147
                                this.topics[ id ].remove.apply( this.topics[ id ], slice.call( arguments, 1 ) );
 
148
                        return this;
 
149
                }
 
150
        };
 
151
 
 
152
        /**
 
153
         * Observable values that support two-way binding.
 
154
         *
 
155
         * @constuctor
 
156
         */
 
157
        api.Value = api.Class.extend({
 
158
                initialize: function( initial, options ) {
 
159
                        this._value = initial; // @todo: potentially change this to a this.set() call.
 
160
                        this.callbacks = $.Callbacks();
 
161
 
 
162
                        $.extend( this, options || {} );
 
163
 
 
164
                        this.set = $.proxy( this.set, this );
 
165
                },
 
166
 
 
167
                /*
 
168
                 * Magic. Returns a function that will become the instance.
 
169
                 * Set to null to prevent the instance from extending a function.
 
170
                 */
 
171
                instance: function() {
 
172
                        return arguments.length ? this.set.apply( this, arguments ) : this.get();
 
173
                },
 
174
 
 
175
                get: function() {
 
176
                        return this._value;
 
177
                },
 
178
 
 
179
                set: function( to ) {
 
180
                        var from = this._value;
 
181
 
 
182
                        to = this._setter.apply( this, arguments );
 
183
                        to = this.validate( to );
 
184
 
 
185
                        // Bail if the sanitized value is null or unchanged.
 
186
                        if ( null === to || this._value === to )
 
187
                                return this;
 
188
 
 
189
                        this._value = to;
 
190
 
 
191
                        this.callbacks.fireWith( this, [ to, from ] );
 
192
 
 
193
                        return this;
 
194
                },
 
195
 
 
196
                _setter: function( to ) {
 
197
                        return to;
 
198
                },
 
199
 
 
200
                setter: function( callback ) {
 
201
                        var from = this.get();
 
202
                        this._setter = callback;
 
203
                        // Temporarily clear value so setter can decide if it's valid.
 
204
                        this._value = null;
 
205
                        this.set( from );
 
206
                        return this;
 
207
                },
 
208
 
 
209
                resetSetter: function() {
 
210
                        this._setter = this.constructor.prototype._setter;
 
211
                        this.set( this.get() );
 
212
                        return this;
 
213
                },
 
214
 
 
215
                validate: function( value ) {
 
216
                        return value;
 
217
                },
 
218
 
 
219
                bind: function() {
 
220
                        this.callbacks.add.apply( this.callbacks, arguments );
 
221
                        return this;
 
222
                },
 
223
 
 
224
                unbind: function() {
 
225
                        this.callbacks.remove.apply( this.callbacks, arguments );
 
226
                        return this;
 
227
                },
 
228
 
 
229
                link: function() { // values*
 
230
                        var set = this.set;
 
231
                        $.each( arguments, function() {
 
232
                                this.bind( set );
 
233
                        });
 
234
                        return this;
 
235
                },
 
236
 
 
237
                unlink: function() { // values*
 
238
                        var set = this.set;
 
239
                        $.each( arguments, function() {
 
240
                                this.unbind( set );
 
241
                        });
 
242
                        return this;
 
243
                },
 
244
 
 
245
                sync: function() { // values*
 
246
                        var that = this;
 
247
                        $.each( arguments, function() {
 
248
                                that.link( this );
 
249
                                this.link( that );
 
250
                        });
 
251
                        return this;
 
252
                },
 
253
 
 
254
                unsync: function() { // values*
 
255
                        var that = this;
 
256
                        $.each( arguments, function() {
 
257
                                that.unlink( this );
 
258
                                this.unlink( that );
 
259
                        });
 
260
                        return this;
 
261
                }
 
262
        });
 
263
 
 
264
        /**
 
265
         * A collection of observable values.
 
266
         *
 
267
         * @constuctor
 
268
         * @augments wp.customize.Class
 
269
         * @mixes wp.customize.Events
 
270
         */
 
271
        api.Values = api.Class.extend({
 
272
                defaultConstructor: api.Value,
 
273
 
 
274
                initialize: function( options ) {
 
275
                        $.extend( this, options || {} );
 
276
 
 
277
                        this._value = {};
 
278
                        this._deferreds = {};
 
279
                },
 
280
 
 
281
                instance: function( id ) {
 
282
                        if ( arguments.length === 1 )
 
283
                                return this.value( id );
 
284
 
 
285
                        return this.when.apply( this, arguments );
 
286
                },
 
287
 
 
288
                value: function( id ) {
 
289
                        return this._value[ id ];
 
290
                },
 
291
 
 
292
                has: function( id ) {
 
293
                        return typeof this._value[ id ] !== 'undefined';
 
294
                },
 
295
 
 
296
                add: function( id, value ) {
 
297
                        if ( this.has( id ) )
 
298
                                return this.value( id );
 
299
 
 
300
                        this._value[ id ] = value;
 
301
                        value.parent = this;
 
302
                        if ( value.extended( api.Value ) )
 
303
                                value.bind( this._change );
 
304
 
 
305
                        this.trigger( 'add', value );
 
306
 
 
307
                        if ( this._deferreds[ id ] )
 
308
                                this._deferreds[ id ].resolve();
 
309
 
 
310
                        return this._value[ id ];
 
311
                },
 
312
 
 
313
                create: function( id ) {
 
314
                        return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
 
315
                },
 
316
 
 
317
                each: function( callback, context ) {
 
318
                        context = typeof context === 'undefined' ? this : context;
 
319
 
 
320
                        $.each( this._value, function( key, obj ) {
 
321
                                callback.call( context, obj, key );
 
322
                        });
 
323
                },
 
324
 
 
325
                remove: function( id ) {
 
326
                        var value;
 
327
 
 
328
                        if ( this.has( id ) ) {
 
329
                                value = this.value( id );
 
330
                                this.trigger( 'remove', value );
 
331
                                if ( value.extended( api.Value ) )
 
332
                                        value.unbind( this._change );
 
333
                                delete value.parent;
 
334
                        }
 
335
 
 
336
                        delete this._value[ id ];
 
337
                        delete this._deferreds[ id ];
 
338
                },
 
339
 
 
340
                /**
 
341
                 * Runs a callback once all requested values exist.
 
342
                 *
 
343
                 * when( ids*, [callback] );
 
344
                 *
 
345
                 * For example:
 
346
                 *     when( id1, id2, id3, function( value1, value2, value3 ) {} );
 
347
                 *
 
348
                 * @returns $.Deferred.promise();
 
349
                 */
 
350
                when: function() {
 
351
                        var self = this,
 
352
                                ids  = slice.call( arguments ),
 
353
                                dfd  = $.Deferred();
 
354
 
 
355
                        // If the last argument is a callback, bind it to .done()
 
356
                        if ( $.isFunction( ids[ ids.length - 1 ] ) )
 
357
                                dfd.done( ids.pop() );
 
358
 
 
359
                        $.when.apply( $, $.map( ids, function( id ) {
 
360
                                if ( self.has( id ) )
 
361
                                        return;
 
362
 
 
363
                                return self._deferreds[ id ] = self._deferreds[ id ] || $.Deferred();
 
364
                        })).done( function() {
 
365
                                var values = $.map( ids, function( id ) {
 
366
                                                return self( id );
 
367
                                        });
 
368
 
 
369
                                // If a value is missing, we've used at least one expired deferred.
 
370
                                // Call Values.when again to generate a new deferred.
 
371
                                if ( values.length !== ids.length ) {
 
372
                                        // ids.push( callback );
 
373
                                        self.when.apply( self, ids ).done( function() {
 
374
                                                dfd.resolveWith( self, values );
 
375
                                        });
 
376
                                        return;
 
377
                                }
 
378
 
 
379
                                dfd.resolveWith( self, values );
 
380
                        });
 
381
 
 
382
                        return dfd.promise();
 
383
                },
 
384
 
 
385
                _change: function() {
 
386
                        this.parent.trigger( 'change', this );
 
387
                }
 
388
        });
 
389
 
 
390
        $.extend( api.Values.prototype, api.Events );
 
391
 
 
392
 
 
393
        /**
 
394
         * Cast a string to a jQuery collection if it isn't already.
 
395
         *
 
396
         * @param {string|jQuery collection} element
 
397
         */
 
398
        api.ensure = function( element ) {
 
399
                return typeof element == 'string' ? $( element ) : element;
 
400
        };
 
401
 
 
402
        /**
 
403
         * An observable value that syncs with an element.
 
404
         *
 
405
         * Handles inputs, selects, and textareas by default.
 
406
         *
 
407
         * @constuctor
 
408
         * @augments wp.customize.Value
 
409
         * @augments wp.customize.Class
 
410
         */
 
411
        api.Element = api.Value.extend({
 
412
                initialize: function( element, options ) {
 
413
                        var self = this,
 
414
                                synchronizer = api.Element.synchronizer.html,
 
415
                                type, update, refresh;
 
416
 
 
417
                        this.element = api.ensure( element );
 
418
                        this.events = '';
 
419
 
 
420
                        if ( this.element.is('input, select, textarea') ) {
 
421
                                this.events += 'change';
 
422
                                synchronizer = api.Element.synchronizer.val;
 
423
 
 
424
                                if ( this.element.is('input') ) {
 
425
                                        type = this.element.prop('type');
 
426
                                        if ( api.Element.synchronizer[ type ] )
 
427
                                                synchronizer = api.Element.synchronizer[ type ];
 
428
                                        if ( 'text' === type || 'password' === type )
 
429
                                                this.events += ' keyup';
 
430
                                } else if ( this.element.is('textarea') ) {
 
431
                                        this.events += ' keyup';
 
432
                                }
 
433
                        }
 
434
 
 
435
                        api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
 
436
                        this._value = this.get();
 
437
 
 
438
                        update  = this.update;
 
439
                        refresh = this.refresh;
 
440
 
 
441
                        this.update = function( to ) {
 
442
                                if ( to !== refresh.call( self ) )
 
443
                                        update.apply( this, arguments );
 
444
                        };
 
445
                        this.refresh = function() {
 
446
                                self.set( refresh.call( self ) );
 
447
                        };
 
448
 
 
449
                        this.bind( this.update );
 
450
                        this.element.bind( this.events, this.refresh );
 
451
                },
 
452
 
 
453
                find: function( selector ) {
 
454
                        return $( selector, this.element );
 
455
                },
 
456
 
 
457
                refresh: function() {},
 
458
 
 
459
                update: function() {}
 
460
        });
 
461
 
 
462
        api.Element.synchronizer = {};
 
463
 
 
464
        $.each( [ 'html', 'val' ], function( index, method ) {
 
465
                api.Element.synchronizer[ method ] = {
 
466
                        update: function( to ) {
 
467
                                this.element[ method ]( to );
 
468
                        },
 
469
                        refresh: function() {
 
470
                                return this.element[ method ]();
 
471
                        }
 
472
                };
 
473
        });
 
474
 
 
475
        api.Element.synchronizer.checkbox = {
 
476
                update: function( to ) {
 
477
                        this.element.prop( 'checked', to );
 
478
                },
 
479
                refresh: function() {
 
480
                        return this.element.prop( 'checked' );
 
481
                }
 
482
        };
 
483
 
 
484
        api.Element.synchronizer.radio = {
 
485
                update: function( to ) {
 
486
                        this.element.filter( function() {
 
487
                                return this.value === to;
 
488
                        }).prop( 'checked', true );
 
489
                },
 
490
                refresh: function() {
 
491
                        return this.element.filter( ':checked' ).val();
 
492
                }
 
493
        };
 
494
 
 
495
        $.support.postMessage = !! window.postMessage;
 
496
 
 
497
        /**
 
498
         * Messenger for postMessage.
 
499
         *
 
500
         * @constuctor
 
501
         * @augments wp.customize.Class
 
502
         * @mixes wp.customize.Events
 
503
         */
 
504
        api.Messenger = api.Class.extend({
 
505
                /**
 
506
                 * Create a new Value.
 
507
                 *
 
508
                 * @param  {string} key     Unique identifier.
 
509
                 * @param  {mixed}  initial Initial value.
 
510
                 * @param  {mixed}  options Options hash. Optional.
 
511
                 * @return {Value}          Class instance of the Value.
 
512
                 */
 
513
                add: function( key, initial, options ) {
 
514
                        return this[ key ] = new api.Value( initial, options );
 
515
                },
 
516
 
 
517
                /**
 
518
                 * Initialize Messenger.
 
519
                 *
 
520
                 * @param  {object} params        Parameters to configure the messenger.
 
521
                 *         {string} .url          The URL to communicate with.
 
522
                 *         {window} .targetWindow The window instance to communicate with. Default window.parent.
 
523
                 *         {string} .channel      If provided, will send the channel with each message and only accept messages a matching channel.
 
524
                 * @param  {object} options       Extend any instance parameter or method with this object.
 
525
                 */
 
526
                initialize: function( params, options ) {
 
527
                        // Target the parent frame by default, but only if a parent frame exists.
 
528
                        var defaultTarget = window.parent == window ? null : window.parent;
 
529
 
 
530
                        $.extend( this, options || {} );
 
531
 
 
532
                        this.add( 'channel', params.channel );
 
533
                        this.add( 'url', params.url || '' );
 
534
                        this.add( 'targetWindow', params.targetWindow || defaultTarget );
 
535
                        this.add( 'origin', this.url() ).link( this.url ).setter( function( to ) {
 
536
                                return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
 
537
                        });
 
538
 
 
539
                        // Since we want jQuery to treat the receive function as unique
 
540
                        // to this instance, we give the function a new guid.
 
541
                        //
 
542
                        // This will prevent every Messenger's receive function from being
 
543
                        // unbound when calling $.off( 'message', this.receive );
 
544
                        this.receive = $.proxy( this.receive, this );
 
545
                        this.receive.guid = $.guid++;
 
546
 
 
547
                        $( window ).on( 'message', this.receive );
 
548
                },
 
549
 
 
550
                destroy: function() {
 
551
                        $( window ).off( 'message', this.receive );
 
552
                },
 
553
 
 
554
                receive: function( event ) {
 
555
                        var message;
 
556
 
 
557
                        event = event.originalEvent;
 
558
 
 
559
                        if ( ! this.targetWindow() )
 
560
                                return;
 
561
 
 
562
                        // Check to make sure the origin is valid.
 
563
                        if ( this.origin() && event.origin !== this.origin() )
 
564
                                return;
 
565
 
 
566
                        // Ensure we have a string that's JSON.parse-able
 
567
                        if ( typeof event.data !== 'string' || event.data[0] !== '{' ) {
 
568
                                return;
 
569
                        }
 
570
 
 
571
                        message = JSON.parse( event.data );
 
572
 
 
573
                        // Check required message properties.
 
574
                        if ( ! message || ! message.id || typeof message.data === 'undefined' )
 
575
                                return;
 
576
 
 
577
                        // Check if channel names match.
 
578
                        if ( ( message.channel || this.channel() ) && this.channel() !== message.channel )
 
579
                                return;
 
580
 
 
581
                        this.trigger( message.id, message.data );
 
582
                },
 
583
 
 
584
                send: function( id, data ) {
 
585
                        var message;
 
586
 
 
587
                        data = typeof data === 'undefined' ? null : data;
 
588
 
 
589
                        if ( ! this.url() || ! this.targetWindow() )
 
590
                                return;
 
591
 
 
592
                        message = { id: id, data: data };
 
593
                        if ( this.channel() )
 
594
                                message.channel = this.channel();
 
595
 
 
596
                        this.targetWindow().postMessage( JSON.stringify( message ), this.origin() );
 
597
                }
 
598
        });
 
599
 
 
600
        // Add the Events mixin to api.Messenger.
 
601
        $.extend( api.Messenger.prototype, api.Events );
 
602
 
 
603
        // Core customize object.
 
604
        api = $.extend( new api.Values(), api );
 
605
        api.get = function() {
 
606
                var result = {};
 
607
 
 
608
                this.each( function( obj, key ) {
 
609
                        result[ key ] = obj.get();
 
610
                });
 
611
 
 
612
                return result;
 
613
        };
 
614
 
 
615
        // Expose the API publicly on window.wp.customize
 
616
        exports.customize = api;
 
617
})( wp, jQuery );