I'm trying to add ajax to add to cart button on front page.
The setup is using Divi. Divi's woo product module does not display Add-to-cart button. I use the below to display add-to-cart button on front page. That works but the only issue is the Ajax is not working on front page. I've enabled "Enable AJAX add to basket buttons on archives" from Woocommerce settings.
add_action('template_redirect', 'work_only_on_front_page', 10);
function work_only_on_front_page(){
if ( is_front_page() ) {
add_action('woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_add_to_cart', 10);
}
}
Below works well on other pages other than the front page.
(function ($) {
$(document).on('click', '.single_add_to_cart_button', function (e) {
e.preventDefault();
var $thisbutton = $(this),
$form = $thisbutton.closest('form.cart'),
id = $thisbutton.val(),
product_qty = $form.find('input[name=quantity]').val() || 1,
product_id = $form.find('input[name=product_id]').val() || id,
variation_id = $form.find('input[name=variation_id]').val() || 0;
var data = {
action: 'woocommerce_ajax_add_to_cart',
product_id: product_id,
product_sku: '',
quantity: product_qty,
variation_id: variation_id,
};
$(document.body).trigger('adding_to_cart', [$thisbutton, data]);
$.ajax({
type: 'post',
url: wc_add_to_cart_params.ajax_url,
data: data,
beforeSend: function (response) {
$thisbutton.removeClass('added').addClass('loading');
},
complete: function (response) {
$thisbutton.addClass('added').removeClass('loading');
},
success: function (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, $thisbutton]);
}
},
});
return false;
});
})(jQuery);
function woocommerce_ajax_add_to_cart_js() {
if (is_product() || is_product_category() || is_shop() || is_front_page()) {
wp_enqueue_script('woocommerce-ajax-add-to-cart', get_stylesheet_directory_uri() . '/assets/js/ajax-add-to-cart.js', array('jquery'), '', true);
}
}
add_action('wp_enqueue_scripts', 'woocommerce_ajax_add_to_cart_js', 99);
add_action('wp_ajax_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');
add_action('wp_ajax_nopriv_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');
function woocommerce_ajax_add_to_cart() {
$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 = absint($_POST['variation_id']);
$passed_validation = apply_filters('woocommerce_add_to_cart_validation', true, $product_id, $quantity);
$product_status = get_post_status($product_id);
if ($passed_validation && WC()->cart->add_to_cart($product_id, $quantity, $variation_id) && 'publish' === $product_status) {
do_action('woocommerce_ajax_added_to_cart', $product_id);
if ('yes' === get_option('woocommerce_cart_redirect_after_add')) {
wc_add_to_cart_message(array($product_id => $quantity), true);
}
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));
echo wp_send_json($data);
}
wp_die();
}
Another thing to check is to ensure that the function woocommerce_ajax_add_to_cart_js() is being called and the script is being enqueued on the front page.
You can check the browser's console to see if there are any errors related to the Javascript code not being loaded.
Related
i like to give the clients an option to add more product in the woocommerce shopping cart widget.
so far i managed to find this code:
add_filter( 'woocommerce_widget_cart_item_quantity', 'add_minicart_quantity_fields', 10, 3 );
function add_minicart_quantity_fields( $html, $cart_item, $cart_item_key ) {
$product_price = apply_filters( 'woocommerce_cart_item_price', WC()->cart->get_product_price( $cart_item['data'] ), $cart_item, $cart_item_key );
return woocommerce_quantity_input( array('input_value' => $cart_item['quantity']), $cart_item['data'], false ) . $product_price;
}
the code make the quantity select visible in the shopping cart.
but the issue is when we change the quantity, no changes take place...
If i understand correctly i should add a Ajax request to change the product's quantity when the user add or remove products.
I also wanted to add quantity buttons to the Woocommerce shopping cart widget. I achieved this by combining your code with a modified version of this answer: https://stackoverflow.com/a/69218525/4234891
We will send the quantity via AJAX, update the quantity in the backend, and send the cart subtotal back.
So first create the quanity buttons like you already did. In functions.php add:
add_filter('woocommerce_widget_cart_item_quantity', 'add_minicart_quantity_fields', 10, 3);
function add_minicart_quantity_fields($html, $cart_item, $cart_item_key)
{
$product_price = apply_filters('woocommerce_cart_item_price', WC()->cart->get_product_price($cart_item['data']), $cart_item, $cart_item_key);
return woocommerce_quantity_input(array('input_value' => $cart_item['quantity']), $cart_item['data'], false) . $product_price;
}
Next, register the JavaSript. In functions.php add:
add_action('wp_enqueue_scripts', function () {
wp_register_script('custom-cart-widget', get_stylesheet_directory_uri() . '/js/cart-widget.js', ['jquery'], '1.0', true);
wp_localize_script('custom-cart-widget', 'cart_widget_qty_ajax', ['ajax_url' => admin_url('admin-ajax.php')]);
wp_enqueue_script('custom-cart-widget');
});
Next, add the function which will be called via AJAX. In functions.php add:
function ajax_change_widget_cart_qty()
{
// Set item key as the hash found in input.qty's name
$cart_item_key = $_POST['hash'];
// Get the array of values owned by the product we're updating
$cart_item_values = WC()->cart->get_cart_item($cart_item_key);
// Get the quantity of the item in the cart
$product_quantity = apply_filters('woocommerce_stock_amount_cart_item', apply_filters('woocommerce_stock_amount', preg_replace("/[^0-9\.]/", '', filter_var($_POST['quantity'], FILTER_SANITIZE_NUMBER_INT))), $cart_item_key);
// Update cart validation
$passed_validation = apply_filters('woocommerce_update_cart_validation', true, $cart_item_key, $cart_item_values, $product_quantity);
// Update the quantity of the item in the cart
if ($passed_validation) {
WC()->cart->set_quantity($cart_item_key, $product_quantity, true);
}
wp_send_json_success(['subtotal_html' => WC()->cart->get_cart_subtotal()]);
}
add_action('wp_ajax_change_widget_cart_qty', 'ajax_change_widget_cart_qty');
add_action('wp_ajax_nopriv_change_widget_cart_qty', 'ajax_change_widget_cart_qty');
Last, create a file in your_theme/js/cart-widget.js and add:
jQuery(document).ready(function($) {
jQuery(document).on('change', '.woocommerce.widget_shopping_cart input.qty', function(){
var value = $(this).val();
if (!isNaN(value) && value > 0) {
var holder = $(this).closest('li');
if (holder.length) {
var removeButton = $(holder).find('.remove_from_cart_button');
if (removeButton.length) {
var itemHash = removeButton.attr('data-cart_item_key');
if (itemHash) {
holder.block({
message: null,
overlayCSS: {
opacity: 0.6
}
});
$.ajax({
type: 'POST',
dataType: 'json',
url: cart_widget_qty_ajax.ajax_url,
data: {
action: 'change_widget_cart_qty',
hash: itemHash,
quantity: value,
},
success: function(response) {
if (response.data.subtotal_html) {
var miniCart = $(holder).closest('div.widget_shopping_cart_content');
if (miniCart.length) {
var subTotalHolder = $(miniCart).find('.woocommerce-mini-cart__total .woocommerce-Price-amount.amount');
if (subTotalHolder) {
subTotalHolder.replaceWith(response.data.subtotal_html);
}
}
}
},
complete: function() {
holder.css( 'opacity', '1' ).unblock();
},
});
}
}
}
}
});
});
I am using a vtiger Crm system and I would like to use the pop up notification to display my own messages. I wrote an event handler that triggers after model save, in this handler I would like to call the notification box with my own message.
Here is a working example that is from the 'Products' module handlers, this code was pre-written, it checks if there were duplicate item numbers and shows a message 'LBL_DUPLICATE_item_number' in the pop-up box
class Products_DuplicateItemNumber_Handler
{
/**
* EditViewPreSave handler function.
*
* #param App\EventHandler $eventHandler
*/
public function editViewPreSave(App\EventHandler $eventHandler)
{
$recordModel = $eventHandler->getRecordModel();
$response = ['result' => true];
$fieldModel = $recordModel->getModule()->getFieldByName('item_number');
if ($fieldModel->isViewable() && ($item_number = $recordModel->get('item_number'))) {
$queryGenerator = new \App\QueryGenerator($recordModel->getModuleName());
$queryGenerator->setStateCondition('All');
$queryGenerator->setFields(['id'])->permissions = false;
$queryGenerator->addCondition($fieldModel->getName(), $item_number, 'e');
if ($recordModel->getId()) {
$queryGenerator->addCondition('id', $recordModel->getId(), 'n');
}
if ($queryGenerator->createQuery()->exists()) {
$response = [
'result' => false,
'hoverField' => 'item_number',
'message' => App\Language::translate('LBL_DUPLICATE_item_number', $recordModel->getModuleName())
];
}
}
return $response;
}
}
However, when i try to return $respone in 'editViewPreSave' of another module, nothing happens.
After some digging around i found out that the system uses 'Pnotify' library to show the pop up message, and i belive it's being called in a js file called 'edit.js' in this path 'public_html/layouts/basic/modules/Vtiger/resources'
preSaveValidation: function (form) {
const aDeferred = $.Deferred();
if (form.find('#preSaveValidation').val()) {
document.progressLoader = $.progressIndicator({
message: app.vtranslate('JS_SAVE_LOADER_INFO'),
position: 'html',
blockInfo: {
enabled: true
}
});
let formData = new FormData(form[0]);
formData.append('mode', 'preSaveValidation');
AppConnector.request({
async: false,
url: 'index.php',
type: 'POST',
data: formData,
processData: false,
contentType: false
})
.done((data) => {
document.progressLoader.progressIndicator({ mode: 'hide' });
let response = data.result;
for (let i = 0; i < response.length; i++) {
if (response[i].result !== true) {
app.showNotify({
text: response[i].message ? response[i].message : app.vtranslate('JS_ERROR'),
type: 'error'
});
if (response[i].hoverField != undefined) {
form.find('[name="' + response[i].hoverField + '"]').focus();
}
}
}
aDeferred.resolve(data.result.length <= 0);
})
.fail((textStatus, errorThrown) => {
document.progressLoader.progressIndicator({ mode: 'hide' });
app.showNotify({
text: app.vtranslate('JS_ERROR'),
type: 'error'
});
app.errorLog(textStatus, errorThrown);
aDeferred.resolve(false);
});
} else {
aDeferred.resolve(true);
}
return aDeferred.promise();
},
I believe that 'app.showNotify' is the function called to display the pop-up box, yet i'm not sure how to replicate the code for my own use, i would like to know the best approach to do this
At the moment I use two php files for my AJAX requests. One file holds the functions, the other file contains a switch function that determines which funtion should be fired. This works fine but recently I looked into a project that used php classes. The most interesting part I found was the fact that within the ajax calls, the url was set directly to the class and function.
The code is as follow:
class Score extends CI_Controller {
const INDIVIDUAL_SCORE_RANGE_SIZE = 7;
function __construct()
{
parent::__construct();
$this->load->model('benchmarkscore','',TRUE);
$this->load->model('aiinst','',TRUE);
$this->load->helper('score');
$this->load->helper('info');
}
function index()
{
show_404('score', false);
}
function accountingratio() {
if($this->session->userdata('logged_in'))
{
$session_data = $this->session->userdata('logged_in');
$ratio = strtolower($this->input->post('ratio'));
$filter = strtolower($this->input->post('filter'));
if(in_array($ratio, array('roi', 'rr', 'qr', 'cr', 'sr', 'vr'))) {
$ai_inst = $this->aiinst->findById($session_data['ICIOM1ID']);
$myScore = $this->benchmarkscore->getMyScoreByAccountingRatio($ratio, $session_data['ICIOM1ID']);
$avgScore = $this->benchmarkscore->getAverageScoreOfOthersByAccountingRatio($ratio, $session_data['ICIOM1ID'], $filter, $ai_inst);
$allScores = $this->benchmarkscore->getAllScoresByAccountingRatio($ratio, $session_data['ICIOM1ID'], $filter, $ai_inst);
// We should only show 20 scores so we have to filter
// some data.
$filterDescription = getFilterDescription(count($allScores), $filter, $ai_inst);
$filteredScores = filterRelatedScores($allScores, $ai_inst['ICIOM1ID'], Score::INDIVIDUAL_SCORE_RANGE_SIZE);
$deviation = getBenchmarkDeviation($myScore['myscore'], $avgScore['avgscore']);
$deviation = $deviation == -1 ? 'N/A' : $deviation.'%';
$result = createResultArray($myScore['myscore'], $avgScore['avgscore'], $filteredScores,
"Uw score: ".number_format($myScore['myscore'], 2, ',', '.')."\nBenchmark afwijking: ".$deviation,
wrapText("Benchmark score: ".number_format($avgScore['avgscore'], 2, ',', '.')."\n\n" .
"test".$filterDescription,35),
$filterDescription);
$this->output->set_content_type('application/json');
$this->output->set_output(json_encode($result));
}
}
}
}
The ajax call:
function getAccountingRatioData(type, filter, callback) {
showLoader();
setTimeout(function() {
var jqxhr = $.ajax({
type: 'POST',
url: '/score/accountingratio',
data: {
ratio: type,
filter: filter
},
dataType: 'json'
}).done(function(data) {
hideLoader();
if(data == null) {
bootbox.alert("error");
} else {
setTopChartDataProvider(data.benchmark);
setBottomChartDataProvider(data.allscores, "test");
setFilterInfo(data);
}
if(callback)
callback.call();
})
.fail(function() { hideLoader();bootbox.alert("error"); });
}, 500);
}
I was wondering how this is working because I learned that classes it self don't actually do anything, they just hold things and you should alwas create an object in order to "fire" the class..
Any thoughts?
I am developing a website where I use a custom build jQuery widget to load data into multiple divs.
This is the code for the widget:
(function ($, window, document, undefined) {
$.widget ("my.contentloader", {
options: {
loading_message: true
},
_create: function () {
var that = this;
$.ajax ({
type: "POST",
url: that.options.url,
data: {data: that.options.formdata, limit: that.options.limit, offset: that.options.offset},
beforeSend: function (html) {
if (that.options.loading_message) {
$(that.options.target_area).html ("<div id='loading'>Loading</div>");
}
},
success: function (html) {
if (that.options.loading_message) {
$('#loading').remove ();
}
$(that.options.target_area).html (html);
},
error: function (html) {
$(that.options.error_area).html (html);
}
});
},
_setOption: function (key, value) {
this.options[key] = value;
$.Widget.prototype._setOption.apply (this, arguments);
}
});
})(jQuery, window, document);
I load data using the widget like this:
$('#targetdiv').contentloader ({
url: '<?php echo $action_url; ?>',
target_area: '#popup_box',
formdata: {'username' : username_email, 'password' : password}
});
I am having problems loading multiple instances on the same page.
Is there a way to not instantiate the widget on a specific div like this?
$('#targetdiv').contentloader
I think you need to assign each instance to a variable. That way, you can control each instance, or write a function that iterates over an array of instances.
var contentLoaders = [];
$('.target-div').each(function(i, data) {
contentLoaders[i] = $.widget("my.contentloader", { ... });
});
So then you should be able to operate on each loader independently, like:
for (var i in contentLoaders) {
var contentLoader = contentLoaders[i];
contentLoader.option( ... );
}
Also, you're using the DOM ID $('#loading') for multiple instances of the widget. This is wrong. You need to either use separate loaders for each widget, or else check to see if the ID exists and only insert the new node if it doesn't exist. And same for removing it.
** I've added this example block, hope it helps: **
//
// This is a way to do it if you want to explicitly define each contentloader.
// Below that, I'll write out a way to define the contentloaders in a loop.
//
var contentLoader1 = $('#targetdiv1').contentloader ({
url: '<?php echo $action_url; ?>',
target_area: '#popup_box',
formdata: {'username' : username_email, 'password' : password}
});
contentLoader1.option('url', 'http://google.com');
var contentLoader2 = $('#targetdiv2').contentloader ({
url: '<?php echo $action_url; ?>',
target_area: '#popup_box',
formdata: {'username' : username_email, 'password' : password}
});
contentLoader2.option('url', 'http:/apple.com');
// Push each widget instance into an array of widget objects
var contentLoaders = [];
contentLoaders.push(contentLoader1);
contentLoaders.push(contentLoader2);
for (var i in contentLoaders) {
console.log(i, contentLoaders[i].option('url'));
}
// Should print:
// 0 http://google.com
// 1 http://apple.com
//
//
// How to set a bunch of widgets at once from an array of content loader data
//
//
var contentLoaderData = [
{
divid: '#targetDiv1',
url: 'google.com',
formdata: {
username: 'joeshmo',
password: 'joeshmo1'
}
},
{
divid: '#targetDiv2',
url: 'apple.com',
formdata: {
username: 'plainjane',
password: 'plainjane1'
}
}
];
// Array of widget instances
var contentLoaders = [];
$.each(contentLoaderData, function(index, value) {
var contentLoader = $(this.divid).contentloader({
url: this.url,
target_area: '#popup_box',
formdata: {'username' : this.formdata.username, 'password' : this.formdata.password}
});
// Push each contentLoader instance into the contentLoaders array
contentLoaders.push(contentLoader);
});
for (var i in contentLoaders) {
console.log(i, contentLoaders[i].option('url'));
}
// Should print:
// 0 http://google.com
// 1 http://apple.com
I have developed below plug-in
(function($) {
$.fn.addressSearch = function(settings) {
settings = jQuery.extend({
searchClass: "quickSearch",
checkElement: "href",
dataElement: "data",
countryListClass: "countryList",
countryCode: "11455",
errorMsg: "You can only search for address in the UK.",
houseNumberClass: "TextboxHouseNumber",
postcodeClass: "postcode",
addressLine1Class: "addSearchLine1",
addressLine2Class: "addSearchLine2",
addressLine3Class: "addSearchLine3",
addressTownCityClass: "addTownCity",
ajaxUrl: "/WebService/addressLook",
submitType: "POST",
dataType: "xml",
parameters: "",
addressProcessURL: "",
callbackFunctionSingleAddress: selectAddress, //Callback 1
callbackFunctionMultipleAddress: quickBoxSearch, //Callback 2
useExternalProcessPage: false,
validateCountry: true
}, settings);
var jQueryMatchedObj = this;
function _initialize() {
_startModal(this, jQueryMatchedObj);
return false;
}
function _startModal(objClicked, jQueryMatchedObj) {
$j(objClicked).addClass(settings.searchClass);
var countryList = "." + settings.countryListClass + "";
if (settings.validateCountry) {
if ($j(countryList) && $j(countryList).val() != settings.countryCode) {
alert(settings.errorMsg);
return false;
}
}
if (settings.parameters) {
$j.ajax({
url: settings.ajaxUrl,
type: settings.submitType,
dataType: settings.dataType,
data: settings.parameters,
success: function(res) {
var addresses = eval(res.getElementsByTagName('string')[0].firstChild.data);
if (addresses.length == 0)
alert('Your address could not be found, please enter it manually');
else if (addresses.length == 1) {
//Callback 1 and parameters set here
settings.callbackFunctionSingleAddress(
addresses[0].addressLine1,
addresses[0].addressLine2,
addresses[0].addressLine3,
addresses[0].town,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
} else if (addresses.length > 1) {
//Callback 2 and parameters set here
settings.callbackFunctionMultipleAddress(
settings.callbackFunctionSingleAddress,
addresses,
settings.useExternalProcessPage,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
}
}
});
}
return false;
}
return this.unbind('click').click(_initialize);
}
})(jQuery);
Above works fine without any problem. I call this with code below
$('#mydiv').click(function() {
$(this).addressSearch(/* ... */);
});
However now I want to extend this even further with the passing both callback functions and parameters in the settings for the plugging so the plugging will be more robust.
how do I do this, basically I want to pass
settings.callbackFunctionSingleAddress(
addresses[0].addressLine1,
addresses[0].addressLine2,
addresses[0].addressLine3,
addresses[0].town,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
AND
settings.callbackFunctionMultipleAddress(
settings.callbackFunctionSingleAddress,
addresses,
settings.useExternalProcessPage,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
as parameters on the click event of a div. So it would look like,
$('#mydiv').click(function() {
$(this).addressSearch({
callbackFunctionSingleAddress: callbackFuntion(param1, param2)
});
});
Above is the idea. Is this possible? Please help
If I'm reading this right, all you need to do is wrap the callbackFunction in a function block:
$('#mydiv').click(function() {
$(this).addressSearch({
callbackFunctionSingleAddress: function() { callbackFuntion(param1, param2); }
});
});