4
votes

I'm running my demo store under Woocommerce and I want to move price that shows up when you choose product variations to be right below Qty field and not between it and the last variation.

This is the code I tried inside my functions.php file so far but it didn't work:

// Move WooCommerce price
remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_price', 10 );
add_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_price', 25 );

Did I make a mistake somewhere?

7
The problem in variable products you have 2 prices. the first one is displayed permanently before the product short description and shows in general the lowest and the highest prices. The other one is displayed once you have selected all the necessary attributes values for this variable product (it shows the price of this variation). So do you really want to mix them up together? This is not a convenient solution… think about it (as the second price appear when a variation has been selected)… - LoicTheAztec

7 Answers

8
votes

The javascript file woocommerce/assets/js/frontend/add-to-cart-variation.js, responsible for displaying variable product prices, basically uses $('form').find('.single_variation'); to update the price, so if the .single_variation is outside of the form, it will not work.

So things like this do not work:

function move_variation_price() {
    remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
    add_action( 'woocommerce_single_product_summary', 'woocommerce_single_variation', 5 );
}
add_action( 'woocommerce_before_add_to_cart_form', 'move_variation_price' );

That said, your best bet if you want to use it on top of your product, is to use the woocommerce_before_variations_form hook.

woocommerce_single_variation hook will bring up the add to cart button too, so you will have to hide that out with CSS:

form.variations_form.cart .single_variation_wrap:nth-child(1) .quantity {
    display: none !important; /* important is necessary */
}
form.variations_form.cart .single_variation_wrap:nth-child(1) button {
    display: none;
}

I know it hurts. But it's the "only" way.

Method 2

Ok, I was pissed with Method 1 and came up with this new method, that doesn't require any changes in PHP, only Javascript and CSS.

Javascript that will check if the variable product price changed, and will update the actual price div with the new value. This new price div can be anywhere, not only inside the form.

Javascript:

// Update price according to variable price
if (jQuery('form.variations_form').length !== 0) {
    var form = jQuery('form.variations_form');
    var variable_product_price = '';
    setInterval(function() {
        if (jQuery('.single_variation_wrap span.price span.amount').length !== 0) {
            if (jQuery('.single_variation_wrap span.price span.amount').text() !== variable_product_price) {
                variable_product_price = jQuery('.single_variation_wrap span.price span.amount').text();
                jQuery('.single-product-summary p.price span.amount').text(variable_product_price);
            }
        }
    }, 500);
}

CSS:

.single_variation_wrap .price {
    display: none;
}

Final result: woocommerce move variable product price

7
votes

This will move the variation price to below the quantity and just above the add to cart button. You may need to apply some CSS styles as needed.

function move_variation_price() {
    remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
    add_action( 'woocommerce_after_add_to_cart_quantity', 'woocommerce_single_variation', 10 );
}
add_action( 'woocommerce_before_add_to_cart_form', 'move_variation_price' );

Since the woocommerce_after_add_to_cart_quantity hook was added in WooCommerce 3.0, in order to get the above code to be work in WooCommerce 2.6.x, you will need to override the single-product/add-to-cart/variation-add-to-cart-button.php template by saving it to your theme's woocommerce folder and then adding in the action hook. See below:

<?php
/**
 * Single variation cart button
 *
 * @see     https://docs.woocommerce.com/document/template-structure/
 * @author  WooThemes
 * @package WooCommerce/Templates
 * @version 2.5.0
 */
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

global $product;
?>
<div class="woocommerce-variation-add-to-cart variations_button">
    <?php if ( ! $product->is_sold_individually() ) : ?>
        <?php woocommerce_quantity_input( array( 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( $_POST['quantity'] ) : 1 ) ); ?>
    <?php endif; ?>
    
    <?php do_action( 'woocommerce_after_add_to_cart_quantity' ); ?>
    
    <button type="submit" class="single_add_to_cart_button button alt"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>
    <input type="hidden" name="add-to-cart" value="<?php echo absint( $product->id ); ?>" />
    <input type="hidden" name="product_id" value="<?php echo absint( $product->id ); ?>" />
    <input type="hidden" name="variation_id" class="variation_id" value="0" />
</div>
4
votes

I think I have found a very simple solution. Woocommerce launches a javascript event when the selection of variations has changed.

We can listen to that event and copy the .woocommerce-variation-price .woocommerce-Price-amount html wherever we want.

Next I leave you an example of my solution:

js



    jQuery(function ($) {
        /**
        * Change Price Variation to correct position
        */
        $('.variations_form').on('woocommerce_variation_has_changed', function () {
            $('.wrap-price-variation').empty();
            $('.wrap-price-variation').html($('.woocommerce-variation-price .woocommerce-Price-amount').html())
        });
    });


css



    .woocommerce-variation .woocommerce-variation-price {
        display: none;
    }


1
votes

You can use CSS:

.woocommerce div.product .summary.entry-summary {
 display: flex;
 flex-direction: column;
}

And order property to sort elements by the order you want:

 .product_title.entry-title {
      order: 1;
 }
 div.woo-short-description {
      order: 2;
 }
 form.cart {
      order: 3;
 }
 p.price {
      order: 4;
 }
 div.quick_buy_container {
      order: 5;
 }

More information can be found at: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items

0
votes

I have used this:

jQuery('form.variations_form #size').change(function(){
    setTimeout(function() { 
        jQuery('.price-wrapper .price > .amount').text(jQuery('.woocommerce-variation-price .price > .amount').text());
    }, 800);
});

and works perfectly. The code about seems not to work.

0
votes

I just wrote this using Lucas' answer above as the template, but utilizing the actual select ("drop-down") element's change-state in jQuery to accomplish it. It also takes into account the price-range default state to deliver a more complete-feeling solution.

CSS:

/* Hide variant prices since we have JS to show that in place of the range */
body.woocommerce .product .single_variation_wrap {
    display: none !important;
}

JavaScript:

/* Product Detail Page: Variant Pricing Updater  */ 
// Updates price according to variable price
jQuery(function() {
    if (jQuery('form.variations_form').length !== 0) {

        /* Set this to the ID of your variant select drop-down.
         * Example: var $variant_selector = jQuery('#color');  */ 
         var $variant_selector = jQuery('#color');

        var variable_product_price = '';
        var vpu_timeout;

        // Get and save the original price range
        var original_price_range = jQuery('.product .summary p.price').html();
        $variant_selector.change(function() { 

            // Clear any previous timeouts to avoid possible stacking
            clearTimeout(vpu_timeout);

            // setTimeout so we don't get ahead of WooCommerce's JS
            vpu_timeout = setTimeout(function(){ 

                // If there's no value, set back to default.
                if($variant_selector.val().length < 1) {
                    jQuery('.product .summary p.price').html(original_price_range);
                    return(false);
                }

                // Make sure we have a price to pull from
                if (jQuery('.single_variation_wrap span.price').length !== 0) {
                    if ($variant_selector.val().length > 0) {

                        // Update the price HTML variable if different
                        variable_product_price = jQuery('.single_variation_wrap span.price').html();

                        // Then change the displayed price
                        jQuery('.product .summary p.price').html(variable_product_price);

                        return(true);
                    }
                }
            }, 5); 
        });

    }
});
0
votes

In case a more complete solution is searched for, this is how I solved the task at hand-

// Edit single page variable product pricing 
add_filter( 'woocommerce_variable_price_html', 'wrap_variation_price_html', 9999, 2 );  
function wrap_variation_price_html( $price, $product ) {
    if( ! is_product() ) return;
    $price = sprintf( __( '<span class="wrap-top-price">%1$s</span>', 'woocommerce' ), $price );
    return $price;
}

add_action('wp_footer', 'update_variable_product_price', 999, 0);
function update_variable_product_price() {
    if( ! is_product() ) return;
    ?>
    <script>
        jQuery(function ($) {
            $(document).ready(function() {
                var original_price_html = $('.wrap-top-price').html();
                $('.entry-summary').on('click', '.reset_variations', function() {
                    $('.wrap-top-price').html(original_price_html);
                });
                $('.variations_form').on('woocommerce_variation_has_changed', function () {
                    if($('.woocommerce-variation-price .woocommerce-Price-amount').html().length) {
                        $('.wrap-top-price').empty();
                        $('.wrap-top-price').html($('.woocommerce-variation-price .woocommerce-Price-amount').html());
                    }
                });
            })
        });
    </script>
    <?php
}

The first filter is adding a wrapper span around the price on top, whatever passed into, the min-price or the range.

Then the JS is getting added in the footer of the single product page. It's copying the initial value of the price on top to cache it which is used later. We listen to the woocommerce_variation_has_changed event and copy the dynamically added variation price element into the top price wrapper we added previously. Once the reset_variations button is clicked, we set the top price back to what it was on page load.