How to save the state of a drag and drop jQuery UI Sortables front end layout editor?

I am building a front end post layout editor using jQuery UI Sortable.

The posts are laid out in 300px by 250px boxes over a background image. The posts are created and edited using the WordPress admin but I want to allow the sites administrator to adjust the order of the boxes using a drag and drop interface on the front end.

Read More

I’ve got the drag and drop sortable part working but need to come up with a way to save the state (order) of the boxes. Ideally I would like to be able to save the state as an option and build it into the query.

The query for the posts is a simple WP_Query that also gets data from custom meta boxes to determine the individual box layout.:

$args= array(
      'meta_key' => 'c3m_shown_on',
       'meta_value'=> 'home' );
    $box_query = new WP_Query($args);  ?>
        <ul id="sortable">
            <?php
    while ($box_query->have_posts()) : $box_query->the_post(); global $post; global $prefix;           
    $box_size = c3m_get_field($prefix.'box_size', FALSE);
    $box_image = c3m_get_field($prefix.'post_box_image', FALSE);
    $overlay_class = c3m_get_field($prefix.'overlay_class', FALSE);
    
    if ( c3m_get_field($prefix.'external_link', FALSE) ) {
    $post_link = c3m_get_field($prefix.'external_link', FALSE);
    } else
            { $post_link = post_permalink(); 
    } ?>     
     <li class="<?php echo $box_size;?>  ui-state-default">
        <article <?php post_class() ?> id="post-<?php the_ID(); ?>">
            <?php echo  '<a href="'.$post_link.'" ><img src="'.esc_url($box_image).'" alt="Image via xxxxx.com" /></a>'; ?>
                <div class="post-box <?php echo $overlay_class;?>">
                <?php if ( c3m_get_field( $prefix.'text_display', FALSE) ) { ?>     
                <h2><a href="<?php echo $post_link?>"><?php the_title();?></a></h2> 
                <p><?php echo substr($post->post_excerpt, 0, 90) . '...'; ?></p>            
                <?php } ?>               
                </div>
         </article>
     </li>              
    <?php endwhile; ?>
       </ul>
</section>

The javascript is just the basic default sortable instructions

jQuery(document).ready(function() {
    jQuery("#sortable").sortable();
  });

There are methods available using cookies to save the state but I also need to disable the sortable drag and drop for non admin users so I really need to save to the database.

I’m looking for the most creative and usable method and will award a 100 point bounty to the best answer.

Update:

I got somatic’s answer working with one minor change.

ajaxurl doesn’t return the value on non admin pages so I used wp_localize_script( 'functions', 'MyAjax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) ); to define the value and changed the javascript line under options to:
url: MyAjax.ajaxurl,

To limit access to arranging the order to only admins I added a conditional to my wp_enqueue_script function:

    function c3m_load_scripts() { 
    if ( current_user_can( 'edit_posts' ) ) {
        wp_enqueue_script( 'jquery-ui' );
        wp_enqueue_script( 'functions', get_bloginfo( 'stylesheet_directory' ) . '/_/js/functions.js', array( 'jquery', 'jquery-ui' ), false);
        wp_localize_script( 'functions', 'MyAjax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );
    }
}

I’m going to do a little more testing and mark this question as solved and award the bounty.

Related posts

Leave a Reply

3 comments

  1. Brady is correct that the best way to handle saving and displaying of custom post type orders is by using the menu_order property

    Here’s the jquery to make the list sortable and to pass the data via ajax to wordpress:

    jQuery(document).ready(function($) {        
        var itemList = $('#sortable');
    
        itemList.sortable({
            update: function(event, ui) {
                $('#loading-animation').show(); // Show the animate loading gif while waiting
    
                opts = {
                    url: ajaxurl, // ajaxurl is defined by WordPress and points to /wp-admin/admin-ajax.php
                    type: 'POST',
                    async: true,
                    cache: false,
                    dataType: 'json',
                    data:{
                        action: 'item_sort', // Tell WordPress how to handle this ajax request
                        order: itemList.sortable('toArray').toString() // Passes ID's of list items in  1,3,2 format
                    },
                    success: function(response) {
                        $('#loading-animation').hide(); // Hide the loading animation
                        return; 
                    },
                    error: function(xhr,textStatus,e) {  // This can be expanded to provide more information
                        alert(e);
                        // alert('There was an error saving the updates');
                        $('#loading-animation').hide(); // Hide the loading animation
                        return; 
                    }
                };
                $.ajax(opts);
            }
        }); 
    });
    

    Here’s the wordpress function that listens for the ajax callback and performs the changes on the DB:

    function my_save_item_order() {
        global $wpdb;
    
        $order = explode(',', $_POST['order']);
        $counter = 0;
        foreach ($order as $item_id) {
            $wpdb->update($wpdb->posts, array( 'menu_order' => $counter ), array( 'ID' => $item_id) );
            $counter++;
        }
        die(1);
    }
    add_action('wp_ajax_item_sort', 'my_save_item_order');
    add_action('wp_ajax_nopriv_item_sort', 'my_save_item_order');
    

    The key to displaying the posts in the order you have saved is to add the menu_order property to the query args:

    $args= array(
        'meta_key' => 'c3m_shown_on',
        'meta_value'=> 'home'
        'orderby' => 'menu_order',
        'order' => 'ASC'
    );
    
    $box_query = new WP_Query($args);
    

    Then run your loop and output each item… (first line is the core wp loading animation – you’ll want to hide it initially via css, and then the jquery function will display when processing)

    <img src="<?php bloginfo('url'); ?>/wp-admin/images/loading.gif" id="loading-animation" />
    <ul id="sortable">
        <li id="{echo post ID here}">{echo title or other name here}</li>
    </ul>
    

    Code inspired by soulsizzle’s excellent tutorial.

  2. http://jsfiddle.net/TbR69/1/

    Far from finished, but the idea is to send an ajax request on drag and drop. You may also want to trigger the ajax request only after clicking a “save” button or something. An array containing post IDs and new order would be sent.

    Then you’d have to update the posts in the database on the server end. Finally, add an order parameter to your WP_Query loop.

    I hope this gets you started. Anybody feel free to continue fiddling.

  3. /**
     *  Enqueue javascript and css files
     */
    function uc_enqueue_my_assets() {
        wp_enqueue_script( 'jquery-ui-sortable');
        wp_register_script( 'order', plugins_url( '/js/order.js', __FILE__ ), array( 'jquery' ) );
        wp_enqueue_script( 'order' );
    }
    
    function uc_is_user_logged_in()
    {
        if ( is_user_logged_in()) {
            add_action( 'wp_enqueue_scripts', 'uc_enqueue_my_assets' );
            add_action( 'admin_enqueue_scripts', 'uc_enqueue_my_assets' );
        }
    }
    add_action('init', 'uc_is_user_logged_in');
    
    
    /**
     *  Update order of posts by ajax on trigger of drag and drop event
     */
    function uc_sort_post_items() {
    
        $order = wp_parse_id_list(explode(',', $_POST['order']));
        write_log($order);
    
        global $wpdb;
        $list = join(', ', $order);
        $wpdb->query('SELECT @i:=0');
        $wpdb->query(
            "UPDATE wp_posts SET menu_order = ( @i:= @i+1 )
            WHERE ID IN ( $list ) ORDER BY FIELD( ID, $list );"
        );
    
        wp_die();
    }
    add_action('wp_ajax_uc_sort_post_items', 'uc_sort_post_items');
    add_action('wp_ajax_nopriv_uc_sort_post_items', 'uc_sort_post_items');
    
    
    
    /**
     *  Display sorted posts
     */
    function uc_pre_get_posts( $wp_query ) {
        write_log(basename($_SERVER['PHP_SELF']));
        $wp_query->set('orderby', 'menu_order');
        $wp_query->set('order', 'ASC');
    }
    add_action( 'pre_get_posts', 'uc_pre_get_posts', 1 );
    

    Javascript file order.js

    $('#the-list').sortable({
            update: function(event, ui) {
    
                $.ajax({
    
                    url: '/wp-admin/admin-ajax.php',
                    type: 'post',
                    dataType: 'json',
                    data:{
                        action: 'uc_sort_post_items', // Tell WordPress how to handle this ajax request
                        order: '4567,4569,4565 ' // Passes ID's of list items in  1,3,2 format. Write your own js method to access the list of id from frontend.
                    },
                    success: function(data, response) {
                        console.log(response);
                    },
                    error: function(xhr,textStatus,e) {
                        alert(e);
                    }
    
                    });
    
            }
        });