We are having a custom code to allow end user select city, date and time of his delivery. The delivery rate should change according to the city. City select box is on the checkout page.
Changing the city triggers ajax that change successfully the charge for the delivery that displayed under order review section. So far so good.
Unfortunately, the price does not reach the order after submitting the order. It gets lost somewhere.
A hint may be in the fact that the same city select box is on the product page as well, and if a user does select it when adding to cart, and just then goes to checkout, the delivery rate is displayed and does not got lost when submitting the order.
Is there any refresh needs to be triggered in order for the shipping rate to be updated?
The function that updates the rates (successfully)
function adjust_shipping_rate( $rates ){
global $woocommerce;
foreach ($rates as $rate) {
$cost = $rate->cost;
$rate->cost = $_COOKIE['shipping_city_cost'];
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'adjust_shipping_rate', 50, 1 );
UPDATE 21-05-2017
This is how the cookie is updated: Once the select box is changed, async ajax call is fired and execute the following PHP code.
function get_and_set_shipping_rate(){
$shipping_city = $_POST['city'];
$shipping_cost = get_shipping_cost_by_city($shipping_city);
setcookie('shipping_city_cost', $shipping_cost, time() + (86400 * 30), '/');
$_COOKIE['shipping_city_cost'] = $shipping_cost;
echo 'Shipping cost updated: '.$shipping_city.' : '.$shipping_cost;
}
add_action( 'wp_ajax_get_and_set_shipping_rate', 'get_and_set_shipping_rate' );
add_action( 'wp_ajax_nopriv_get_and_set_shipping_rate', 'get_and_set_shipping_rate' );
Here is the ajax call:
jQuery(document).on('change', '#shipping_delivery_city', function(){
var requested_city = jQuery(this).val();
var data = {
'action': 'get_and_set_shipping_rate',
'city': requested_city
};
jQuery.ajax({
type: "POST",
url: shipping_dates.ajax_url,
data: data,
async: false,
success: function (response) {
console.log(response);
}
});
});
With this, I have two assumptions. 1. ) You are using this code on checkout page. 2. ) You already have your own function get_shipping_cost_by_city
add_action( 'woocommerce_checkout_update_order_review', 'woocommerce_checkout_update_order_review' );
function woocommerce_checkout_update_order_review( $post_data ){
$data = array();
$vars = explode('&', $post_data);
foreach ($vars as $k => $value){
$v = explode('=', urldecode($value));
$data[$v[0]] = $v[1];
}
$shipping_cost = get_shipping_cost_by_city( $data['billing_city'] );
WC()->session->set( 'shipping_city_cost', $shipping_cost );
foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ) {
// this is needed for us to remove the session set for the shipping cost. Without this, we can't set it on the checkout page.
WC()->session->set( 'shipping_for_package_' . $package_key, false );
}
}
add_filter( 'woocommerce_package_rates', 'adjust_shipping_rate', 50 );
function adjust_shipping_rate( $rates ){
foreach ($rates as $rate) {
$cost = $rate->cost;
$rate->cost = WC()->session->get( 'shipping_city_cost' );
}
return $rates;
}
the setting/getting of shipping_city_cost is done by WC()->session->set and WC()->session->get. I need not to put an ajax function for woocommerce_checkout_update_order_review is an action hook inside an ajax call whenever an update is done on the checkout page.
for this test, I use this function:
function get_shipping_cost_by_city( $city ) {
if ( $city == 'Ormoc City' ){
return 100;
}
return 130;
}
Related
I'm trying to update cart item quantities and its price based on the custom selection field on checkout. If someone selects "Single person" option from the dropdown then the quantity will be 1 and if someone select "Two person" from the Dropdown then the cart quantity will update to 2 and the price will update as well.
Users can only add one product to the cart, Here is the reference link from where I got some help.
https://www.webroomtech.com/woocommerce-checkout-change-quantity-and-delete-products/
Im using wordpress ajax call to acheive this.
In functions.php I have this code
function customjs_script_add_quanity_js() {
wp_enqueue_script( 'checkout_script', get_stylesheet_directory_uri() . '/assets/js/add_quantity.js', array('jquery') );
wp_localize_script( 'checkout_script', 'add_quantity', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
add_action( 'wp_enqueue_scripts', 'customjs_script_add_quanity_js' );
function custom_addqty_load_ajax() {
if ( !is_user_logged_in() ){
add_action( 'wp_ajax_nopriv_update_order_review', 'anp_update_order_review' );
} else{
add_action( 'wp_ajax_update_order_review', 'anp_update_order_review' );
}
}
add_action( 'init', 'custom_addqty_load_ajax' );
function anp_update_order_review() {
$numberof_people = intval($_POST['number_of_people']);
$values = array();
parse_str($_POST['post_data'], $values);
$cart = $values['cart']; //This have no values in it,
foreach ( $cart as $cart_key => $cart_value ){
WC()->cart->set_quantity( $cart_key, $numberof_people);
WC()->cart->calculate_totals();
woocommerce_cart_totals();
}
wp_die();
}
In my **add_quantity.js** file
jQuery(function() {
jQuery( "form.checkout" ).on( "change", ".woocommerce-additional-fields select#______NumberOfTravelers______", function( ) {
//console.log('on chage called');
var number_of_people = jQuery(this).val();
var data = {
action: 'update_order_review',
security: wc_checkout_params.update_order_review_nonce,
number_of_people: number_of_people,
post_data: jQuery( 'form.checkout' ).serialize()
};
jQuery.post( add_quantity.ajax_url, data, function( response )
{
jQuery( 'body' ).trigger( 'update_checkout' );
console.log('success');
});
});
});
My $cart variable in the ajax function shows empty and the cart is not updating on the selection field change. Any help will be appreciated. Thanks
I've updated my code function.php and add_quantity.js
function customjs_script_add_quanity_js() {
wp_enqueue_script( 'checkout_script', get_stylesheet_directory_uri() . '/assets/js/add_quantity.js', array('jquery') );
wp_localize_script( 'checkout_script', 'add_quantity', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
add_action( 'wp_enqueue_scripts', 'customjs_script_add_quanity_js' );
function anp_update_order_review() {
$numberof_people = intval($_POST['number_of_people']);
$cart = WC()->cart->get_cart();
foreach ( $cart as $cart_item_key => $item ){
WC()->cart->set_quantity( $cart_item_key, $numberof_people);
WC()->cart->calculate_totals();
woocommerce_cart_totals(); //checkout order total not calculating correctly and cart items quantity and price not changing
}
wp_die();
}
add_action( 'wp_ajax_nopriv_update_order_review', 'anp_update_order_review' );
add_action( 'wp_ajax_update_order_review', 'anp_update_order_review' );
updated add_quantity.js file
jQuery(function() {
jQuery( "form.checkout" ).on( "change", ".woocommerce-additional-fields select#________NumberOfTravelers________", function( ) {
var number_of_people = jQuery(this).val();
//console.log('nop: ' + number_of_people);
var data = {
action: 'update_order_review',
number_of_people: number_of_people,
security: wc_checkout_params.update_order_review_nonce
};
jQuery.post( add_quantity.ajax_url, data, function( response )
{
jQuery( 'body' ).trigger( 'update_checkout' );
console.log('success');
});
});
});
Now this start working but it calculating wrong price on checkout order table at bottom and cart item quantity and price is not updating on dropdown selection.
I think you don't need serialize checkout form data.
just in your ajax function use the following code:
$cart = WC()->cart;
for your ajax function you don't have to check if the user is logged in. By default, WordPress checks the hook if its admin side or front end side. In addition, you are calling the hooks from inside a function.
function anp_update_order_review() {
$numberof_people = intval($_POST['number_of_people']);
$values = array();
parse_str($_POST['post_data'], $values);
$cart = $values['cart']; //This have no values in it,
foreach ( $cart as $cart_key => $cart_value ){
WC()->cart->set_quantity( $cart_key, $numberof_people);
WC()->cart->calculate_totals();
woocommerce_cart_totals();
}
wp_die();
}
add_action( 'wp_ajax_nopriv_update_order_review', 'anp_update_order_review' );
add_action( 'wp_ajax_update_order_review', 'anp_update_order_review' );
I've resolved the issue and managed to update the cart quantity and its price without an ajax call.
My functions.php file only includes a custom add_quantity.js file
function customjs_script_add_quanity_js() {
wp_enqueue_script( 'checkout_script', get_stylesheet_directory_uri() . '/assets/js/add_quantity.js', array('jquery') );
wp_localize_script( 'checkout_script', 'add_quantity', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
add_action( 'wp_enqueue_scripts', 'customjs_script_add_quanity_js' );
Above enqueue scripts Code goes in function.php file of your active child theme (or active theme).
updated add_quantity.js file
jQuery(function() {
jQuery( "form.checkout" ).on( "change", ".woocommerce-additional-fields select#_number_of_travlers_", function( ) {
var number_of_people = parseInt(jQuery(this).val());
if(number_of_people){
jQuery( 'input.qty' ).attr('value', number_of_people);
}else{
jQuery( 'input.qty' ).attr('value', 1);
}
jQuery(".cart [name='update_cart']").removeAttr('disabled').attr('aria-disabled','false').trigger("click");
});
});
Tested and works perfectly!
I'm trying to load Woocommerce checkout form via Ajax on a custom landing page so that it can have an instant checkout for the visitor.
I'm using the following code:
PHP function for AJAX:
add_action( 'wp_ajax_getCheckoutPageContent', 'getCheckoutPageContentCallBack' );
add_action( 'wp_ajax_nopriv_getCheckoutPageContent', 'getCheckoutPageContentCallBack' );
function getCheckoutPageContentCallBack() {
$product_id = absint( $_POST['product_id'] );
$quantity = absint( $_POST['quantity'] );
$product_status = get_post_status( $product_id );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( WC()->cart->add_to_cart( $product_id, $quantity ) ) {
do_action( 'woocommerce_ajax_added_to_cart', $product_id );
global $woocommerce;
$items = $woocommerce->cart->get_cart();
wc_setcookie( 'woocommerce_items_in_cart', count( $items ) );
wc_setcookie( 'woocommerce_cart_hash', md5( json_encode( $items ) ) );
do_action( 'woocommerce_set_cart_cookies', true );
define( 'WOOCOMMERCE_CHECKOUT', true );
echo do_shortcode('[woocommerce_checkout]');
}else{
define( 'WOOCOMMERCE_CHECKOUT', true );
echo do_shortcode('[woocommerce_checkout]');
}
die();
}
Javascrip code:
var wp_ajax_url= myAjax.ajaxurl;
var data = {
action: 'getCheckoutPageContent',
product_id: $('#land_prod_id').val(),
quantity: 1
};
jQuery.post( wp_ajax_url, data, function(content) {
jQuery("#buy_form_location").html(content);
});
} else{
All payment methods work except for Stripe where I get redirected to the actual checkout page, with the following error: Payment processing failed. Please retry.
On the regular checkout page the payment works, but I'd like it to work on the one loaded via AJAX too.
I'm using this plugin: woocommerce.com/products/stripe
For checkout I use one script to show and hide billing fields depending on the shipping way.
Earlier it worked fine, but that time - I think because of using select2 in the city field instead of the text input - the address text field is making issues.
When user writes their address not so fast, the form is starting to update too quick, can delete the new written after that short pause characters or not delete, randomly, and when I want to delete something - the select 2 can appear on the top of the page and not closing.
add_filter( 'woocommerce_update_order_review_fragments', 'awoohc_add_update_form_billing', 99 );
function awoohc_add_update_form_billing( $fragments ) {
$checkout = WC()->checkout();
ob_start();
?>
<div class="woocommerce-billing-fields__field-wrapper">
<?php
$fields = $checkout->get_checkout_fields( 'billing' );
foreach ( $fields as $key => $field ) {
if ( isset( $field['country_field'], $fields[ $field['country_field'] ] ) ) {
$field['country'] = $checkout->get_value( $field['country_field'] );
}
woocommerce_form_field( $key, $field, $checkout->get_value( $key ) );
}
?>
</div>
<?php
$art_add_update_form_billing = ob_get_clean();
$fragments['.woocommerce-billing-fields'] = $art_add_update_form_billing;
return $fragments;
}
add_filter( 'woocommerce_checkout_fields' , 'override_billing_checkout_fields', 20, 1 );
function override_billing_checkout_fields( $fields ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
if ( 'local_pickup:1' === $chosen_methods[0] ) {
unset( $fields['billing']['billing_address_1'] );
unset( $fields['billing']['billing_city'] );
unset( $fields['billing']['billing_state'] );
}
return $fields;
}
add_filter( 'woocommerce_checkout_fields' , 'b_override_billing_checkout_fields', 20, 1 );
function b_override_billing_checkout_fields( $fields ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
if ( 'boxberry_self:4' === $chosen_methods[0] || 'boxberry_self_after:6' === $chosen_methods[0] ) {
unset( $fields['billing']['billing_address_1'] );
}
return $fields;
}
// Just hide woocommerce billing country
add_action( 'woocommerce_before_checkout_form', 'hide_checkout_billing_country', 5 );
function hide_checkout_billing_country() {
echo '<style>#billing_country_field{display:none;}</style>';
}
add_filter('woocommerce_billing_fields', 'customize_checkout_fields', 100 );
function customize_checkout_fields( $fields ) {
if ( is_checkout() ) {
// HERE set the required key fields below
$chosen_fields = array( 'postcode', 'country', 'company','last_name', 'address_2');
foreach( $chosen_fields as $key ) {
if( isset($fields['billing_'.$key]) && $key !== 'country') {
unset($fields['billing_'.$key]); // Remove all define fields except country
}
}
}
return $fields;
}
/*
* Updating the form
*/
add_action( 'wp_footer', 'awoohc_add_script_update_shipping_method' );
function awoohc_add_script_update_shipping_method() {
if ( is_checkout() ) {
?>
<script>
jQuery(document).ready(function ($) {
$(document.body).on('updated_checkout updated_shipping_method', function (event, xhr, data) {
$('input[name^="shipping_method"]').on('change', function () {
$('.woocommerce-billing-fields__field-wrapper').block({
message: null,
overlayCSS: {
background: '#fff',
'z-index': 1000000,
opacity: 0.3
}
});
$('select#billing_city').select2();
});
var first_name = $('#billing_first_name').val(),
phone = $('#billing_phone').val(),
email = $('#billing_email').val(),
city = $('#billing_city').val(),
address_1 = $('#billing_address_1').val(),
$(".woocommerce-billing-fields__field-wrapper").html(xhr.fragments[".woocommerce-billing-fields"]);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_first_name"]').val(first_name);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_phone"]').val(phone);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_email"]').val(email);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_city"]').val(city);
$('select#billing_city').select2();
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_address_1"]').val(address_1);
$('.woocommerce-billing-fields__field-wrapper').unblock();
});
});
</script>
<?php
}
}
Yes, I needed to use select2 twice, in other ways it didn't work as needed. But when even I use it once, it didn't help me with that issue. I can't disable select2, the client needs it, and needs to fix changing the address.
How I replaced the city input to select
add_filter( 'woocommerce_checkout_fields' , 'override_checkout_city_fields' );
function override_checkout_city_fields( $fields ) {
// Define here in the array your desired cities (Here an example of cities)
$option_cities = array(
'city1' => 'city1',
'city2' => 'city2',
);
$fields['billing']['billing_city']['type'] = 'select';
$fields['billing']['billing_city']['options'] = $option_cities;
$fields['shipping']['shipping_city']['type'] = 'select';
$fields['shipping']['shipping_city']['options'] = $option_cities;
return $fields;
}
Or maybe it's a delivery service plugin. Maybe you see what I don't see.
I know that exist many plugins, but I need to find what's wrong manually.
I tried to /comment/ different parts of the script, so I think it's something with select, or with delicery service plugin if not it.
I'm trying to put all of the pieces of this puzzle together. I've been reading all of the questions and answers on this subject for the past 3 days. So the general blueprint that i'm following is as follows:
On single product page, first checking whether the type of product is "simple" or "variable".
If product is "variable" then i'm using woocommerce_variable_add_to_cart(); function to output the proper html.
Then trying to generate new and custom html (i.e "radio buttons") using the defualt html (i.e "dropdown menu") and woocommerce hooks.
Then trying to give functionality to the new and custom html (i.e "radio buttons") using javascript.
Then hiding the default dropdown menu using css.
Then trying to send an ajax request to the wordpress.
Then trying to process that ajax request on the backend and add the product to the cart.
Here is my code for each section:
Checking whether the type of product is "variable" on the single product page:
global $post;
$product = wc_get_product($post->ID);
$product_type = $product->get_type();
If the product type is "variable" then output the proper html:
if($product_type == 'variable'):
woocommerce_variable_add_to_cart();
endif;
Generating new and custom html (radio buttons) using php and woocommerce hooks:
add_filter('woocommerce_dropdown_variation_attribute_options_html', 'my_theme_variation_radio_buttons', 20, 2);
function my_theme_variation_radio_buttons($html, $args)
{
$args = wp_parse_args(apply_filters('woocommerce_dropdown_variation_attribute_options_args', $args), array(
'options' => false,
'attribute' => false,
'product' => false,
'selected' => false,
'name' => '',
'id' => '',
'class' => '',
'show_option_none' => __('Choose an option', 'woocommerce'),
));
if (false === $args['selected'] && $args['attribute'] && $args['product'] instanceof WC_Product) {
$selected_key = 'attribute_' . sanitize_title($args['attribute']);
$args['selected'] = isset($_REQUEST[$selected_key]) ? wc_clean(wp_unslash($_REQUEST[$selected_key])) : $args['product']->get_variation_default_attribute($args['attribute']);
}
$options = $args['options'];
$product = $args['product'];
$attribute = $args['attribute'];
$name = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title($attribute);
$id = $args['id'] ? $args['id'] : sanitize_title($attribute);
$class = $args['class'];
$show_option_none = (bool)$args['show_option_none'];
$show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __('Choose an option', 'woocommerce');
if (empty($options) && !empty($product) && !empty($attribute)) {
$attributes = $product->get_variation_attributes();
$options = $attributes[$attribute];
}
$radios = '<div class="variation-radios">';
if (!empty($options)) {
if ($product && taxonomy_exists($attribute)) {
$terms = wc_get_product_terms($product->get_id(), $attribute, array(
'fields' => 'all',
));
foreach ($terms as $term) {
if (in_array($term->slug, $options, true)) {
$id = $name . '-' . $term->slug;
$radios .= '<input type="radio" data-checked="no" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr($term->slug) . '" ' . checked(sanitize_title($args['selected']), $term->slug, false) . '><label for="' . esc_attr($id) . '">' . esc_html(apply_filters('woocommerce_variation_option_name', $term->name)) . '</label>';
}
}
} else {
foreach ($options as $option) {
$id = $name . '-' . $option;
$checked = sanitize_title($args['selected']) === $args['selected'] ? checked($args['selected'], sanitize_title($option), false) : checked($args['selected'], $option, false);
$radios .= '<input type="radio" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr($option) . '" id="' . sanitize_title($option) . '" ' . $checked . '><label for="' . esc_attr($id) . '">' . esc_html(apply_filters('woocommerce_variation_option_name', $option)) . '</label>';
}
}
}
$radios .= '</div>';
return $html . $radios;
}
add_filter('woocommerce_variation_is_active', 'my_theme_variation_check', 10, 2);
function my_theme_variation_check($active, $variation)
{
if (!$variation->is_in_stock() && !$variation->backorders_allowed()) {
return false;
}
return $active;
}
Giving functionality to "radio buttons" using javascript:
jQuery(document).ready($ => {
$(document).on('change', '.variation-radio input', function () {
$('.variation-radio input:checked').each(function (index, element) {
var radioElement = $(element);
var radioName = radioElement.attr('name');
var radioValue = radioElement.attr('value');
$('select[name="' + radioName + '"]').val(radioValue).trigger('change');
});
});
$(document).on('woocommerce_update_variation_values', function () {
$('.variation-radio input').each(function (index, element) {
var radioElement = $(element);
var radioName = radioElement.attr('name');
var radioValue = radioElement.attr('value');
radioElement.removeAttr('disabled');
if ($('select[name="' + radioName + '"] option[value="' + radioValue + '"]').is(':disabled')) {
radioElement.prop('disabled', true);
}
});
});
$("a.reset_variations").click(function () {
$('input:radio[name="attribute_size"]').prop('checked', false); $(this).css('display', 'none');
});
})
Hiding the default dropdown menu using css:
table.variations select{
display: none;
}
Sending an ajax request to the wordpress:
jQuery(document).ready($ => {
$("button.single_add_to_cart_button").on('click', function (e) {
e.preventDefault();
var myBtn = $(this),
$form = myBtn.closest('form.variations_form'),
product_qty = $form.find('input[name=quantity]').val() || 1,
product_id = $form.find('input[name=product_id]').val(),
variation_id = $form.find('input[name=variation_id]').val() || 0,
variation = {},
keys = [],
values = [];
// Looping through the attributes names and save them as the keys array
$('table tr td.label label').each(function (index, element) {
let radioElement = $(element);
keys[index] = radioElement.text();
});
// Looping through the attributes values and save them as the values array
$('.variation-radios input:checked').each(function (index, element) {
let radioElement = $(element);
values[index] = radioElement.val();
});
// Looping through the variation object and save keys and values in that object
$.each(keys, function (index, element) {
variation[element] = values[index]
})
console.log(variation);
var data = {
action: 'woocommerce_add_variation_to_cart',
product_id: product_id,
quantity: product_qty,
variation_id: variation_id,
var: variation
};
$(document.body).trigger('adding_to_cart', [myBtn, data]);
$.ajax({
type: 'post',
url: wc_add_to_cart_params.ajax_url,
data: data,
beforeSend: function (response) {
myBtn.removeClass('added').addClass('loading');
},
complete: function (response) {
myBtn.addClass('added').removeClass('loading');
},
success: function (response) {
console.log(response);
if (response.error && response.product_url) {
window.location = response.product_url;
return;
} else {
$(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, myBtn]);
}
},
});
return false;
});
})
Processing the ajax request on the backend and add the product to the cart:
add_action('wp_ajax_nopriv_woocommerce_add_variation_to_cart', 'my_theme_testing_add_to_cart_variable');
add_action('wp_ajax_woocommerce_add_variation_to_cart', 'my_theme_testing_add_to_cart_variable');
function my_theme_testing_add_to_cart_variable()
{
if (isset($_POST['product_id']) && $_POST['product_id'] > 0) {
$product_id = apply_filters('woocommerce_add_to_cart_product_id', absint($_POST['product_id']));
$quantity = empty($_POST['quantity']) ? 1 : wc_stock_amount($_POST['quantity']);
$variation_id = isset($_POST['variation_id']) ? absint($_POST['variation_id']) : '';
$attributes = explode(',', $_POST['var']);
$variation = array();
foreach ($attributes as $values) {
$values = explode(':', $values);
$variation['attributes_' . $values[0]] = $values[1];
}
$passed_validation = apply_filters('woocommerce_add_to_cart_validation', true, $product_id, $quantity);
if ($passed_validation && WC()->cart->add_to_cart($product_id, $quantity, $variation_id, $variation)) {
do_action('woocommerce_ajax_added_to_cart', $product_id);
if (get_option('woocommerce_cart_redirect_after_add') == 'yes') {
wc_add_to_cart_message($product_id);
}
WC_AJAX::get_refreshed_fragments();
} else {
$data = array(
'error' => true,
'product_url' => apply_filters('woocommerce_cart_redirect_after_error', get_permalink($product_id), $product_id)
);
wp_send_json($data);
}
die();
}
}
PROBLEM
Everything works until step 7 with no error but when i run the whole thing, the single product page refreshes and variable product doesn't get added to the cart. And the wordpress error says "{your attribute field} is a required field"!
I think the bug could be somewhere in the ajax call when i'm trying to send variation object to the backend.
Although, i get the data absolutely fine on the backend but it doesn't add it to the cart.
Things that i've tried to debug it
I've tried to send the data in the ajax call as an array but didn't work either.
I also tried to explode the variation data using both = and : but none has worked!
Hooof! It's been a long week so far :\ full of debugging, headaches and frustrations. Now when i try to run the whole shebang, i can't get it to work and i've been reading all of the Qs and As on SO but can't find the bug! I think i've been overthinking it for a couple of days and also there are lots of pieces to it.
So i think i need some extra pairs of eyes to hopefully detect the bug.
Thank you, i appreciate any insight(s)!
In addition, huge shout out to these fellows whom i've learned a lot from:
LoicTheAztec for this great answer and this and this and many more.
cfx for this great answer
Anthony Grist for this answer
helgatheviking for this answer
AND OTHERS
EDIT
The code works fine, bug wasn't in the code, it was in the data that i was provided with. I'll leave the code here just in case somebody needs it in the future.
Short ansewer
The code works fine, i'll leave it here just in case somebody needs it in the future. Bug wasn't in the code, it was in the data that i was provided with.
Detailed Explanation
The code works fine. The data that i was provided with was manipulated for some reasons by my client so that each variable product wasn't a real variable product but at the same time the labels were typed in as variable products (yea i know it's confusing and not a standard practice), that's why whenever i tried to add them to cart, it would give the error saying {your-attribute} is a required field.
So we deleted each product data and add it back as a real and true variable product, then the code worked without us changing anything in it.
Take-away
So remember, whenever developing your app, there are always two sides to this coin! One side is your code and the other side is the data you're working on.
So, always always always, make sure the data you're working with is the way/format it's supposed to be. Also if you can't find any bug in your code, remember to check/debug the other side which is the data.
If you don't check the data first or at any debugging stage, then it'll be hard to track down the issue down the road!
This bug created a long delay in the project (about 2 weeks until we tracked down the bug in the data). So make sure to always check the both sides of the coin:
First, the data you're working with
Second, the code you wrote.
I have a form with 4 fields: title, price, image and category.
When submitted, it inserts the 3 strings into the DB and uploads the image, or returns errors. It all works fine.
However, now I want it to be done via ajax.
It works fine for the title, price and category fields, but how do I pass the info needed from the file field so that codeigniter can upload it?
Right now it keeps saying "You did not select a file to upload."
My controller:
function add_new_product_ajax()
{
// make sure it's the admin, else redirect
if ( ! $this->session->userdata('is_admin') ) {
redirect('admin/login');
exit();
}
// get product name
$pn = $this->input->post('product_title');
// get price
$p = $this->input->post('price');
// get category id
$cid = $this->input->post('categories');
// upload config
$config['upload_path'] = './uploads/';
$config['allowed_types'] = 'gif|jpg|png';
$config['max_size'] = '100';
$config['max_width'] = '1024';
$config['max_height'] = '768';
$config['overwrite'] = TRUE;
// load form validation
$this->load->library('form_validation');
// validate fields
$this->form_validation->set_rules('product_title', 'Product Name', 'trim|required|is_unique[products.name]');
$this->form_validation->set_rules('price', 'Price', 'trim|required|callback_check_price');
$this->form_validation->set_rules('categories', 'Product\'s Category', 'required');
// if validation failed
if ($this->form_validation->run($this) == FALSE)
{
// dummy var
$data['dummy'] = '';
// load categories module to make a select list
$this->load->module('categories/categories');
$data['options'] = $this->categories->categories_select();
// load the view
$this->load->view('new_product_view', $data);
}
else // validation passed
{
// do upload
$this->load->library('upload', $config);
$field_name = "userfile";
// $this->upload->do_upload($field_name);
// try to upload
if ( ! $this->upload->do_upload()) {
$data['error'] = $this->upload->display_errors();
// load categories module to make a select list
$this->load->module('categories/categories');
$data['options'] = $this->categories->categories_select();
// load the view
$this->load->view('new_product_view', $data);
}
else // upload successful, insert data into table
{
// insert product data
$this->mdl_products->add_product($pn, $p, $cid);
// success msg
$data['msg'] = '<div class=successmsg>The product has been added!</div>';
// load categories module to make a select list
$this->load->module('categories/categories');
$data['options'] = $this->categories->categories_select();
// load the view
$this->load->view('new_product_view', $data);
}
}
}
My ajax ( with missing file value parameter):
$('body').on('click', 'a.submitnewprod', function(e) {
e.preventDefault();
// alert('code');return;
// get product title
var pt = $('#product_title').val();
// get product price
var pp = $('#price').val();
// get category id
var cid = $('#categories').val();
// get userfile ???????
var uf = $('#uploadImage').val();
$.ajax({
type: "POST",
url: site + "admin/add-new-product-ajax",
data: {
product_title:pt,
price: pp,
categories: cid
},
beforeSend: function() {
$('#ajaximg img').addClass('act');
},
success: function(data) {
// $('.results').html(data);
$('#ajax').html(data);
},
complete: function() {
$('#ajaximg img').removeClass('act');
}
});
});
the file html from my view file:
<input id="uploadImage" type="file" name="userfile" size="20" onchange="PreviewImage();" />
What do I need to pass in my ajax call inside data parameter, so that the upload can work normally?
Its not an issue in your PHP its to do with how you are posting the $_FILE field. Instead of grabbing individual form fields try using the form serialize function of jQuery and declare the post as multipart/form-data. There is loads of examples of how to achieve this on SO but should look a bit like this...
var myData = $('#your_form').serialize();
$.ajax({
type: "POST",
contentType:attr( "enctype", "multipart/form-data" ),
url: " URL Goes Here ",
data: myData
...