(function( exports, $ ){
var api = wp.customize;
/*
* @param options
* - previewer - The Previewer instance to sync with.
* - transport - The transport to use for previewing. Supports 'refresh' and 'postMessage'.
*/
api.Setting = api.Value.extend({
initialize: function( id, value, options ) {
var element;
api.Value.prototype.initialize.call( this, value, options );
this.id = id;
this.transport = this.transport || 'refresh';
this.bind( this.preview );
},
preview: function() {
switch ( this.transport ) {
case 'refresh':
return this.previewer.refresh();
case 'postMessage':
return this.previewer.send( 'setting', [ this.id, this() ] );
}
}
});
api.Control = api.Class.extend({
initialize: function( id, options ) {
var control = this,
nodes, radios, settings;
this.params = {};
$.extend( this, options || {} );
this.id = id;
this.selector = '#customize-control-' + id.replace( ']', '' ).replace( '[', '-' );
this.container = $( this.selector );
settings = $.map( this.params.settings, function( value ) {
return value;
});
api.apply( api, settings.concat( function() {
var key;
control.settings = {};
for ( key in control.params.settings ) {
control.settings[ key ] = api( control.params.settings[ key ] );
}
control.setting = control.settings['default'] || null;
control.ready();
}) );
control.elements = [];
nodes = this.container.find('[data-customize-setting-link]');
radios = {};
nodes.each( function() {
var node = $(this),
name;
if ( node.is(':radio') ) {
name = node.prop('name');
if ( radios[ name ] )
return;
radios[ name ] = true;
node = nodes.filter( '[name="' + name + '"]' );
}
api( node.data('customizeSettingLink'), function( setting ) {
var element = new api.Element( node );
control.elements.push( element );
element.sync( setting );
element.set( setting() );
});
});
},
ready: function() {},
dropdownInit: function() {
var control = this,
statuses = this.container.find('.dropdown-status'),
params = this.params,
update = function( to ) {
if ( typeof to === 'string' && params.statuses && params.statuses[ to ] )
statuses.html( params.statuses[ to ] ).show();
else
statuses.hide();
};
// Support the .dropdown class to open/close complex elements
this.container.on( 'click', '.dropdown', function( event ) {
event.preventDefault();
control.container.toggleClass('open');
});
this.setting.bind( update );
update( this.setting() );
}
});
api.ColorControl = api.Control.extend({
ready: function() {
var control = this,
rhex, spot, input, text, update;
rhex = /^#([A-Fa-f0-9]{3}){0,2}$/;
spot = this.container.find('.dropdown-content');
input = new api.Element( this.container.find('.color-picker-hex') );
update = function( color ) {
spot.css( 'background', color );
control.farbtastic.setColor( color );
};
this.farbtastic = $.farbtastic( this.container.find('.farbtastic-placeholder'), control.setting.set );
// Only pass through values that are valid hexes/empty.
input.sync( this.setting ).validate = function( to ) {
return rhex.test( to ) ? to : null;
};
this.setting.bind( update );
update( this.setting() );
this.dropdownInit();
}
});
api.UploadControl = api.Control.extend({
ready: function() {
var control = this;
this.params.removed = this.params.removed || '';
this.success = $.proxy( this.success, this );
this.uploader = $.extend({
container: this.container,
browser: this.container.find('.upload'),
dropzone: this.container.find('.upload-dropzone'),
success: this.success
}, this.uploader || {} );
if ( this.uploader.supported ) {
if ( control.params.context )
control.uploader.param( 'post_data[context]', this.params.context );
control.uploader.param( 'post_data[theme]', api.settings.theme.stylesheet );
}
this.uploader = new wp.Uploader( this.uploader );
this.remover = this.container.find('.remove');
this.remover.click( function( event ) {
control.setting.set( control.params.removed );
event.preventDefault();
});
this.removerVisibility = $.proxy( this.removerVisibility, this );
this.setting.bind( this.removerVisibility );
this.removerVisibility( this.setting.get() );
},
success: function( attachment ) {
this.setting.set( attachment.get('url') );
},
removerVisibility: function( to ) {
this.remover.toggle( to != this.params.removed );
}
});
api.ImageControl = api.UploadControl.extend({
ready: function() {
var control = this,
panels;
this.uploader = {
init: function( up ) {
var fallback, button;
if ( this.supports.dragdrop )
return;
// Maintain references while wrapping the fallback button.
fallback = control.container.find( '.upload-fallback' );
button = fallback.children().detach();
this.browser.detach().empty().append( button );
fallback.append( this.browser ).show();
}
};
api.UploadControl.prototype.ready.call( this );
this.thumbnail = this.container.find('.preview-thumbnail img');
this.thumbnailSrc = $.proxy( this.thumbnailSrc, this );
this.setting.bind( this.thumbnailSrc );
this.library = this.container.find('.library');
// Generate tab objects
this.tabs = {};
panels = this.library.find('.library-content');
this.library.children('ul').children('li').each( function() {
var link = $(this),
id = link.data('customizeTab'),
panel = panels.filter('[data-customize-tab="' + id + '"]');
control.tabs[ id ] = {
both: link.add( panel ),
link: link,
panel: panel
};
});
// Bind tab switch events
this.library.children('ul').on( 'click', 'li', function( event ) {
var id = $(this).data('customizeTab'),
tab = control.tabs[ id ];
event.preventDefault();
if ( tab.link.hasClass('library-selected') )
return;
control.selected.both.removeClass('library-selected');
control.selected = tab;
control.selected.both.addClass('library-selected');
});
// Bind events to switch image urls.
this.library.on( 'click', 'a', function( event ) {
var value = $(this).data('customizeImageValue');
if ( value ) {
control.setting.set( value );
event.preventDefault();
}
});
if ( this.tabs.uploaded ) {
this.tabs.uploaded.target = this.library.find('.uploaded-target');
if ( ! this.tabs.uploaded.panel.find('.thumbnail').length )
this.tabs.uploaded.both.addClass('hidden');
}
// Select a tab
panels.each( function() {
var tab = control.tabs[ $(this).data('customizeTab') ];
// Select the first visible tab.
if ( ! tab.link.hasClass('hidden') ) {
control.selected = tab;
tab.both.addClass('library-selected');
return false;
}
});
this.dropdownInit();
},
success: function( attachment ) {
api.UploadControl.prototype.success.call( this, attachment );
// Add the uploaded image to the uploaded tab.
if ( this.tabs.uploaded && this.tabs.uploaded.target.length ) {
this.tabs.uploaded.both.removeClass('hidden');
// @todo: Do NOT store this on the attachment model. That is bad.
attachment.element = $( '' )
.data( 'customizeImageValue', attachment.get('url') )
.append( '' )
.appendTo( this.tabs.uploaded.target );
}
},
thumbnailSrc: function( to ) {
if ( /^(https?:)?\/\//.test( to ) )
this.thumbnail.prop( 'src', to ).show();
else
this.thumbnail.hide();
}
});
// Change objects contained within the main customize object to Settings.
api.defaultConstructor = api.Setting;
// Create the collection of Control objects.
api.control = new api.Values({ defaultConstructor: api.Control });
api.PreviewFrame = api.Messenger.extend({
sensitivity: 2000,
initialize: function( params, options ) {
var deferred = $.Deferred(),
self = this;
// This is the promise object.
deferred.promise( this );
this.container = params.container;
this.signature = params.signature;
$.extend( params, { channel: api.PreviewFrame.uuid() });
api.Messenger.prototype.initialize.call( this, params, options );
this.add( 'previewUrl', params.previewUrl );
this.query = $.extend( params.query || {}, { customize_messenger_channel: this.channel() });
this.run( deferred );
},
run: function( deferred ) {
var self = this,
loaded = false,
ready = false;
if ( this._ready )
this.unbind( 'ready', this._ready );
this._ready = function() {
ready = true;
if ( loaded )
deferred.resolveWith( self );
};
this.bind( 'ready', this._ready );
this.request = $.ajax( this.previewUrl(), {
type: 'POST',
data: this.query,
xhrFields: {
withCredentials: true
}
} );
this.request.fail( function() {
deferred.rejectWith( self, [ 'request failure' ] );
});
this.request.done( function( response ) {
var location = self.request.getResponseHeader('Location'),
signature = self.signature,
index;
// Check if the location response header differs from the current URL.
// If so, the request was redirected; try loading the requested page.
if ( location && location != self.previewUrl() ) {
deferred.rejectWith( self, [ 'redirect', location ] );
return;
}
// Check if the user is not logged in.
if ( '0' === response ) {
self.login( deferred );
return;
}
// Check for cheaters.
if ( '-1' === response ) {
deferred.rejectWith( self, [ 'cheatin' ] );
return;
}
// Check for a signature in the request.
index = response.lastIndexOf( signature );
if ( -1 === index || index < response.lastIndexOf('