]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/js/customize-controls.dev.js
WordPress 3.4.2
[autoinstalls/wordpress.git] / wp-admin / js / customize-controls.dev.js
1 (function( exports, $ ){
2         var api = wp.customize;
3
4         /*
5          * @param options
6          * - previewer - The Previewer instance to sync with.
7          * - transport - The transport to use for previewing. Supports 'refresh' and 'postMessage'.
8          */
9         api.Setting = api.Value.extend({
10                 initialize: function( id, value, options ) {
11                         var element;
12
13                         api.Value.prototype.initialize.call( this, value, options );
14
15                         this.id = id;
16                         this.transport = this.transport || 'refresh';
17
18                         this.bind( this.preview );
19                 },
20                 preview: function() {
21                         switch ( this.transport ) {
22                                 case 'refresh':
23                                         return this.previewer.refresh();
24                                 case 'postMessage':
25                                         return this.previewer.send( 'setting', [ this.id, this() ] );
26                         }
27                 }
28         });
29
30         api.Control = api.Class.extend({
31                 initialize: function( id, options ) {
32                         var control = this,
33                                 nodes, radios, settings;
34
35                         this.params = {};
36                         $.extend( this, options || {} );
37
38                         this.id = id;
39                         this.selector = '#customize-control-' + id.replace( ']', '' ).replace( '[', '-' );
40                         this.container = $( this.selector );
41
42                         settings = $.map( this.params.settings, function( value ) {
43                                 return value;
44                         });
45
46                         api.apply( api, settings.concat( function() {
47                                 var key;
48
49                                 control.settings = {};
50                                 for ( key in control.params.settings ) {
51                                         control.settings[ key ] = api( control.params.settings[ key ] );
52                                 }
53
54                                 control.setting = control.settings['default'] || null;
55                                 control.ready();
56                         }) );
57
58                         control.elements = [];
59
60                         nodes  = this.container.find('[data-customize-setting-link]');
61                         radios = {};
62
63                         nodes.each( function() {
64                                 var node = $(this),
65                                         name;
66
67                                 if ( node.is(':radio') ) {
68                                         name = node.prop('name');
69                                         if ( radios[ name ] )
70                                                 return;
71
72                                         radios[ name ] = true;
73                                         node = nodes.filter( '[name="' + name + '"]' );
74                                 }
75
76                                 api( node.data('customizeSettingLink'), function( setting ) {
77                                         var element = new api.Element( node );
78                                         control.elements.push( element );
79                                         element.sync( setting );
80                                         element.set( setting() );
81                                 });
82                         });
83                 },
84
85                 ready: function() {},
86
87                 dropdownInit: function() {
88                         var control  = this,
89                                 statuses = this.container.find('.dropdown-status'),
90                                 params   = this.params,
91                                 update   = function( to ) {
92                                         if ( typeof     to === 'string' && params.statuses && params.statuses[ to ] )
93                                                 statuses.html( params.statuses[ to ] ).show();
94                                         else
95                                                 statuses.hide();
96                                 };
97
98                         // Support the .dropdown class to open/close complex elements
99                         this.container.on( 'click', '.dropdown', function( event ) {
100                                 event.preventDefault();
101                                 control.container.toggleClass('open');
102                         });
103
104                         this.setting.bind( update );
105                         update( this.setting() );
106                 }
107         });
108
109         api.ColorControl = api.Control.extend({
110                 ready: function() {
111                         var control = this,
112                                 rhex, spot, input, text, update;
113
114                         rhex   = /^#([A-Fa-f0-9]{3}){0,2}$/;
115                         spot   = this.container.find('.dropdown-content');
116                         input  = new api.Element( this.container.find('.color-picker-hex') );
117                         update = function( color ) {
118                                 spot.css( 'background', color );
119                                 control.farbtastic.setColor( color );
120                         };
121
122                         this.farbtastic = $.farbtastic( this.container.find('.farbtastic-placeholder'), control.setting.set );
123
124                         // Only pass through values that are valid hexes/empty.
125                         input.sync( this.setting ).validate = function( to ) {
126                                 return rhex.test( to ) ? to : null;
127                         };
128
129                         this.setting.bind( update );
130                         update( this.setting() );
131
132                         this.dropdownInit();
133                 }
134         });
135
136         api.UploadControl = api.Control.extend({
137                 ready: function() {
138                         var control = this;
139
140                         this.params.removed = this.params.removed || '';
141
142                         this.success = $.proxy( this.success, this );
143
144                         this.uploader = $.extend({
145                                 container: this.container,
146                                 browser:   this.container.find('.upload'),
147                                 dropzone:  this.container.find('.upload-dropzone'),
148                                 success:   this.success
149                         }, this.uploader || {} );
150
151                         if ( this.uploader.supported ) {
152                                 if ( control.params.context )
153                                         control.uploader.param( 'post_data[context]', this.params.context );
154
155                                 control.uploader.param( 'post_data[theme]', api.settings.theme.stylesheet );
156                         }
157
158                         this.uploader = new wp.Uploader( this.uploader );
159
160                         this.remover = this.container.find('.remove');
161                         this.remover.click( function( event ) {
162                                 control.setting.set( control.params.removed );
163                                 event.preventDefault();
164                         });
165
166                         this.removerVisibility = $.proxy( this.removerVisibility, this );
167                         this.setting.bind( this.removerVisibility );
168                         this.removerVisibility( this.setting.get() );
169                 },
170                 success: function( attachment ) {
171                         this.setting.set( attachment.url );
172                 },
173                 removerVisibility: function( to ) {
174                         this.remover.toggle( to != this.params.removed );
175                 }
176         });
177
178         api.ImageControl = api.UploadControl.extend({
179                 ready: function() {
180                         var control = this,
181                                 panels;
182
183                         this.uploader = {
184                                 init: function( up ) {
185                                         var fallback, button;
186
187                                         if ( this.supports.dragdrop )
188                                                 return;
189
190                                         // Maintain references while wrapping the fallback button.
191                                         fallback = control.container.find( '.upload-fallback' );
192                                         button   = fallback.children().detach();
193
194                                         this.browser.detach().empty().append( button );
195                                         fallback.append( this.browser ).show();
196                                 }
197                         };
198
199                         api.UploadControl.prototype.ready.call( this );
200
201                         this.thumbnail    = this.container.find('.preview-thumbnail img');
202                         this.thumbnailSrc = $.proxy( this.thumbnailSrc, this );
203                         this.setting.bind( this.thumbnailSrc );
204
205                         this.library = this.container.find('.library');
206
207                         // Generate tab objects
208                         this.tabs = {};
209                         panels    = this.library.find('.library-content');
210
211                         this.library.children('ul').children('li').each( function() {
212                                 var link  = $(this),
213                                         id    = link.data('customizeTab'),
214                                         panel = panels.filter('[data-customize-tab="' + id + '"]');
215
216                                 control.tabs[ id ] = {
217                                         both:  link.add( panel ),
218                                         link:  link,
219                                         panel: panel
220                                 };
221                         });
222
223                         // Bind tab switch events
224                         this.library.children('ul').on( 'click', 'li', function( event ) {
225                                 var id  = $(this).data('customizeTab'),
226                                         tab = control.tabs[ id ];
227
228                                 event.preventDefault();
229
230                                 if ( tab.link.hasClass('library-selected') )
231                                         return;
232
233                                 control.selected.both.removeClass('library-selected');
234                                 control.selected = tab;
235                                 control.selected.both.addClass('library-selected');
236                         });
237
238                         // Bind events to switch image urls.
239                         this.library.on( 'click', 'a', function( event ) {
240                                 var value = $(this).data('customizeImageValue');
241
242                                 if ( value ) {
243                                         control.setting.set( value );
244                                         event.preventDefault();
245                                 }
246                         });
247
248                         if ( this.tabs.uploaded ) {
249                                 this.tabs.uploaded.target = this.library.find('.uploaded-target');
250                                 if ( ! this.tabs.uploaded.panel.find('.thumbnail').length )
251                                         this.tabs.uploaded.both.addClass('hidden');
252                         }
253
254                         // Select a tab
255                         panels.each( function() {
256                                 var tab = control.tabs[ $(this).data('customizeTab') ];
257
258                                 // Select the first visible tab.
259                                 if ( ! tab.link.hasClass('hidden') ) {
260                                         control.selected = tab;
261                                         tab.both.addClass('library-selected');
262                                         return false;
263                                 }
264                         });
265
266                         this.dropdownInit();
267                 },
268                 success: function( attachment ) {
269                         api.UploadControl.prototype.success.call( this, attachment );
270
271                         // Add the uploaded image to the uploaded tab.
272                         if ( this.tabs.uploaded && this.tabs.uploaded.target.length ) {
273                                 this.tabs.uploaded.both.removeClass('hidden');
274
275                                 attachment.element = $( '<a href="#" class="thumbnail"></a>' )
276                                         .data( 'customizeImageValue', attachment.url )
277                                         .append( '<img src="' +  attachment.url+ '" />' )
278                                         .appendTo( this.tabs.uploaded.target );
279                         }
280                 },
281                 thumbnailSrc: function( to ) {
282                         if ( /^(https?:)?\/\//.test( to ) )
283                                 this.thumbnail.prop( 'src', to ).show();
284                         else
285                                 this.thumbnail.hide();
286                 }
287         });
288
289         // Change objects contained within the main customize object to Settings.
290         api.defaultConstructor = api.Setting;
291
292         // Create the collection of Control objects.
293         api.control = new api.Values({ defaultConstructor: api.Control });
294
295         api.PreviewFrame = api.Messenger.extend({
296                 sensitivity: 2000,
297
298                 initialize: function( params, options ) {
299                         var deferred = $.Deferred(),
300                                 self     = this;
301
302                         // This is the promise object.
303                         deferred.promise( this );
304
305                         this.container = params.container;
306                         this.signature = params.signature;
307
308                         $.extend( params, { channel: api.PreviewFrame.uuid() });
309
310                         api.Messenger.prototype.initialize.call( this, params, options );
311
312                         this.add( 'previewUrl', params.previewUrl );
313
314                         this.query = $.extend( params.query || {}, { customize_messenger_channel: this.channel() });
315
316                         this.run( deferred );
317                 },
318
319                 run: function( deferred ) {
320                         var self   = this,
321                                 loaded = false,
322                                 ready  = false;
323
324                         if ( this._ready )
325                                 this.unbind( 'ready', this._ready );
326
327                         this._ready = function() {
328                                 ready = true;
329
330                                 if ( loaded )
331                                         deferred.resolveWith( self );
332                         };
333
334                         this.bind( 'ready', this._ready );
335
336                         this.request = $.ajax( this.previewUrl(), {
337                                 type: 'POST',
338                                 data: this.query,
339                                 xhrFields: {
340                                         withCredentials: true
341                                 }
342                         } );
343
344                         this.request.fail( function() {
345                                 deferred.rejectWith( self, [ 'request failure' ] );
346                         });
347
348                         this.request.done( function( response ) {
349                                 var location = self.request.getResponseHeader('Location'),
350                                         signature = self.signature,
351                                         index;
352
353                                 // Check if the location response header differs from the current URL.
354                                 // If so, the request was redirected; try loading the requested page.
355                                 if ( location && location != self.previewUrl() ) {
356                                         deferred.rejectWith( self, [ 'redirect', location ] );
357                                         return;
358                                 }
359
360                                 // Check if the user is not logged in.
361                                 if ( '0' === response ) {
362                                         self.login( deferred );
363                                         return;
364                                 }
365
366                                 // Check for cheaters.
367                                 if ( '-1' === response ) {
368                                         deferred.rejectWith( self, [ 'cheatin' ] );
369                                         return;
370                                 }
371
372                                 // Check for a signature in the request.
373                                 index = response.lastIndexOf( signature );
374                                 if ( -1 === index || index < response.lastIndexOf('</html>') ) {
375                                         deferred.rejectWith( self, [ 'unsigned' ] );
376                                         return;
377                                 }
378
379                                 // Strip the signature from the request.
380                                 response = response.slice( 0, index ) + response.slice( index + signature.length );
381
382                                 // Create the iframe and inject the html content.
383                                 self.iframe = $('<iframe />').appendTo( self.container );
384
385                                 // Bind load event after the iframe has been added to the page;
386                                 // otherwise it will fire when injected into the DOM.
387                                 self.iframe.one( 'load', function() {
388                                         loaded = true;
389
390                                         if ( ready ) {
391                                                 deferred.resolveWith( self );
392                                         } else {
393                                                 setTimeout( function() {
394                                                         deferred.rejectWith( self, [ 'ready timeout' ] );
395                                                 }, self.sensitivity );
396                                         }
397                                 });
398
399                                 self.targetWindow( self.iframe[0].contentWindow );
400
401                                 self.targetWindow().document.open();
402                                 self.targetWindow().document.write( response );
403                                 self.targetWindow().document.close();
404                         });
405                 },
406
407                 login: function( deferred ) {
408                         var self = this,
409                                 reject;
410
411                         reject = function() {
412                                 deferred.rejectWith( self, [ 'logged out' ] );
413                         };
414
415                         if ( this.triedLogin )
416                                 return reject();
417
418                         // Check if we have an admin cookie.
419                         $.get( api.settings.url.ajax, {
420                                 action: 'logged-in'
421                         }).fail( reject ).done( function( response ) {
422                                 var iframe;
423
424                                 if ( '1' !== response )
425                                         reject();
426
427                                 iframe = $('<iframe src="' + self.previewUrl() + '" />').hide();
428                                 iframe.appendTo( self.container );
429                                 iframe.load( function() {
430                                         self.triedLogin = true;
431
432                                         iframe.remove();
433                                         self.run( deferred );
434                                 });
435                         });
436                 },
437
438                 destroy: function() {
439                         api.Messenger.prototype.destroy.call( this );
440                         this.request.abort();
441
442                         if ( this.iframe )
443                                 this.iframe.remove();
444
445                         delete this.request;
446                         delete this.iframe;
447                         delete this.targetWindow;
448                 }
449         });
450
451         (function(){
452                 var uuid = 0;
453                 api.PreviewFrame.uuid = function() {
454                         return 'preview-' + uuid++;
455                 };
456         }());
457
458         api.Previewer = api.Messenger.extend({
459                 refreshBuffer: 250,
460
461                 /**
462                  * Requires params:
463                  *  - container  - a selector or jQuery element
464                  *  - previewUrl - the URL of preview frame
465                  */
466                 initialize: function( params, options ) {
467                         var self = this,
468                                 rscheme = /^https?/,
469                                 url;
470
471                         $.extend( this, options || {} );
472
473                         /*
474                          * Wrap this.refresh to prevent it from hammering the servers:
475                          *
476                          * If refresh is called once and no other refresh requests are
477                          * loading, trigger the request immediately.
478                          *
479                          * If refresh is called while another refresh request is loading,
480                          * debounce the refresh requests:
481                          * 1. Stop the loading request (as it is instantly outdated).
482                          * 2. Trigger the new request once refresh hasn't been called for
483                          *    self.refreshBuffer milliseconds.
484                          */
485                         this.refresh = (function( self ) {
486                                 var refresh  = self.refresh,
487                                         callback = function() {
488                                                 timeout = null;
489                                                 refresh.call( self );
490                                         },
491                                         timeout;
492
493                                 return function() {
494                                         if ( typeof timeout !== 'number' ) {
495                                                 if ( self.loading ) {
496                                                         self.abort();
497                                                 } else {
498                                                         return callback();
499                                                 }
500                                         }
501
502                                         clearTimeout( timeout );
503                                         timeout = setTimeout( callback, self.refreshBuffer );
504                                 };
505                         })( this );
506
507                         this.container   = api.ensure( params.container );
508                         this.allowedUrls = params.allowedUrls;
509                         this.signature   = params.signature;
510
511                         params.url = window.location.href;
512
513                         api.Messenger.prototype.initialize.call( this, params );
514
515                         this.add( 'scheme', this.origin() ).link( this.origin ).setter( function( to ) {
516                                 var match = to.match( rscheme );
517                                 return match ? match[0] : '';
518                         });
519
520                         // Limit the URL to internal, front-end links.
521                         //
522                         // If the frontend and the admin are served from the same domain, load the
523                         // preview over ssl if the customizer is being loaded over ssl. This avoids
524                         // insecure content warnings. This is not attempted if the admin and frontend
525                         // are on different domains to avoid the case where the frontend doesn't have
526                         // ssl certs.
527
528                         this.add( 'previewUrl', params.previewUrl ).setter( function( to ) {
529                                 var result;
530
531                                 // Check for URLs that include "/wp-admin/" or end in "/wp-admin".
532                                 // Strip hashes and query strings before testing.
533                                 if ( /\/wp-admin(\/|$)/.test( to.replace(/[#?].*$/, '') ) )
534                                         return null;
535
536                                 // Attempt to match the URL to the control frame's scheme
537                                 // and check if it's allowed. If not, try the original URL.
538                                 $.each([ to.replace( rscheme, self.scheme() ), to ], function( i, url ) {
539                                         $.each( self.allowedUrls, function( i, allowed ) {
540                                                 if ( 0 === url.indexOf( allowed ) ) {
541                                                         result = url;
542                                                         return false;
543                                                 }
544                                         });
545                                         if ( result )
546                                                 return false;
547                                 });
548
549                                 // If we found a matching result, return it. If not, bail.
550                                 return result ? result : null;
551                         });
552
553                         // Refresh the preview when the URL is changed (but not yet).
554                         this.previewUrl.bind( this.refresh );
555
556                         this.scroll = 0;
557                         this.bind( 'scroll', function( distance ) {
558                                 this.scroll = distance;
559                         });
560
561                         // Update the URL when the iframe sends a URL message.
562                         this.bind( 'url', this.previewUrl );
563                 },
564
565                 query: function() {},
566
567                 abort: function() {
568                         if ( this.loading ) {
569                                 this.loading.destroy();
570                                 delete this.loading;
571                         }
572                 },
573
574                 refresh: function() {
575                         var self = this;
576
577                         this.abort();
578
579                         this.loading = new api.PreviewFrame({
580                                 url:        this.url(),
581                                 previewUrl: this.previewUrl(),
582                                 query:      this.query() || {},
583                                 container:  this.container,
584                                 signature:  this.signature
585                         });
586
587                         this.loading.done( function() {
588                                 // 'this' is the loading frame
589                                 this.bind( 'synced', function() {
590                                         if ( self.preview )
591                                                 self.preview.destroy();
592                                         self.preview = this;
593                                         delete self.loading;
594
595                                         self.targetWindow( this.targetWindow() );
596                                         self.channel( this.channel() );
597
598                                         self.send( 'active' );
599                                 });
600
601                                 this.send( 'sync', {
602                                         scroll:   self.scroll,
603                                         settings: api.get()
604                                 });
605                         });
606
607                         this.loading.fail( function( reason, location ) {
608                                 if ( 'redirect' === reason && location )
609                                         self.previewUrl( location );
610
611                                 if ( 'logged out' === reason ) {
612                                         if ( self.preview ) {
613                                                 self.preview.destroy();
614                                                 delete self.preview;
615                                         }
616
617                                         self.login().done( self.refresh );
618                                 }
619
620                                 if ( 'cheatin' === reason )
621                                         self.cheatin();
622                         });
623                 },
624
625                 login: function() {
626                         var previewer = this,
627                                 deferred, messenger, iframe;
628
629                         if ( this._login )
630                                 return this._login;
631
632                         deferred = $.Deferred();
633                         this._login = deferred.promise();
634
635                         messenger = new api.Messenger({
636                                 channel: 'login',
637                                 url:     api.settings.url.login
638                         });
639
640                         iframe = $('<iframe src="' + api.settings.url.login + '" />').appendTo( this.container );
641
642                         messenger.targetWindow( iframe[0].contentWindow );
643
644                         messenger.bind( 'login', function() {
645                                 iframe.remove();
646                                 messenger.destroy();
647                                 delete previewer._login;
648                                 deferred.resolve();
649                         });
650
651                         return this._login;
652                 },
653
654                 cheatin: function() {
655                         $( document.body ).empty().addClass('cheatin').append( '<p>' + api.l10n.cheatin + '</p>' );
656                 }
657         });
658
659         /* =====================================================================
660          * Ready.
661          * ===================================================================== */
662
663         api.controlConstructor = {
664                 color:  api.ColorControl,
665                 upload: api.UploadControl,
666                 image:  api.ImageControl
667         };
668
669         $( function() {
670                 api.settings = window._wpCustomizeSettings;
671                 api.l10n = window._wpCustomizeControlsL10n;
672
673                 // Check if we can run the customizer.
674                 if ( ! api.settings )
675                         return;
676
677                 // Redirect to the fallback preview if any incompatibilities are found.
678                 if ( ! $.support.postMessage || ( ! $.support.cors && api.settings.isCrossDomain ) )
679                         return window.location = api.settings.url.fallback;
680
681                 var body = $( document.body ),
682                         overlay = body.children('.wp-full-overlay'),
683                         query, previewer, parent;
684
685                 // Prevent the form from saving when enter is pressed.
686                 $('#customize-controls').on( 'keydown', function( e ) {
687                         if ( $( e.target ).is('textarea') )
688                                 return;
689
690                         if ( 13 === e.which ) // Enter
691                                 e.preventDefault();
692                 });
693
694                 // Initialize Previewer
695                 previewer = new api.Previewer({
696                         container:   '#customize-preview',
697                         form:        '#customize-controls',
698                         previewUrl:  api.settings.url.preview,
699                         allowedUrls: api.settings.url.allowed,
700                         signature:   'WP_CUSTOMIZER_SIGNATURE'
701                 }, {
702
703                         nonce: api.settings.nonce,
704
705                         query: function() {
706                                 return {
707                                         wp_customize: 'on',
708                                         theme:        api.settings.theme.stylesheet,
709                                         customized:   JSON.stringify( api.get() ),
710                                         nonce:        this.nonce.preview
711                                 };
712                         },
713
714                         save: function() {
715                                 var self  = this,
716                                         query = $.extend( this.query(), {
717                                                 action: 'customize_save',
718                                                 nonce:  this.nonce.save
719                                         }),
720                                         request = $.post( api.settings.url.ajax, query );
721
722                                 api.trigger( 'save', request );
723
724                                 body.addClass('saving');
725
726                                 request.always( function() {
727                                         body.removeClass('saving');
728                                 });
729
730                                 request.done( function( response ) {
731                                         // Check if the user is logged out.
732                                         if ( '0' === response ) {
733                                                 self.preview.iframe.hide();
734                                                 self.login().done( function() {
735                                                         self.save();
736                                                         self.preview.iframe.show();
737                                                 });
738                                                 return;
739                                         }
740
741                                         // Check for cheaters.
742                                         if ( '-1' === response ) {
743                                                 self.cheatin();
744                                                 return;
745                                         }
746
747                                         api.trigger( 'saved' );
748                                 });
749                         }
750                 });
751
752                 // Refresh the nonces if the preview sends updated nonces over.
753                 previewer.bind( 'nonce', function( nonce ) {
754                         $.extend( this.nonce, nonce );
755                 });
756
757                 $.each( api.settings.settings, function( id, data ) {
758                         api.create( id, id, data.value, {
759                                 transport: data.transport,
760                                 previewer: previewer
761                         } );
762                 });
763
764                 $.each( api.settings.controls, function( id, data ) {
765                         var constructor = api.controlConstructor[ data.type ] || api.Control,
766                                 control;
767
768                         control = api.control.add( id, new constructor( id, {
769                                 params: data,
770                                 previewer: previewer
771                         } ) );
772                 });
773
774                 // Check if preview url is valid and load the preview frame.
775                 if ( previewer.previewUrl() )
776                         previewer.refresh();
777                 else
778                         previewer.previewUrl( api.settings.url.home );
779
780                 // Save and activated states
781                 (function() {
782                         var state = new api.Values(),
783                                 saved = state.create('saved'),
784                                 activated = state.create('activated');
785
786                         state.bind( 'change', function() {
787                                 var save = $('#save'),
788                                         back = $('.back');
789
790                                 if ( ! activated() ) {
791                                         save.val( api.l10n.activate ).prop( 'disabled', false );
792                                         back.text( api.l10n.cancel );
793
794                                 } else if ( saved() ) {
795                                         save.val( api.l10n.saved ).prop( 'disabled', true );
796                                         back.text( api.l10n.close );
797
798                                 } else {
799                                         save.val( api.l10n.save ).prop( 'disabled', false );
800                                         back.text( api.l10n.cancel );
801                                 }
802                         });
803
804                         // Set default states.
805                         saved( true );
806                         activated( api.settings.theme.active );
807
808                         api.bind( 'change', function() {
809                                 state('saved').set( false );
810                         });
811
812                         api.bind( 'saved', function() {
813                                 state('saved').set( true );
814                                 state('activated').set( true );
815                         });
816
817                         activated.bind( function( to ) {
818                                 if ( to )
819                                         api.trigger( 'activated' );
820                         });
821
822                         // Expose states to the API.
823                         api.state = state;
824                 }());
825
826                 // Temporary accordion code.
827                 $('.customize-section-title').click( function( event ) {
828                         var clicked = $( this ).parents( '.customize-section' );
829
830                         if ( clicked.hasClass('cannot-expand') )
831                                 return;
832
833                         $( '.customize-section' ).not( clicked ).removeClass( 'open' );
834                         clicked.toggleClass( 'open' );
835                         event.preventDefault();
836                 });
837
838                 // Button bindings.
839                 $('#save').click( function( event ) {
840                         previewer.save();
841                         event.preventDefault();
842                 });
843
844                 $('.collapse-sidebar').click( function( event ) {
845                         overlay.toggleClass( 'collapsed' ).toggleClass( 'expanded' );
846                         event.preventDefault();
847                 });
848
849                 // Create a potential postMessage connection with the parent frame.
850                 parent = new api.Messenger({
851                         url: api.settings.url.parent,
852                         channel: 'loader'
853                 });
854
855                 // If we receive a 'back' event, we're inside an iframe.
856                 // Send any clicks to the 'Return' link to the parent page.
857                 parent.bind( 'back', function() {
858                         $('.back').on( 'click.back', function( event ) {
859                                 event.preventDefault();
860                                 parent.send( 'close' );
861                         });
862                 });
863
864                 // Pass events through to the parent.
865                 api.bind( 'saved', function() {
866                         parent.send( 'saved' );
867                 });
868
869                 // When activated, let the loader handle redirecting the page.
870                 // If no loader exists, redirect the page ourselves (if a url exists).
871                 api.bind( 'activated', function() {
872                         if ( parent.targetWindow() )
873                                 parent.send( 'activated', api.settings.url.activated );
874                         else if ( api.settings.url.activated )
875                                 window.location = api.settings.url.activated;
876                 });
877
878                 // Initialize the connection with the parent frame.
879                 parent.send( 'ready' );
880
881                 // Control visibility for default controls
882                 $.each({
883                         'background_image': {
884                                 controls: [ 'background_repeat', 'background_position_x', 'background_attachment' ],
885                                 callback: function( to ) { return !! to }
886                         },
887                         'show_on_front': {
888                                 controls: [ 'page_on_front', 'page_for_posts' ],
889                                 callback: function( to ) { return 'page' === to }
890                         },
891                         'header_textcolor': {
892                                 controls: [ 'header_textcolor' ],
893                                 callback: function( to ) { return 'blank' !== to }
894                         }
895                 }, function( settingId, o ) {
896                         api( settingId, function( setting ) {
897                                 $.each( o.controls, function( i, controlId ) {
898                                         api.control( controlId, function( control ) {
899                                                 var visibility = function( to ) {
900                                                         control.container.toggle( o.callback( to ) );
901                                                 };
902
903                                                 visibility( setting.get() );
904                                                 setting.bind( visibility );
905                                         });
906                                 });
907                         });
908                 });
909
910                 // Juggle the two controls that use header_textcolor
911                 api.control( 'display_header_text', function( control ) {
912                         var last = '';
913
914                         control.elements[0].unsync( api( 'header_textcolor' ) );
915
916                         control.element = new api.Element( control.container.find('input') );
917                         control.element.set( 'blank' !== control.setting() );
918
919                         control.element.bind( function( to ) {
920                                 if ( ! to )
921                                         last = api( 'header_textcolor' ).get();
922
923                                 control.setting.set( to ? last : 'blank' );
924                         });
925
926                         control.setting.bind( function( to ) {
927                                 control.element.set( 'blank' !== to );
928                         });
929                 });
930
931                 // Handle header image data
932                 api.control( 'header_image', function( control ) {
933                         control.setting.bind( function( to ) {
934                                 if ( to === control.params.removed )
935                                         control.settings.data.set( false );
936                         });
937
938                         control.library.on( 'click', 'a', function( event ) {
939                                 control.settings.data.set( $(this).data('customizeHeaderImageData') );
940                         });
941
942                         control.uploader.success = function( attachment ) {
943                                 var data;
944
945                                 api.ImageControl.prototype.success.call( control, attachment );
946
947                                 data = {
948                                         attachment_id: attachment.id,
949                                         url:           attachment.url,
950                                         thumbnail_url: attachment.url,
951                                         height:        attachment.meta.height,
952                                         width:         attachment.meta.width
953                                 };
954
955                                 attachment.element.data( 'customizeHeaderImageData', data );
956                                 control.settings.data.set( data );
957                         }
958                 });
959
960                 api.trigger( 'ready' );
961         });
962
963 })( wp, jQuery );