mirror of
https://github.com/WordPress/WordPress.git
synced 2024-12-23 09:37:42 +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 = {};
|
||||
|
||||
(function( exports, $ ){
|
||||
var api, extend, ctor, inherits, ready,
|
||||
var api, extend, ctor, inherits,
|
||||
slice = Array.prototype.slice;
|
||||
|
||||
/* =====================================================================
|
||||
@ -66,34 +66,7 @@ if ( typeof wp === 'undefined' )
|
||||
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 = function( callback, context ) {
|
||||
// if ( $.isFunction( callback ) ) {
|
||||
// if ( context )
|
||||
// callback = $.proxy( callback, context );
|
||||
// ready.add( callback );
|
||||
//
|
||||
// return api;
|
||||
// }
|
||||
// }
|
||||
|
||||
/* =====================================================================
|
||||
* Base class.
|
||||
@ -156,6 +129,8 @@ if ( typeof wp === 'undefined' )
|
||||
this.callbacks = $.Callbacks();
|
||||
|
||||
$.extend( this, options || {} );
|
||||
|
||||
this.set = $.proxy( this.set, this );
|
||||
},
|
||||
|
||||
/*
|
||||
@ -173,6 +148,7 @@ if ( typeof wp === 'undefined' )
|
||||
set: function( to ) {
|
||||
var from = this._value;
|
||||
|
||||
to = this._setter.apply( this, arguments );
|
||||
to = this.validate( to );
|
||||
|
||||
// Bail if the sanitized value is null or unchanged.
|
||||
@ -186,6 +162,22 @@ if ( typeof wp === 'undefined' )
|
||||
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 ) {
|
||||
return value;
|
||||
},
|
||||
@ -200,183 +192,48 @@ if ( typeof wp === 'undefined' )
|
||||
return this;
|
||||
},
|
||||
|
||||
/*
|
||||
* 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() {
|
||||
link: function() { // values*
|
||||
var set = this.set;
|
||||
|
||||
$.each( this.links, function( key, value ) {
|
||||
value.unbind( set );
|
||||
$.each( arguments, function() {
|
||||
this.bind( set );
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
delete this.links;
|
||||
this.set = this.set.original;
|
||||
unlink: function() { // values*
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
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({
|
||||
api.Values = api.Class.extend({
|
||||
defaultConstructor: api.Value,
|
||||
|
||||
initialize: function( options ) {
|
||||
api.Value.prototype.initialize.call( this, {}, options || {} );
|
||||
$.extend( this, options || {} );
|
||||
|
||||
this._value = {};
|
||||
this._deferreds = {};
|
||||
},
|
||||
|
||||
@ -400,7 +257,7 @@ if ( typeof wp === 'undefined' )
|
||||
return this.value( id );
|
||||
|
||||
this._value[ id ] = value;
|
||||
this._value[ id ]._parent = this._value;
|
||||
this._value[ id ].parent = this;
|
||||
|
||||
if ( this._deferreds[ id ] )
|
||||
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() {
|
||||
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.
|
||||
* ===================================================================== */
|
||||
|
||||
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 ) {
|
||||
$.extend( this, options || {} );
|
||||
|
||||
this.add( 'url', url );
|
||||
url = this.add( 'url', url );
|
||||
this.add( 'targetWindow', targetWindow || null );
|
||||
this.add( 'origin' ).link( 'url', function( url ) {
|
||||
return url().replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
|
||||
this.add( 'origin', url() ).link( url ).setter( function( to ) {
|
||||
return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
|
||||
});
|
||||
|
||||
this.topics = {};
|
||||
|
||||
$.receiveMessage( $.proxy( this.receive, this ), this.origin() || null );
|
||||
},
|
||||
|
||||
receive: function( event ) {
|
||||
var message;
|
||||
|
||||
@ -507,6 +453,7 @@ if ( typeof wp === 'undefined' )
|
||||
if ( message && message.id && message.data && this.topics[ message.id ] )
|
||||
this.topics[ message.id ].fireWith( this, [ message.data ]);
|
||||
},
|
||||
|
||||
send: function( id, data ) {
|
||||
var message;
|
||||
|
||||
@ -516,10 +463,12 @@ if ( typeof wp === 'undefined' )
|
||||
message = JSON.stringify({ id: id, data: data });
|
||||
$.postMessage( message, this.url(), this.targetWindow() );
|
||||
},
|
||||
|
||||
bind: function( id, callback ) {
|
||||
var topic = this.topics[ id ] || ( this.topics[ id ] = $.Callbacks() );
|
||||
topic.add( callback );
|
||||
},
|
||||
|
||||
unbind: function( id, callback ) {
|
||||
if ( this.topics[ id ] )
|
||||
this.topics[ id ].remove( callback );
|
||||
|
@ -4,7 +4,7 @@
|
||||
/*
|
||||
* @param options
|
||||
* - 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({
|
||||
initialize: function( id, value, options ) {
|
||||
@ -24,12 +24,10 @@
|
||||
element.appendTo( this.previewer.form );
|
||||
this.element = new api.Element( element );
|
||||
|
||||
this.element.link( this );
|
||||
this.link( this.element );
|
||||
|
||||
this.bind( this.sync );
|
||||
this.sync( this.element );
|
||||
this.bind( this.preview );
|
||||
},
|
||||
sync: function() {
|
||||
preview: function() {
|
||||
switch ( this.method ) {
|
||||
case 'refresh':
|
||||
return this.previewer.refresh();
|
||||
@ -88,9 +86,8 @@
|
||||
api( node.data('customizeSettingLink'), function( setting ) {
|
||||
var element = new api.Element( node );
|
||||
control.elements.push( element );
|
||||
element.link( setting ).bind( function( to ) {
|
||||
setting( to );
|
||||
});
|
||||
element.sync( setting );
|
||||
element.set( setting() );
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -122,10 +119,6 @@
|
||||
this.setting.bind( update );
|
||||
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({
|
||||
@ -400,7 +393,7 @@
|
||||
api.control( 'display_header_text', function( control ) {
|
||||
var last = '';
|
||||
|
||||
control.elements[0].unlink();
|
||||
control.elements[0].unsync( api( 'header_textcolor' ) );
|
||||
|
||||
control.element = new api.Element( control.container.find('input') );
|
||||
control.element.set( 'blank' !== control.setting() );
|
||||
|
Loading…
Reference in New Issue
Block a user