WordPress.org

Make WordPress Core

Changeset 20645


Ignore:
Timestamp:
04/30/2012 03:46:17 PM (8 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.