Make WordPress Core

Changeset 20645


Ignore:
Timestamp:
04/30/2012 03:46:17 PM (14 years ago)
Author:
koopersmith
Message:

Theme Customizer: Migrate to an ajax-based solution for refreshing the preview and saving. see #20507, #19910.

  • Use ajax-based saving, add saving indicator.
  • Use ajax-based refreshing instead of form targets.
  • Instead of using hidden inputs with prefixed names to track the canonical data, use the values stored in wp.customize. Encode the values as JSON before sending to avoid bugs with ids that contain square brackets (PHP mangles POST values with nested brackets).
  • Use wp.customize.Previewer solely for the purpose of previewing; move the postMessage connection with the parent frame and other unrelated code snippets into the 'ready' handler.
Location:
trunk/wp-includes
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/class-wp-customize-setting.php

    r20585 r20645  
    2222        private $_post_value; // Cached, sanitized $_POST value.
    2323
    24         // Prefix for $_POST values to prevent naming conflicts.
    25         const name_prefix = 'customize_';
    26 
    2724        /**
    2825         * Constructor.
    … …  
    122119                        return $this->_post_value;
    123120
    124                 $base = self::name_prefix . $this->id_data[ 'base' ];
    125 
    126                 if ( ! isset( $_POST[ $base ] ) )
    127                         return $default;
    128 
    129                 $result = $this->multidimensional_get( $_POST[ $base ], $this->id_data[ 'keys' ] );
    130                 if ( ! isset( $result ) )
    131                         return $default;
    132 
    133                 $result = $this->sanitize( $result );
     121                $result = $this->manager->post_value( $this );
     122
    134123                if ( isset( $result ) )
    135124                        return $this->_post_value = $result;
  • trunk/wp-includes/class-wp-customize.php

    r20598 r20645  
    1818        protected $controls = array();
    1919
     20        protected $customized;
     21
     22        private $_post_values;
     23
    2024        /**
    2125         * Constructor.
    … …  
    3135                add_action( 'admin_init',   array( $this, 'admin_init' ) );
    3236                add_action( 'wp_loaded',    array( $this, 'wp_loaded' ) );
     37
     38                add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
    3339
    3440                add_action( 'customize_register',                 array( $this, 'register_controls' ) );
    … …  
    150156
    151157        /**
     158         * Decode the $_POST attribute used to override the WP_Customize_Setting values.
     159         *
     160         * @since 3.4.0
     161         */
     162        public function post_value( $setting ) {
     163                if ( ! isset( $this->_post_values ) ) {
     164                        if ( isset( $_POST['customized'] ) )
     165                                $this->_post_values = json_decode( stripslashes( $_POST['customized'] ), true );
     166                        else
     167                                $this->_post_values = false;
     168                }
     169
     170                if ( isset( $this->_post_values[ $setting->id ] ) )
     171                        return $setting->sanitize( $this->_post_values[ $setting->id ] );
     172        }
     173
     174
     175        /**
    152176         * Print javascript settings.
    153177         *
    … …  
    268292         */
    269293        public function admin_init() {
    270                 if ( isset( $_REQUEST['save_customize_controls'] ) )
    271                         $this->save();
    272 
    273294                if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) )
    274295                        return;
    … …  
    298319        public function save() {
    299320                if ( ! $this->is_preview() )
    300                         return;
    301 
    302                 check_admin_referer( 'customize_controls' );
     321                        die;
     322
     323                check_ajax_referer( 'customize_controls', 'nonce' );
    303324
    304325                // Do we have to switch themes?
    305326                if ( $this->get_stylesheet() != $this->original_stylesheet ) {
    306327                        if ( ! current_user_can( 'switch_themes' ) )
    307                                 return;
     328                                die;
    308329
    309330                        // Temporarily stop previewing the theme to allow switch_themes()
    … …  
    321342
    322343                add_action( 'admin_notices', array( $this, '_save_feedback' ) );
     344
     345                die;
    323346        }
    324347
  • trunk/wp-includes/css/customize-controls.dev.css

    r20598 r20645  
    127127#customize-theme-controls .customize-section-content {
    128128        margin: 0;
     129}
     130
     131#customize-footer-actions img {
     132        display: none;
     133        position: absolute;
     134        top: 18px;
     135        margin-left: 4px;
     136}
     137.saving #customize-footer-actions img {
     138        display: inline;
    129139}
    130140
  • trunk/wp-includes/customize-controls.php

    r20585 r20645  
    4242</head>
    4343<body class="wp-full-overlay">
    44         <form id="customize-controls" method="post" class="wrap wp-full-overlay-sidebar" target="_parent" action="<?php echo esc_url( add_query_arg( 'save_customize_controls', '1', admin_url( 'themes.php' ) ) ); ?>">
     44        <form id="customize-controls" class="wrap wp-full-overlay-sidebar">
    4545                <?php wp_nonce_field( 'customize_controls' ); ?>
    46                 <input type="hidden" name="customize" value="on" />
    47                 <input type="hidden" name="theme" value="<?php echo esc_attr( $this->get_stylesheet() ); ?>" />
    4846                <div id="customize-header-actions" class="customize-section wp-full-overlay-header">
    4947                        <a class="back" href="<?php echo esc_url( admin_url( 'themes.php' ) ); ?>">
    … …  
    8078                        submit_button( $save_text, 'primary', 'save', false );
    8179                        ?>
     80                        <img src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" />
     81
    8282                        <a href="#" class="collapse-sidebar button-secondary" title="<?php esc_attr_e('Collapse Sidebar'); ?>">
    8383                                <span class="collapse-sidebar-label"><?php _e('Collapse'); ?></span>
    … …  
    8686                </div>
    8787        </form>
    88         <div id="customize-preview" class="wp-full-overlay-main">
    89                 <iframe name="customize-target"></iframe>
    90         </div>
     88        <div id="customize-preview" class="wp-full-overlay-main"></div>
    9189        <?php
    9290
    … …  
    9694        $scheme = is_ssl() ? 'https' : 'http';
    9795        $settings = array(
     96                'theme'    => $this->get_stylesheet(),
    9897                'preview'  => esc_url( home_url( '/', $scheme ) ),
    9998                'settings' => array(),
    10099                'controls' => array(),
    101                 'prefix'   => WP_Customize_Setting::name_prefix,
    102100                'parent'   => esc_url( admin_url() ),
     101                'ajax'     => esc_url( admin_url( 'admin-ajax.php', 'relative' ) ),
    103102        );
    104103
  • trunk/wp-includes/js/customize-base.dev.js

    r20517 r20645  
    266266                },
    267267
     268                get: function() {
     269                        var result = {};
     270
     271                        if ( arguments.length )
     272                                return this.pass( 'get', arguments );
     273
     274                        $.each( this._value, function( key, obj ) {
     275                                result[ key ] = obj.get();
     276                        } );
     277                        return result;
     278                },
     279
    268280                set: function( id ) {
    269281                        if ( this.has( id ) )
    … …  
    327339        });
    328340
    329         $.each( [ 'get', 'bind', 'unbind', 'link', 'unlink', 'sync', 'unsync', 'setter', 'resetSetter' ], function( i, method ) {
     341        $.each( [ 'bind', 'unbind', 'link', 'unlink', 'sync', 'unsync', 'setter', 'resetSetter' ], function( i, method ) {
    330342                api.Values.prototype[ method ] = function() {
    331343                        return this.pass( method, arguments );
  • trunk/wp-includes/js/customize-controls.dev.js

    r20598 r20645  
    1616                        this.transport = this.transport || 'refresh';
    1717
    18                         element = $( '<input />', {
    19                                 type:  'hidden',
    20                                 value: this.get(),
    21                                 name:  api.settings.prefix + id
    22                         });
    23 
    24                         element.appendTo( this.previewer.form );
    25                         this.element = new api.Element( element );
    26 
    27                         this.sync( this.element );
    2818                        this.bind( this.preview );
    2919                },
    … …  
    272262                /**
    273263                 * Requires params:
    274                  *  - iframe - a selector or jQuery element
    275                  *  - form   - a selector or jQuery element
    276                  *  - url    - the URL of preview frame
     264                 *  - container - a selector or jQuery element
     265                 *  - url       - the URL of preview frame
    277266                 */
    278267                initialize: function( params, options ) {
    … …  
    282271
    283272                        this.loaded = $.proxy( this.loaded, this );
    284 
    285                         this.loaderUuid = 0;
    286273
    287274                        /*
    … …  
    321308                        })( this );
    322309
    323                         this.iframe = api.ensure( params.iframe );
    324                         this.form   = api.ensure( params.form );
    325                         this.name   = this.iframe.prop('name');
    326 
    327                         this.container = this.iframe.parent();
    328 
    329                         api.Messenger.prototype.initialize.call( this, params.url, this.iframe[0].contentWindow );
    330 
    331                         this._formOriginalProps = {
    332                                 target: this.form.prop('target'),
    333                                 action: this.form.prop('action')
    334                         };
     310                        this.container = api.ensure( params.container );
     311
     312                        api.Messenger.prototype.initialize.call( this, params.url );
    335313
    336314                        this.bind( 'url', function( url ) {
    337315                                // Bail if we're navigating to the current url, to a different origin, or wp-admin.
    338                                 if ( this.url() == url || 0 !== url.indexOf( this.origin() + '/' ) || -1 !== url.indexOf( 'wp-admin' )  )
     316                                if ( this.url() == url || 0 !== url.indexOf( this.origin() + '/' ) || -1 !== url.indexOf( 'wp-admin' ) )
    339317                                        return;
    340318
    … …  
    342320                                this.refresh();
    343321                        });
    344 
    345                         this.refresh();
    346 
    347                         // Prevent the form from saving when enter is pressed.
    348                         this.form.on( 'keydown', function( e ) {
    349                                 if ( 13 === e.which ) // Enter
    350                                         e.preventDefault();
    351                         });
    352 
    353                         // Create a potential postMessage connection with the parent frame.
    354                         this.parent = new api.Messenger( api.settings.parent );
    355 
    356                         // If we receive a 'back' event, we're inside an iframe.
    357                         // Send any clicks to the 'Return' link to the parent page.
    358                         this.parent.bind( 'back', function( text ) {
    359                                 self.form.find('.back').text( text ).click( function( event ) {
    360                                         event.preventDefault();
    361                                         self.parent.send( 'close' );
    362                                 });
    363                         });
    364 
    365                         // Initialize the connection with the parent frame.
    366                         this.parent.send( 'ready' );
    367322                },
    368323                loader: function() {
    … …  
    370325                                return this.loading;
    371326
    372                         this.loading = $('<iframe />', {
    373                                 name: this.name + '-loading-' + this.loaderUuid++
    374                         }).appendTo( this.container );
     327                        this.loading = $('<iframe />').appendTo( this.container );
    375328
    376329                        return this.loading;
    377330                },
    378331                loaded: function() {
    379                         this.iframe.remove();
     332                        if ( this.iframe )
     333                                this.iframe.remove();
    380334                        this.iframe = this.loading;
    381335                        delete this.loading;
    382                         this.iframe.prop( 'name', this.name );
     336
    383337                        this.targetWindow( this.iframe[0].contentWindow );
    384338                },
     339                query: function() {},
    385340                refresh: function() {
    386                         this.loader().one( 'load', this.loaded );
    387 
    388                         this.submit({
    389                                 target: this.loader().prop('name'),
    390                                 action: this.url()
    391                         });
    392                 },
    393                 submit: function( props ) {
    394                         if ( props )
    395                                 this.form.prop( props );
    396                         this.form.submit();
    397                         if ( props )
    398                                 this.form.prop( this._formOriginalProps );
     341                        var self = this;
     342
     343                        if ( this.request )
     344                                this.request.abort();
     345
     346                        this.request = $.post( this.url(), this.query() || {}, function( response ) {
     347                                var iframe = self.loader()[0].contentWindow;
     348
     349                                self.loader().one( 'load', self.loaded );
     350
     351                                iframe.document.open();
     352                                iframe.document.write( response );
     353                                iframe.document.close();
     354                        });
    399355                }
    400356        });
    … …  
    416372                // Initialize Previewer
    417373                var body = $( document.body ),
    418                         previewer = new api.Previewer({
    419                                 iframe: '#customize-preview iframe',
    420                                 form:   '#customize-controls',
    421                                 url:    api.settings.preview
    422                         });
     374                        query, previewer, parent;
     375
     376                // Prevent the form from saving when enter is pressed.
     377                $('#customize-controls').on( 'keydown', function( e ) {
     378                        if ( 13 === e.which ) // Enter
     379                                e.preventDefault();
     380                });
     381
     382                previewer = new api.Previewer({
     383                        container: '#customize-preview',
     384                        form:      '#customize-controls',
     385                        url:       api.settings.preview
     386                }, {
     387                        query: function() {
     388                                return {
     389                                        customize:  'on',
     390                                        theme:      api.settings.theme,
     391                                        customized: JSON.stringify( api.get() )
     392                                };
     393                        },
     394
     395                        nonce: $('#_wpnonce').val(),
     396
     397                        save: function() {
     398                                var query = $.extend( this.query(), {
     399                                                action: 'customize_save',
     400                                                nonce:  this.nonce
     401                                        }),
     402                                        request = $.post( api.settings.ajax, query );
     403
     404                                body.addClass('saving');
     405                                request.always( function() {
     406                                        body.removeClass('saving');
     407                                });
     408                        }
     409                });
    423410
    424411                $.each( api.settings.settings, function( id, data ) {
    … …  
    439426                });
    440427
     428                // Load the preview frame.
     429                previewer.refresh();
     430
    441431                // Temporary accordion code.
    442432                $('.customize-section-title').click( function() {
    … …  
    447437                // Button bindings.
    448438                $('#save').click( function( event ) {
    449                         previewer.submit();
     439                        previewer.save();
    450440                        event.preventDefault();
    451441                });
    … …  
    455445                        event.preventDefault();
    456446                });
     447
     448                // Create a potential postMessage connection with the parent frame.
     449                parent = new api.Messenger( api.settings.parent );
     450
     451                // If we receive a 'back' event, we're inside an iframe.
     452                // Send any clicks to the 'Return' link to the parent page.
     453                parent.bind( 'back', function( text ) {
     454                        $('.back').text( text ).click( function( event ) {
     455                                event.preventDefault();
     456                                parent.send( 'close' );
     457                        });
     458                });
     459
     460                // Initialize the connection with the parent frame.
     461                parent.send( 'ready' );
    457462
    458463                // Control visibility for default controls
Note: See TracChangeset for help on using the changeset viewer.