WordPress.org

Make WordPress Core

Changeset 20344


Ignore:
Timestamp:
04/03/12 22:04:40 (2 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.