mirror of
https://github.com/WordPress/WordPress.git
synced 2025-01-11 10:59:07 +01:00
Theme Customizer: Improve data binding in wp.customize.Value and wp.customize.Values. see #19910.
* Replace the convoluted wp.customize.Value.link method with a simple shortcut for direct binding. * Add wp.customize.Value.sync for bidirectional linking. * Add wp.customize.Value.setter for handling compound values (instead of using wp.customize.Value.link). git-svn-id: http://svn.automattic.com/wordpress/trunk@20344 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
32e1568691
commit
272d6daac2
@ -2,7 +2,7 @@ if ( typeof wp === 'undefined' )
|
|||||||
var wp = {};
|
var wp = {};
|
||||||
|
|
||||||
(function( exports, $ ){
|
(function( exports, $ ){
|
||||||
var api, extend, ctor, inherits, ready,
|
var api, extend, ctor, inherits,
|
||||||
slice = Array.prototype.slice;
|
slice = Array.prototype.slice;
|
||||||
|
|
||||||
/* =====================================================================
|
/* =====================================================================
|
||||||
@ -66,34 +66,7 @@ if ( typeof wp === 'undefined' )
|
|||||||
return child;
|
return child;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* =====================================================================
|
|
||||||
* customize function.
|
|
||||||
* ===================================================================== */
|
|
||||||
ready = $.Callbacks( 'once memory' );
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sugar for main customize function. Supports several signatures.
|
|
||||||
*
|
|
||||||
* customize( callback, [context] );
|
|
||||||
* Binds a callback to be fired when the customizer is ready.
|
|
||||||
* - callback, function
|
|
||||||
* - context, object
|
|
||||||
*
|
|
||||||
* customize( setting );
|
|
||||||
* Fetches a setting object by ID.
|
|
||||||
* - setting, string - The setting ID.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
api = {};
|
api = {};
|
||||||
// api = function( callback, context ) {
|
|
||||||
// if ( $.isFunction( callback ) ) {
|
|
||||||
// if ( context )
|
|
||||||
// callback = $.proxy( callback, context );
|
|
||||||
// ready.add( callback );
|
|
||||||
//
|
|
||||||
// return api;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* =====================================================================
|
/* =====================================================================
|
||||||
* Base class.
|
* Base class.
|
||||||
@ -156,6 +129,8 @@ if ( typeof wp === 'undefined' )
|
|||||||
this.callbacks = $.Callbacks();
|
this.callbacks = $.Callbacks();
|
||||||
|
|
||||||
$.extend( this, options || {} );
|
$.extend( this, options || {} );
|
||||||
|
|
||||||
|
this.set = $.proxy( this.set, this );
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -173,6 +148,7 @@ if ( typeof wp === 'undefined' )
|
|||||||
set: function( to ) {
|
set: function( to ) {
|
||||||
var from = this._value;
|
var from = this._value;
|
||||||
|
|
||||||
|
to = this._setter.apply( this, arguments );
|
||||||
to = this.validate( to );
|
to = this.validate( to );
|
||||||
|
|
||||||
// Bail if the sanitized value is null or unchanged.
|
// Bail if the sanitized value is null or unchanged.
|
||||||
@ -186,6 +162,22 @@ if ( typeof wp === 'undefined' )
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_setter: function( to ) {
|
||||||
|
return to;
|
||||||
|
},
|
||||||
|
|
||||||
|
setter: function( callback ) {
|
||||||
|
this._setter = callback;
|
||||||
|
this.set( this.get() );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
resetSetter: function() {
|
||||||
|
this._setter = this.constructor.prototype._setter;
|
||||||
|
this.set( this.get() );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
validate: function( value ) {
|
validate: function( value ) {
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
@ -200,183 +192,48 @@ if ( typeof wp === 'undefined' )
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
link: function() { // values*
|
||||||
* Allows the creation of composite values.
|
|
||||||
* Overrides the native link method (can be reverted with `unlink`).
|
|
||||||
*/
|
|
||||||
link: function() {
|
|
||||||
var keys = slice.call( arguments ),
|
|
||||||
callback = keys.pop(),
|
|
||||||
self = this,
|
|
||||||
set, key, active;
|
|
||||||
|
|
||||||
if ( this.links )
|
|
||||||
this.unlink();
|
|
||||||
|
|
||||||
this.links = [];
|
|
||||||
|
|
||||||
// Single argument means a direct binding.
|
|
||||||
if ( ! keys.length ) {
|
|
||||||
keys = [ callback ];
|
|
||||||
callback = function( value, to ) {
|
|
||||||
return to;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
while ( key = keys.shift() ) {
|
|
||||||
if ( this._parent && $.type( key ) == 'string' )
|
|
||||||
this.links.push( this._parent[ key ] );
|
|
||||||
else
|
|
||||||
this.links.push( key );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace this.set with the assignment function.
|
|
||||||
set = function() {
|
|
||||||
var args, result;
|
|
||||||
|
|
||||||
// If we call set from within the assignment function,
|
|
||||||
// pass the arguments to the original set.
|
|
||||||
if ( active )
|
|
||||||
return self.set.original.apply( self, arguments );
|
|
||||||
|
|
||||||
active = true;
|
|
||||||
|
|
||||||
args = self.links.concat( slice.call( arguments ) );
|
|
||||||
result = callback.apply( self, args );
|
|
||||||
|
|
||||||
active = false;
|
|
||||||
|
|
||||||
if ( typeof result !== 'undefined' )
|
|
||||||
self.set.original.call( self, result );
|
|
||||||
};
|
|
||||||
|
|
||||||
set.original = this.set;
|
|
||||||
this.set = set;
|
|
||||||
|
|
||||||
// Bind the new function to the master values.
|
|
||||||
$.each( this.links, function( key, value ) {
|
|
||||||
value.bind( self.set );
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set( this.get() );
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
unlink: function() {
|
|
||||||
var set = this.set;
|
var set = this.set;
|
||||||
|
$.each( arguments, function() {
|
||||||
$.each( this.links, function( key, value ) {
|
this.bind( set );
|
||||||
value.unbind( set );
|
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
delete this.links;
|
unlink: function() { // values*
|
||||||
this.set = this.set.original;
|
var set = this.set;
|
||||||
|
$.each( arguments, function() {
|
||||||
|
this.unbind( set );
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
sync: function() { // values*
|
||||||
|
var that = this;
|
||||||
|
$.each( arguments, function() {
|
||||||
|
that.link( this );
|
||||||
|
this.link( that );
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
unsync: function() { // values*
|
||||||
|
var that = this;
|
||||||
|
$.each( arguments, function() {
|
||||||
|
that.unlink( this );
|
||||||
|
this.unlink( that );
|
||||||
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
api.ensure = function( element ) {
|
api.Values = api.Class.extend({
|
||||||
return typeof element == 'string' ? $( element ) : element;
|
|
||||||
};
|
|
||||||
|
|
||||||
api.Element = api.Value.extend({
|
|
||||||
initialize: function( element, options ) {
|
|
||||||
var self = this,
|
|
||||||
synchronizer = api.Element.synchronizer.html,
|
|
||||||
type, update, refresh;
|
|
||||||
|
|
||||||
this.element = api.ensure( element );
|
|
||||||
this.events = '';
|
|
||||||
|
|
||||||
if ( this.element.is('input, select, textarea') ) {
|
|
||||||
this.events += 'change';
|
|
||||||
synchronizer = api.Element.synchronizer.val;
|
|
||||||
|
|
||||||
if ( this.element.is('input') ) {
|
|
||||||
type = this.element.prop('type');
|
|
||||||
if ( api.Element.synchronizer[ type ] )
|
|
||||||
synchronizer = api.Element.synchronizer[ type ];
|
|
||||||
if ( 'text' === type || 'password' === type )
|
|
||||||
this.events += ' keyup';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
|
|
||||||
this._value = this.get();
|
|
||||||
|
|
||||||
update = this.update;
|
|
||||||
refresh = this.refresh;
|
|
||||||
|
|
||||||
this.update = function( to ) {
|
|
||||||
if ( to !== refresh.call( self ) )
|
|
||||||
update.apply( this, arguments );
|
|
||||||
};
|
|
||||||
this.refresh = function() {
|
|
||||||
self.set( refresh.call( self ) );
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bind( this.update );
|
|
||||||
this.element.bind( this.events, this.refresh );
|
|
||||||
},
|
|
||||||
|
|
||||||
find: function( selector ) {
|
|
||||||
return $( selector, this.element );
|
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function() {},
|
|
||||||
update: function() {}
|
|
||||||
});
|
|
||||||
|
|
||||||
api.Element.synchronizer = {};
|
|
||||||
|
|
||||||
$.each( [ 'html', 'val' ], function( i, method ) {
|
|
||||||
api.Element.synchronizer[ method ] = {
|
|
||||||
update: function( to ) {
|
|
||||||
this.element[ method ]( to );
|
|
||||||
},
|
|
||||||
refresh: function() {
|
|
||||||
return this.element[ method ]();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
api.Element.synchronizer.checkbox = {
|
|
||||||
update: function( to ) {
|
|
||||||
this.element.prop( 'checked', to );
|
|
||||||
},
|
|
||||||
refresh: function() {
|
|
||||||
return this.element.prop( 'checked' );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
api.Element.synchronizer.radio = {
|
|
||||||
update: function( to ) {
|
|
||||||
this.element.filter( function() {
|
|
||||||
return this.value === to;
|
|
||||||
}).prop( 'checked', true );
|
|
||||||
},
|
|
||||||
refresh: function() {
|
|
||||||
return this.element.filter( ':checked' ).val();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
api.ValueFactory = function( constructor ) {
|
|
||||||
constructor = constructor || api.Value;
|
|
||||||
|
|
||||||
return function( key ) {
|
|
||||||
var args = slice.call( arguments, 1 );
|
|
||||||
this[ key ] = new constructor( api.Class.applicator, args );
|
|
||||||
this[ key ]._parent = this;
|
|
||||||
return this[ key ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
api.Values = api.Value.extend({
|
|
||||||
defaultConstructor: api.Value,
|
defaultConstructor: api.Value,
|
||||||
|
|
||||||
initialize: function( options ) {
|
initialize: function( options ) {
|
||||||
api.Value.prototype.initialize.call( this, {}, options || {} );
|
$.extend( this, options || {} );
|
||||||
|
|
||||||
|
this._value = {};
|
||||||
this._deferreds = {};
|
this._deferreds = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -400,7 +257,7 @@ if ( typeof wp === 'undefined' )
|
|||||||
return this.value( id );
|
return this.value( id );
|
||||||
|
|
||||||
this._value[ id ] = value;
|
this._value[ id ] = value;
|
||||||
this._value[ id ]._parent = this._value;
|
this._value[ id ].parent = this;
|
||||||
|
|
||||||
if ( this._deferreds[ id ] )
|
if ( this._deferreds[ id ] )
|
||||||
this._deferreds[ id ].resolve();
|
this._deferreds[ id ].resolve();
|
||||||
@ -469,32 +326,121 @@ if ( typeof wp === 'undefined' )
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.each( [ 'get', 'bind', 'unbind', 'link', 'unlink' ], function( i, method ) {
|
$.each( [ 'get', 'bind', 'unbind', 'link', 'unlink', 'sync', 'unsync', 'setter', 'resetSetter' ], function( i, method ) {
|
||||||
api.Values.prototype[ method ] = function() {
|
api.Values.prototype[ method ] = function() {
|
||||||
return this.pass( method, arguments );
|
return this.pass( method, arguments );
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
api.ensure = function( element ) {
|
||||||
|
return typeof element == 'string' ? $( element ) : element;
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Element = api.Value.extend({
|
||||||
|
initialize: function( element, options ) {
|
||||||
|
var self = this,
|
||||||
|
synchronizer = api.Element.synchronizer.html,
|
||||||
|
type, update, refresh;
|
||||||
|
|
||||||
|
this.element = api.ensure( element );
|
||||||
|
this.events = '';
|
||||||
|
|
||||||
|
if ( this.element.is('input, select, textarea') ) {
|
||||||
|
this.events += 'change';
|
||||||
|
synchronizer = api.Element.synchronizer.val;
|
||||||
|
|
||||||
|
if ( this.element.is('input') ) {
|
||||||
|
type = this.element.prop('type');
|
||||||
|
if ( api.Element.synchronizer[ type ] )
|
||||||
|
synchronizer = api.Element.synchronizer[ type ];
|
||||||
|
if ( 'text' === type || 'password' === type )
|
||||||
|
this.events += ' keyup';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
|
||||||
|
this._value = this.get();
|
||||||
|
|
||||||
|
update = this.update;
|
||||||
|
refresh = this.refresh;
|
||||||
|
|
||||||
|
this.update = function( to ) {
|
||||||
|
if ( to !== refresh.call( self ) )
|
||||||
|
update.apply( this, arguments );
|
||||||
|
};
|
||||||
|
this.refresh = function() {
|
||||||
|
self.set( refresh.call( self ) );
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bind( this.update );
|
||||||
|
this.element.bind( this.events, this.refresh );
|
||||||
|
},
|
||||||
|
|
||||||
|
find: function( selector ) {
|
||||||
|
return $( selector, this.element );
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function() {},
|
||||||
|
|
||||||
|
update: function() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.Element.synchronizer = {};
|
||||||
|
|
||||||
|
$.each( [ 'html', 'val' ], function( i, method ) {
|
||||||
|
api.Element.synchronizer[ method ] = {
|
||||||
|
update: function( to ) {
|
||||||
|
this.element[ method ]( to );
|
||||||
|
},
|
||||||
|
refresh: function() {
|
||||||
|
return this.element[ method ]();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
api.Element.synchronizer.checkbox = {
|
||||||
|
update: function( to ) {
|
||||||
|
this.element.prop( 'checked', to );
|
||||||
|
},
|
||||||
|
refresh: function() {
|
||||||
|
return this.element.prop( 'checked' );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Element.synchronizer.radio = {
|
||||||
|
update: function( to ) {
|
||||||
|
this.element.filter( function() {
|
||||||
|
return this.value === to;
|
||||||
|
}).prop( 'checked', true );
|
||||||
|
},
|
||||||
|
refresh: function() {
|
||||||
|
return this.element.filter( ':checked' ).val();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* =====================================================================
|
/* =====================================================================
|
||||||
* Messenger for postMessage.
|
* Messenger for postMessage.
|
||||||
* ===================================================================== */
|
* ===================================================================== */
|
||||||
|
|
||||||
api.Messenger = api.Class.extend({
|
api.Messenger = api.Class.extend({
|
||||||
add: api.ValueFactory(),
|
add: function( key, initial, options ) {
|
||||||
|
return this[ key ] = new api.Value( initial, options );
|
||||||
|
},
|
||||||
|
|
||||||
initialize: function( url, targetWindow, options ) {
|
initialize: function( url, targetWindow, options ) {
|
||||||
$.extend( this, options || {} );
|
$.extend( this, options || {} );
|
||||||
|
|
||||||
this.add( 'url', url );
|
url = this.add( 'url', url );
|
||||||
this.add( 'targetWindow', targetWindow || null );
|
this.add( 'targetWindow', targetWindow || null );
|
||||||
this.add( 'origin' ).link( 'url', function( url ) {
|
this.add( 'origin', url() ).link( url ).setter( function( to ) {
|
||||||
return url().replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
|
return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
|
||||||
});
|
});
|
||||||
|
|
||||||
this.topics = {};
|
this.topics = {};
|
||||||
|
|
||||||
$.receiveMessage( $.proxy( this.receive, this ), this.origin() || null );
|
$.receiveMessage( $.proxy( this.receive, this ), this.origin() || null );
|
||||||
},
|
},
|
||||||
|
|
||||||
receive: function( event ) {
|
receive: function( event ) {
|
||||||
var message;
|
var message;
|
||||||
|
|
||||||
@ -507,6 +453,7 @@ if ( typeof wp === 'undefined' )
|
|||||||
if ( message && message.id && message.data && this.topics[ message.id ] )
|
if ( message && message.id && message.data && this.topics[ message.id ] )
|
||||||
this.topics[ message.id ].fireWith( this, [ message.data ]);
|
this.topics[ message.id ].fireWith( this, [ message.data ]);
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function( id, data ) {
|
send: function( id, data ) {
|
||||||
var message;
|
var message;
|
||||||
|
|
||||||
@ -516,10 +463,12 @@ if ( typeof wp === 'undefined' )
|
|||||||
message = JSON.stringify({ id: id, data: data });
|
message = JSON.stringify({ id: id, data: data });
|
||||||
$.postMessage( message, this.url(), this.targetWindow() );
|
$.postMessage( message, this.url(), this.targetWindow() );
|
||||||
},
|
},
|
||||||
|
|
||||||
bind: function( id, callback ) {
|
bind: function( id, callback ) {
|
||||||
var topic = this.topics[ id ] || ( this.topics[ id ] = $.Callbacks() );
|
var topic = this.topics[ id ] || ( this.topics[ id ] = $.Callbacks() );
|
||||||
topic.add( callback );
|
topic.add( callback );
|
||||||
},
|
},
|
||||||
|
|
||||||
unbind: function( id, callback ) {
|
unbind: function( id, callback ) {
|
||||||
if ( this.topics[ id ] )
|
if ( this.topics[ id ] )
|
||||||
this.topics[ id ].remove( callback );
|
this.topics[ id ].remove( callback );
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
/*
|
/*
|
||||||
* @param options
|
* @param options
|
||||||
* - previewer - The Previewer instance to sync with.
|
* - previewer - The Previewer instance to sync with.
|
||||||
* - method - The method to use for syncing. Supports 'refresh' and 'postMessage'.
|
* - method - The method to use for previewing. Supports 'refresh' and 'postMessage'.
|
||||||
*/
|
*/
|
||||||
api.Setting = api.Value.extend({
|
api.Setting = api.Value.extend({
|
||||||
initialize: function( id, value, options ) {
|
initialize: function( id, value, options ) {
|
||||||
@ -24,12 +24,10 @@
|
|||||||
element.appendTo( this.previewer.form );
|
element.appendTo( this.previewer.form );
|
||||||
this.element = new api.Element( element );
|
this.element = new api.Element( element );
|
||||||
|
|
||||||
this.element.link( this );
|
this.sync( this.element );
|
||||||
this.link( this.element );
|
this.bind( this.preview );
|
||||||
|
|
||||||
this.bind( this.sync );
|
|
||||||
},
|
},
|
||||||
sync: function() {
|
preview: function() {
|
||||||
switch ( this.method ) {
|
switch ( this.method ) {
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
return this.previewer.refresh();
|
return this.previewer.refresh();
|
||||||
@ -88,9 +86,8 @@
|
|||||||
api( node.data('customizeSettingLink'), function( setting ) {
|
api( node.data('customizeSettingLink'), function( setting ) {
|
||||||
var element = new api.Element( node );
|
var element = new api.Element( node );
|
||||||
control.elements.push( element );
|
control.elements.push( element );
|
||||||
element.link( setting ).bind( function( to ) {
|
element.sync( setting );
|
||||||
setting( to );
|
element.set( setting() );
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -122,10 +119,6 @@
|
|||||||
this.setting.bind( update );
|
this.setting.bind( update );
|
||||||
update( this.setting() );
|
update( this.setting() );
|
||||||
}
|
}
|
||||||
// ,
|
|
||||||
// validate: function( to ) {
|
|
||||||
// return /^[a-fA-F0-9]{3}([a-fA-F0-9]{3})?$/.test( to ) ? to : null;
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
api.UploadControl = api.Control.extend({
|
api.UploadControl = api.Control.extend({
|
||||||
@ -400,7 +393,7 @@
|
|||||||
api.control( 'display_header_text', function( control ) {
|
api.control( 'display_header_text', function( control ) {
|
||||||
var last = '';
|
var last = '';
|
||||||
|
|
||||||
control.elements[0].unlink();
|
control.elements[0].unsync( api( 'header_textcolor' ) );
|
||||||
|
|
||||||
control.element = new api.Element( control.container.find('input') );
|
control.element = new api.Element( control.container.find('input') );
|
||||||
control.element.set( 'blank' !== control.setting() );
|
control.element.set( 'blank' !== control.setting() );
|
||||||
|
Loading…
Reference in New Issue
Block a user