phpajaxwordpresswoocommercejquery-ui-sortable

How can I make Woocommerce cart items sortable using drag and drop?


I use mPdf to create pdf's of the items in my woocommerce cart and I need a way to sort the items in the cart table using drag and drop and also output the new order of the items as $cart_contents for the pdf to recognize the order of the items. My idea is that I can use jquery sortable to make the cart table drag and dropable and then add a positon number to each item in the cart table using cart item data. I can then use the update: function event and update the position number according to it's position in the cart. After that I can output the sorted cart items using asort and the position number assigned to them. I'm not sure if I'm making this way more difficult than it should be since I'm new to jquery and php. I've been searching for an answer to this all over the internet for quite some time but to no avail so any pointers or help with the answer would be greatly appreciated.

Here I make the cart items sortable by drag and drop and then sends the product id's as a string in the order they are placed trough ajax:

function wc_sortable_cart_items() {
    if ( class_exists( 'WooCommerce' ) ) {
        if( is_cart() && (count(WC()->cart->get_cart()) > 0) ){
?>
<script>
jQuery(function($) {
      $('table.shop_table.cart tbody').sortable({
        items: 'tr.cart_item',
        cursor: 'move',
            axis: 'y',
            update: function(event, ui) {
                $('#loading-animation').show();
                opts = {
                    url: ajaxurl,
                    type: 'POST',
                    async: true,
                    cache: false,
                    dataType: 'json',
                    data:{
                        action: 'item_sort',
                        order: $(this).sortable('toArray').toString(),
                    },
                    success: function(response) {
                        $('#loading-animation').hide();
                        return; 
                    },
                    error: function(xhr,textStatus,e) {
                        alert(e);
                        alert('There was an error saving the updates');
                        $('#loading-animation').hide();
                        return; 
                    }
                };
                $.ajax(opts);
            }
        });
});
</script>
<?php
        }
    }
}
add_action( 'wp_footer', 'wc_sortable_cart_items' );

Here I recive the product id's and update the menu_order meta trough $wpdb.


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

Here I sort the cart using the menu_order meta:

add_action('woocommerce_cart_loaded_from_session', 'sort_cart_items_by_customfield', 100);
function sort_cart_items_by_customfield() {
    $items_to_sort = $cart_contents = array();
    foreach (WC()->cart->cart_contents as $item_key => $cart_item) {
        $product        = $cart_item['data'];
        $menu_order     = $product->get_menu_order();
        $order_number   = intval($menu_order);
        $items_to_sort[$item_key] = $order_number;

    }
        asort($items_to_sort);
        foreach ($items_to_sort as $cart_item_key => $order_number) {
        $cart_contents[$cart_item_key] = WC()->cart->cart_contents[$cart_item_key];
    }
        WC()->cart->cart_contents = $cart_contents;
}

All of this is working but since I'm using the menu_order meta to sort the products quite a few problems can arise. For example if you have two of the same product in the cart on seperate table rows or if two people have the same item in their carts at the same time then this method won't work at all.

Does anyone here have any experience doing something similar but using a unique identifier such as the cart item key and storing the position in the session data instead? Or perhaps I could take advantage of a hidden input field and the index of the cart items and update them accordingly?

I'm running out of ideas and would really love some help here.

This is my first time asking a question here so if you need more information or if something is not clear please tell me.

Thanks in advance!


Solution

  • So I manged to solve the problem eventually.

    I'm creating this answer for anyone that might come by looking for a similar solution in the future.

    1. I add the cart item key as the ID of each cart item table row by editing the cart.php template. Ideally do this by adding the cart.php to /wp-content/themes/YOURTHEME/woocommerce/cart to avoid it being overwritten during updates.
    <tr class="woocommerce-cart-form__cart-item <?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ); ?>"id="<?php echo $cart_item['key']; ?>">
    
    1. I enqueue the needed scripts and localize the admin-ajax.php:
    wp_localize_script( 'admin-js', 'dtAjax', array( 'ajaxurl' => admin_url( 'admin-ajax.php')));
    
    function enqueue_script_jquery() {
        if( is_cart() && (count(WC()->cart->get_cart()) > 0) ){
            wp_enqueue_script( 'jquery-ui');
            wp_enqueue_script( 'jquery-ui-sortable');
        }
    }
    add_action('wp_enqueue_scripts', 'enqueue_script_jquery');
    
    1. I add the script to make the table rows sortable and post the order of the ID's to the server.
    function wc_sortable_cart_items() {
        if ( class_exists( 'WooCommerce' ) ) {
            if( is_cart() && (count(WC()->cart->get_cart()) > 0) ){
    ?>
    <script async>
    jQuery(document).ready(function($){
        $(document).ajaxComplete(function() {
            $('table.shop_table.cart tbody').sortable({
                items: 'tr.cart_item',
                cursor: 'move',
                axis: 'y',
                opacity: 0.6,
                delay: 75,
                containment: "document",
                update: function(event, ui) {
                    var cart_keys = $(".cart_item[id]").map(function(){ return this.id; }).get();
                    opts = {
                        url: ajaxurl,
                        type: 'POST',
                        async: true,
                        cache: false,
                        dataType: 'json',
                        data:{
                            security: $('#woocommerce-cart-nonce').val(),
                            action: 'item_sort',
                            order: cart_keys,
                        },
                    };
                    $.ajax(opts);
                }
            });
        });
    });
    </script>
    <?php
            }
        }
    }
    add_action( 'wp_footer', 'wc_sortable_cart_items' );
    
    1. Then I save the order of the cart items(cart item keys) in the session:
    function my_save_item_order() {
     if( ! isset( $_POST['security'] ) || ! wp_verify_nonce( $_POST['security'], 'woocommerce-cart' ) ) {
     wp_send_json( array( 'nonce_fail' => 1 ) );
     exit;
     }
      $order = $_POST['order'];
            WC()->session->set( 'sort_order', $order );
            wp_send_json( [
                'sort_order' => $order,
            ] );
    wp_die(1);
    }
    add_action('wp_ajax_item_sort', 'my_save_item_order');
    add_action('wp_ajax_nopriv_item_sort', 'my_save_item_order');
    
    1. And then I order the cart items according to the set session:
    function sort_cart_items_by_dd() {
        $sort_order = WC()->session->get( 'sort_order' ) ;
            if( empty( $sort_order ) )
                return;
            
            WC()->session->__unset( 'sort_order' );
            
            $cart_contents = array();
            foreach( $sort_order as $cart_key )
            {
                foreach( WC()->cart->cart_contents as $item_key => $cart_item )
                {
                    if( $item_key == $cart_key )
                    {
                        $cart_contents[$item_key] = $cart_item;
                    }
                    
                }
            }
            
            WC()->cart->cart_contents = $cart_contents;
    }
    add_action('woocommerce_cart_loaded_from_session', 'sort_cart_items_by_dd', 100);
    

    And that's it. Now you can sort the cart items in the the cart table using drag and drop.