WordPress.org

Make WordPress Core

Changeset 20344


Ignore:
Timestamp:
04/03/2012 10:04:40 PM (7 years ago)
Author:
koopersmith
Message:

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).
Location:
trunk/wp-includes/js
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/js/customize-base.dev.js

    r20295 r20344  
    33
    44(function( exports, $ ){
    5     var api, extend, ctor, inherits, ready,
     5    var api, extend, ctor, inherits,
    66        slice = Array.prototype.slice;
    77
     
    6767    };
    6868
    69     /* =====================================================================
    70      * customize function.
    71      * ===================================================================== */
    72     ready = $.Callbacks( 'once memory' );
    73 
    74     /*
    75      * Sugar for main customize function. Supports several signatures.
    76      *
    77      * customize( callback, [context] );
    78      *   Binds a callback to be fired when the customizer is ready.
    79      *   - callback, function
    80      *   - context, object
    81      *
    82      * customize( setting );
    83      *   Fetches a setting object by ID.
    84      *   - setting, string - The setting ID.
    85      *
    86      */
    8769    api = {};
    88     // api = function( callback, context ) {
    89     //  if ( $.isFunction( callback ) ) {
    90     //      if ( context )
    91     //          callback = $.proxy( callback, context );
    92     //      ready.add( callback );
    93     //
    94     //      return api;
    95     //  }
    96     // }
    9770
    9871    /* =====================================================================
     
    157130
    158131            $.extend( this, options || {} );
     132
     133            this.set = $.proxy( this.set, this );
    159134        },
    160135
     
    174149            var from = this._value;
    175150
     151            to = this._setter.apply( this, arguments );
    176152            to = this.validate( to );
    177153
     
    187163        },
    188164
     165        _setter: function( to ) {
     166            return to;
     167        },
     168
     169        setter: function( callback ) {
     170            this._setter = callback;
     171            this.set( this.get() );
     172            return this;
     173        },
     174
     175        resetSetter: function() {
     176            this._setter = this.constructor.prototype._setter;
     177            this.set( this.get() );
     178            return this;
     179        },
     180
    189181        validate: function( value ) {
    190182            return value;
     
    201193        },
    202194
    203         /*
    204          * Allows the creation of composite values.
    205          * Overrides the native link method (can be reverted with `unlink`).
    206          */
    207         link: function() {
    208             var keys = slice.call( arguments ),
    209                 callback = keys.pop(),
    210                 self = this,
    211                 set, key, active;
    212 
    213             if ( this.links )
    214                 this.unlink();
    215 
    216             this.links = [];
    217 
    218             // Single argument means a direct binding.
    219             if ( ! keys.length ) {
    220                 keys = [ callback ];
    221                 callback = function( value, to ) {
    222                     return to;
    223                 };
    224             }
    225 
    226             while ( key = keys.shift() ) {
    227                 if ( this._parent && $.type( key ) == 'string' )
    228                     this.links.push( this._parent[ key ] );
    229                 else
    230                     this.links.push( key );
    231             }
    232 
    233             // Replace this.set with the assignment function.
    234             set = function() {
    235                 var args, result;
    236 
    237                 // If we call set from within the assignment function,
    238                 // pass the arguments to the original set.
    239                 if ( active )
    240                     return self.set.original.apply( self, arguments );
    241 
    242                 active = true;
    243 
    244                 args = self.links.concat( slice.call( arguments ) );
    245                 result = callback.apply( self, args );
    246 
    247                 active = false;
    248 
    249                 if ( typeof result !== 'undefined' )
    250                     self.set.original.call( self, result );
    251             };
    252 
    253             set.original = this.set;
    254             this.set = set;
    255 
    256             // Bind the new function to the master values.
    257             $.each( this.links, function( key, value ) {
    258                 value.bind( self.set );
    259             });
    260 
    261             this.set( this.get() );
    262 
    263             return this;
    264         },
    265 
    266         unlink: function() {
     195        link: function() { // values*
    267196            var set = this.set;
    268 
    269             $.each( this.links, function( key, value ) {
    270                 value.unbind( set );
    271             });
    272 
    273             delete this.links;
    274             this.set = this.set.original;
    275             return this;
    276         }
    277     });
    278 
    279     api.ensure = function( element ) {
    280         return typeof element == 'string' ? $( element ) : element;
    281     };
    282 
    283     api.Element = api.Value.extend({
    284         initialize: function( element, options ) {
    285             var self = this,
    286                 synchronizer = api.Element.synchronizer.html,
    287                 type, update, refresh;
    288 
    289             this.element = api.ensure( element );
    290             this.events = '';
    291 
    292             if ( this.element.is('input, select, textarea') ) {
    293                 this.events += 'change';
    294                 synchronizer = api.Element.synchronizer.val;
    295 
    296                 if ( this.element.is('input') ) {
    297                     type = this.element.prop('type');
    298                     if ( api.Element.synchronizer[ type ] )
    299                         synchronizer = api.Element.synchronizer[ type ];
    300                     if ( 'text' === type || 'password' === type )
    301                         this.events += ' keyup';
    302                 }
    303             }
    304 
    305             api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
    306             this._value = this.get();
    307 
    308             update  = this.update;
    309             refresh = this.refresh;
    310 
    311             this.update = function( to ) {
    312                 if ( to !== refresh.call( self ) )
    313                     update.apply( this, arguments );
    314             };
    315             this.refresh = function() {
    316                 self.set( refresh.call( self ) );
    317             };
    318 
    319             this.bind( this.update );
    320             this.element.bind( this.events, this.refresh );
    321         },
    322 
    323         find: function( selector ) {
    324             return $( selector, this.element );
    325         },
    326 
    327         refresh: function() {},
    328         update: function() {}
    329     });
    330 
    331     api.Element.synchronizer = {};
    332 
    333     $.each( [ 'html', 'val' ], function( i, method ) {
    334         api.Element.synchronizer[ method ] = {
    335             update: function( to ) {
    336                 this.element[ method ]( to );
    337             },
    338             refresh: function() {
    339                 return this.element[ method ]();
    340             }
    341         };
    342     });
    343 
    344     api.Element.synchronizer.checkbox = {
    345         update: function( to ) {
    346             this.element.prop( 'checked', to );
    347         },
    348         refresh: function() {
    349             return this.element.prop( 'checked' );
    350         }
    351     };
    352 
    353     api.Element.synchronizer.radio = {
    354         update: function( to ) {
    355             this.element.filter( function() {
    356                 return this.value === to;
    357             }).prop( 'checked', true );
    358         },
    359         refresh: function() {
    360             return this.element.filter( ':checked' ).val();
    361         }
    362     };
    363 
    364     api.ValueFactory = function( constructor ) {
    365         constructor = constructor || api.Value;
    366 
    367         return function( key ) {
    368             var args = slice.call( arguments, 1 );
    369             this[ key ] = new constructor( api.Class.applicator, args );
    370             this[ key ]._parent = this;
    371             return this[ key ];
    372         };
    373     };
    374 
    375     api.Values = api.Value.extend({
     197            $.each( arguments, function() {
     198                this.bind( set );
     199            });
     200            return this;
     201        },
     202
     203        unlink: function() { // values*
     204            var set = this.set;
     205            $.each( arguments, function() {
     206                this.unbind( set );
     207            });
     208            return this;
     209        },
     210
     211        sync: function() { // values*
     212            var that = this;
     213            $.each( arguments, function() {
     214                that.link( this );
     215                this.link( that );
     216            });
     217            return this;
     218        },
     219
     220        unsync: function() { // values*
     221            var that = this;
     222            $.each( arguments, function() {
     223                that.unlink( this );
     224                this.unlink( that );
     225            });
     226            return this;
     227        }
     228    });
     229
     230    api.Values = api.Class.extend({
    376231        defaultConstructor: api.Value,
    377232
    378233        initialize: function( options ) {
    379             api.Value.prototype.initialize.call( this, {}, options || {} );
     234            $.extend( this, options || {} );
     235
     236            this._value = {};
    380237            this._deferreds = {};
    381238        },
     
    401258
    402259            this._value[ id ] = value;
    403             this._value[ id ]._parent = this._value;
     260            this._value[ id ].parent = this;
    404261
    405262            if ( this._deferreds[ id ] )
     
    470327    });
    471328
    472     $.each( [ 'get', 'bind', 'unbind', 'link', 'unlink' ], function( i, method ) {
     329    $.each( [ 'get', 'bind', 'unbind', 'link', 'unlink', 'sync', 'unsync', 'setter', 'resetSetter' ], function( i, method ) {
    473330        api.Values.prototype[ method ] = function() {
    474331            return this.pass( method, arguments );
     
    476333    });
    477334
     335    api.ensure = function( element ) {
     336        return typeof element == 'string' ? $( element ) : element;
     337    };
     338
     339    api.Element = api.Value.extend({
     340        initialize: function( element, options ) {
     341            var self = this,
     342                synchronizer = api.Element.synchronizer.html,
     343                type, update, refresh;
     344
     345            this.element = api.ensure( element );
     346            this.events = '';
     347
     348            if ( this.element.is('input, select, textarea') ) {
     349                this.events += 'change';
     350                synchronizer = api.Element.synchronizer.val;
     351
     352                if ( this.element.is('input') ) {
     353                    type = this.element.prop('type');
     354                    if ( api.Element.synchronizer[ type ] )
     355                        synchronizer = api.Element.synchronizer[ type ];
     356                    if ( 'text' === type || 'password' === type )
     357                        this.events += ' keyup';
     358                }
     359            }
     360
     361            api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
     362            this._value = this.get();
     363
     364            update  = this.update;
     365            refresh = this.refresh;
     366
     367            this.update = function( to ) {
     368                if ( to !== refresh.call( self ) )
     369                    update.apply( this, arguments );
     370            };
     371            this.refresh = function() {
     372                self.set( refresh.call( self ) );
     373            };
     374
     375            this.bind( this.update );
     376            this.element.bind( this.events, this.refresh );
     377        },
     378
     379        find: function( selector ) {
     380            return $( selector, this.element );
     381        },
     382
     383        refresh: function() {},
     384
     385        update: function() {}
     386    });
     387
     388    api.Element.synchronizer = {};
     389
     390    $.each( [ 'html', 'val' ], function( i, method ) {
     391        api.Element.synchronizer[ method ] = {
     392            update: function( to ) {
     393                this.element[ method ]( to );
     394            },
     395            refresh: function() {
     396                return this.element[ method ]();
     397            }
     398        };
     399    });
     400
     401    api.Element.synchronizer.checkbox = {
     402        update: function( to ) {
     403            this.element.prop( 'checked', to );
     404        },
     405        refresh: function() {
     406            return this.element.prop( 'checked' );
     407        }
     408    };
     409
     410    api.Element.synchronizer.radio = {
     411        update: function( to ) {
     412            this.element.filter( function() {
     413                return this.value === to;
     414            }).prop( 'checked', true );
     415        },
     416        refresh: function() {
     417            return this.element.filter( ':checked' ).val();
     418        }
     419    };
     420
    478421    /* =====================================================================
    479422     * Messenger for postMessage.
     
    481424
    482425    api.Messenger = api.Class.extend({
    483         add: api.ValueFactory(),
     426        add: function( key, initial, options ) {
     427            return this[ key ] = new api.Value( initial, options );
     428        },
    484429
    485430        initialize: function( url, targetWindow, options ) {
    486431            $.extend( this, options || {} );
    487432
    488             this.add( 'url', url );
     433            url = this.add( 'url', url );
    489434            this.add( 'targetWindow', targetWindow || null );
    490             this.add( 'origin' ).link( 'url', function( url ) {
    491                 return url().replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
     435            this.add( 'origin', url() ).link( url ).setter( function( to ) {
     436                return to.replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
    492437            });
    493438
     
    496441            $.receiveMessage( $.proxy( this.receive, this ), this.origin() || null );
    497442        },
     443
    498444        receive: function( event ) {
    499445            var message;
     
    508454                this.topics[ message.id ].fireWith( this, [ message.data ]);
    509455        },
     456
    510457        send: function( id, data ) {
    511458            var message;
     
    517464            $.postMessage( message, this.url(), this.targetWindow() );
    518465        },
     466
    519467        bind: function( id, callback ) {
    520468            var topic = this.topics[ id ] || ( this.topics[ id ] = $.Callbacks() );
    521469            topic.add( callback );
    522470        },
     471
    523472        unbind: function( id, callback ) {
    524473            if ( this.topics[ id ] )
  • trunk/wp-includes/js/customize-controls.dev.js

    r20319 r20344  
    55     * @param options
    66     * - previewer - The Previewer instance to sync with.
    7      * - method    - The method to use for syncing. Supports 'refresh' and 'postMessage'.
     7     * - method    - The method to use for previewing. Supports 'refresh' and 'postMessage'.
    88     */
    99    api.Setting = api.Value.extend({
     
    2525            this.element = new api.Element( element );
    2626
    27             this.element.link( this );
    28             this.link( this.element );
    29 
    30             this.bind( this.sync );
    31         },
    32         sync: function() {
     27            this.sync( this.element );
     28            this.bind( this.preview );
     29        },
     30        preview: function() {
    3331            switch ( this.method ) {
    3432                case 'refresh':
     
    8987                    var element = new api.Element( node );
    9088                    control.elements.push( element );
    91                     element.link( setting ).bind( function( to ) {
    92                         setting( to );
    93                     });
     89                    element.sync( setting );
     90                    element.set( setting() );
    9491                });
    9592            });
     
    123120            update( this.setting() );
    124121        }
    125         // ,
    126         //      validate: function( to ) {
    127         //          return /^[a-fA-F0-9]{3}([a-fA-F0-9]{3})?$/.test( to ) ? to : null;
    128         //      }
    129122    });
    130123
     
    401394            var last = '';
    402395
    403             control.elements[0].unlink();
     396            control.elements[0].unsync( api( 'header_textcolor' ) );
    404397
    405398            control.element = new api.Element( control.container.find('input') );
Note: See TracChangeset for help on using the changeset viewer.