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.
Hi I successfully designed a chat message system between users and it works fine. The only problem I am having at this point is polling data from chat table to browser real time.
The messages are displayed using ajax and the setInterval works as I checked the console. The issue is that it does not capture new entries to the table for display and hence the user has to keep refreshing the page to see new content.
Please help. My code is below. Please forgive my file naming convention as I will change it later on.
PS this is developed using codeigniter framework.
**Chats.php - Controller**
public function ajax_get_chat_messages(){
echo $this->_get_chat_messages();
}
function _get_chat_messages($recipient = null)
{
$user_id = $this->session->userdata('user_id');
$recipient = $this->input->post('recipient');
$data['recipient'] = $this->User_model->get_users($user_id);
$data['chats_count'] = $this->Chats_model->get_chat_messages_count($recipient);
$content = $this->Chats_model->get_chat_messages_count($recipient);
$data['chats'] = $this->Chats_model->get_chat_messages($user_id);
$result = array('status' =>'ok', 'content'=>$content);
return json_encode($result);
}
**Model - Chats_model.php**
public function get_chat_messages_count($recipient = null){
$session = $this->session->userdata('user_id');
$this->db->select('*');
$this->db->from('chat_messages');
$this->db->join('users', 'users.user_id = chat_messages.user_id');
$this->db->where(array('chat_messages.user_id' => $session));
$this->db->where(array('chat_messages.recipient' => $recipient));
$this->db->or_where(array('chat_messages.user_id' => $recipient));
$this->db->where(array('chat_messages.recipient' => $session));
$this->db->where_in(array('chat_messages.chat_id' => $session , $recipient));
$query = $this->db->get();
return $query->result_array();
}
**View - chats_view.php**
<script type="text/javascript">
var user = "<div class='timeline-item' id='view'><ul><?php foreach($chats_count as $chat){echo '<li>'; echo $chat['username']; echo '</li>'; }?></ul></div>";
</script>
<div class="wrapper wrapper-content">
<div class="row animated fadeInRight">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>Chat</h5>
<div class="ibox-tools" >
</div>
</div>
<div class="ibox-content inspinia-timeline" id="view1" >
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
**JS - chat2.js**
$(document).ready(function(){
setInterval(function() { get_chat_messages(); }, 2500)
$("input#chat_message").keypress(function(e){
if(e.which == 13){
$("a#submit_message").click();
return false;
}
});
function get_chat_messages(){
$.post(base_url +"user/chats/ajax_get_chat_messages",{user: user}, function(data) {
if(data)
{
var current_content = $("#view1").html();
$("#view1").html(user);
console.log(user);
}
else
{
}, "json");
}
get_chat_messages();
});
Images attached showing the table structure, the chat page on browser and console data.
[Chat page on browser, only showing username for testing purposes][1]
[Chat Table][2]
[Console Data, only showing username for testing purposes][3]
Here is my Model:
function get_search() {
$match = $this->input->post('search');
$this->db->like('p_category',$match);
$this->db->or_like('p_place',$match);
$this->db->or_like('p_price',$match);
$query = $this->db->get('tbl_property');
if($query->num_rows() > 0)
return $query->result();
else
return FALSE;
}
Here is my View:
<?php
foreach($query as $pack_details):
?>
<table class="table table-hover table-bordered">
<?php echo $pack_details->p_title?>
</table>
<?php
endforeach;?>
want to search and will show result only which i searched.
This is an old one of mine, but you may be able to adapt it to yours. Your form sends the post to the controller. The variable $art is the search item. The variable $num is the number of results. The jquery sends the form to the article class search_results
Controller
public function seek()
{
// this is the artist search display
$art = html_escape(trim($this->input->post('art')));
$this->db->like('artist', $art);
$this->db->select('artist, song, album, mix_name');
$query = $this->db->get('podcasts');
$num = $query->num_rows();
echo "<h3>We found $num $art song's</h3>";
if($query->num_rows() > 0) {
foreach ($query->result() as $row) {
echo "<li class='search_list'>Song: $row->song <br> Album: $row->album <br> Podcast: $row->mix_name</li> ";
}
}else {
echo "You searched for $art, Please check your spelling, or ensure the artists name is complete";
}
}
Jquery if you want the search to show up on the same page
$(function () {
"use strict";
$("#search").submit(function () {
var data = $('#search').serialize();
//alert(data); return false;
$.ajax({
url: "/display/seek",
data: data,
type: "POST",
success: function (msg) {
$('article.search_results').html(msg).show();
}
});
return false;
});
});
View is simply. See the last line in the jquery
<article class="search_results"></article>
have a form like this. This is an update form, i just need to update qty foreach product in cart.
But i have tried to explode each results and not work... it return object2array conversion error... its the first time that i get this error How i can save this in DB?
<form action="update">
#foreach($products as $product)
<input type="text" name="products[]">
<input type="text" name="qty[]">
#endforeach
<input type="submit" calss="btn btn-primary">
This is my controller:
Route::post('aggiorna', function() {
$quantita = Input::all();
$carrelli = \App\Models\Carrello::where('entry_by', \Session::get('uid'))->get();
$quantita = explode(',', $quantita);
$i = 0;
foreach($carrelli as $carrello) {
$carrello->quantita = $quantita[$i];
$i = $i++;
}
return Redirect::to('carrello');
});
Thanks in advance.
you do the iteration for each $carreli that exist on your database, however you never save the value.
Route::post('aggiorna', function() {
$quantita = Input::all();
$carrelli = \App\Models\Carrello::where('entry_by', \Session::get('uid'))->get();
$quantita = explode(',', $quantita);
$i = 0;
foreach($carrelli as $carrello) {
$carrello->quantita = $quantita[$i];
$i = $i++;
$carrelo->save();
}
return Redirect::to('carrello');
});
add the $carrelo->save(); after you update the value in order to save it on db.
Also careful when you use Input::all();. That means that your data array contains both products and quantities. I would suggest using the following code:
Route::post('aggiorna', function() {
$quantita = Input::get('qty');
$products = Input::get('products');
$carrelli = \App\Models\Carrello::where('entry_by', \Session::get('uid'))->get();
$quantita = explode(',', $quantita);
$products = explode(',', $products);
$i = 0;
foreach($carrelli as $carrello) {
$carrello->quantita = $quantita[$i];
$carrello->products = $products[$i];
$i = $i++;
$carrelo->save();
}
return Redirect::to('carrello');
});
However since i do not know what you are trying to achieve, I posted both solutions here.
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
...