Jump to content

Cart reloads page


ank
 Share

Recommended Posts

Hello,

when i add or update the cart, the page reloads to the overview grid/productpage (although the cart is updated), even when i add a product from a single product page.
I use the example code:

// get the $cartRender class
$cartRender = $padloper->cartRender;
// get the product whose add to cart button you want to be rendered
// @note: it is assumed you already retrieved $product as per the example linked to above
// render the add product to cart form
$content = $cartRender->addToCart($product);
$out .= $content;

Is there a way to update the cart without reloading the page, only add or update the cart.

 

Link to comment
Share on other sites

Hi @ank,

Padloper can handle both ajax (no page reload) and non-ajax (page reload) requests. Please see the example projects in this repo:

https://github.com/kongondo/Padloper2Starter

With respect to ajax, Vanilla JS and jQuery examples are not complete. However, the htmx (Demo 1, etc.) examples are complete. Please have a look and let me know how you get on. I am planning to add some simpler examples some time soon.

Thanks.

Link to comment
Share on other sites

Hi @ank,

Apologies for the late response.

Below are examples of 'add to cart' buttons without page reload. Ajax is used to submit the request and handle the response from the server. There are two examples: 

  1. jQuery ajax
  2. Vanilla JavaScript ajax

In both cases, the server (Padloper) returns a JSON response. This is unlike in htmx in which case the server will return markup as the response. Hence, in the examples below, we need to handle the response ourselves. I have given some examples but you can choose to handle the response however you wish. The most important thing is the submission aspect of the code, i.e. what form inputs you send to the server. I'll explain these below before I get to the examples. In future, I will add the examples below to the demos repository. I will also be adding the explanations below to the docs.

Add to Cart Endpoint

You need to send a POST request to the address /padloper/add/. Padloper listens to requests sent there using URL Hooks.

INPUTS

Required

Product ID (integer)

Your request must contain the ID of the product you want to add to the cart. Please note that this applies to both a product without variants or a variant. I.e., in case of a variant, you only need to send its ID. Its ID will be its product ID. 

The input name must be: product_id

Optional

Quantity (integer)

This tells Padloper how many of the given product/product variant to add to the cart. If this is not specified, it defaults to 1 item.

The input name must be: padloper_cart_add_product_quantity

Redirect Page (string | integer)

This is only applicable for non-ajax requests! This tells Padloper where to redirect the user to after they add an item to the cart. It is useful in cases such as 'add to cart and checkout'. The value can be a string representing the URL to redirect to or a page ID whose URL to redirect to.

The input name must be: padloper_cart_redirect

EXAMPLES

Notes

  1. These examples assume you are using a delayed output template strategy. Otherwise, if using a direct output you just need to echo $content. 
  2. Please also note that form markup can be added to any page. You don't need a special page to add an item to the cart.
  3. Padloper products pages live under the admin hence cannot be accessed directly from the frontend. One way to show a product in the frontend is to access it via a URL Segment, e.g. /products/my-nice-product/ OR /products/1234/ where 1234 is the ID of the product. You can then use $padloper->get("id=1234") OR $padloper->get("name=my-nice-product, template=product").
  4. We are using Tailwind CSS in our examples.
  5. For the jQuery example, you will need to link to jQuery script somewhere in your <head> or <body>.
  6. In this examples, we don't show how to use the values in the JSON in the server response. They are self-explanatory though. If not, let me know.
  7. The JavaScript handlers code was not thoroughly tested. They are intended as a quick illustration.

jQuery Example

Template File (PHP)

Spoiler
<?php

namespace ProcessWire;


// ++++++++++++++
# FOR DEMO/TESTING jQUERY AJAX 'ADD TO CART', i.e. non-htmx demos
# @NOTE: YOU NEED TO ADD SOME CHECKS TO YOUR CODE; e.g. ensure the product exists, etc
// ++++++++++++++

// @NOTE: hardcoded PRODUCT ID for testing only! Get programmatically in real case
$testProductID = 1895;
// fetch the product page
$testProduct = $padloper->get("id={$testProductID}");

// get the first image for this product
$testProductImage = $testProduct->padloper_images->first();
// create a thumb for the image
$testProductThumb = $testProductImage->height(130);

// get the stock info for this product
$stock = $testProduct->padloper_product_stock;
// get the price of the product formatted as a currency
$price = $padloper->getValueFormattedAsCurrencyForShop($stock->price);

// FOR TESTING NON-AJAX ONLY! FOR REDIRECT BACK HERE
// $testProductName = $sanitizer->pageNameTranslate($testProduct->getUnformatted('title'));
// $testProductURL = "/products/{$testProductName}/";

##################

// Primary content is the page's body copy
$content = $page->body;

// FOR TESTING JQUERY AJAX ADD TO CART
$content .= "<div>" .
	"<h3>TEST JQUERY AJAX ADD TO CART</h3>" .
	"<hr class='my-3'>" .

	"<div id='padloper_add_single_product' class='my-8'>" .
	"<div class='container mx-auto px-6'>" .
	"<div>" .
	"<div>" .
	"<h2 class='text-gray-900 text-3xl title-font font-medium mb-1'>{$testProduct->title}</h2>" .
	"<img class='rounded'" .
	"src='{$testProductThumb->url}' alt='{$testProduct->title}'>" .
	"<div class='lg:w-1/2 w-full lg:pl-10 lg:py-6 mt-6 lg:mt-0'>" .

	// 					<!-- DESCRIPTION -->
	"<p class='leading-relaxed'></p>" .
	$testProduct->padloper_description .

	//					<!-- PRICE -->
	"<div id='padloper_2_demo_product_add_to_cart_wrapper' class='flex mt-4'>" .
	// price
	"<span class='title-font font-medium text-xl text-gray-900'>{$price}</span>" .
	// *** jQUERY AJAX FORM ***
	"<form method='post' action='{$config->urls->root}padloper/add/' id='padloper-cart-add-product-other-ajax-jquery' class='padloper-cart-add-product flex ml-auto'>" .
	// product id hidden input
	"<input type='hidden' name='product_id' value='{$testProduct->id}'>" .
	// csrf hidden input
	$session->CSRF->renderInput() .
	# ++++++++++++
	// FOR TESTING NON-AJAX ONLY! FOR REDIRECT BACK HERE
	// "<input type='hidden' name='padloper_cart_redirect' value='{$testProductURL}'>" .	
	// "<input type='hidden' name='padloper_cart_redirect' value='{$page->id}'>" .
  	# ++++++++++++
	// add to cart button
	"<button class='text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded'>Add to Cart (jQuery)</button>" .
	"</form>" .
	"</div>" .
	"</div>" .
	"" .
	"</div>" .
	"</div>" .
	// success/error message	
	// JQUERY
	"<span id='other-ajax-add-to-cart-success-jquery' class='font-medium text-lg bg-gray-700 text-white'></span>" .
	"<span id='other-ajax-add-to-cart-error-jquery'class='font-medium text-lg bg-white-700 text-red-300'></span>" .
	//		<!-- div.container -->
	"</div>" .
	//	<!-- div#padloper_add_single_product -->
	"</div>";

 

JavaScript

Spoiler
/**
 * DOM ready
 *
 */
document.addEventListener("DOMContentLoaded", function (event) {
	console.log("TEST/DEMO: OTHER AJAX ADD TO CART DEMO")
	jqueryAddToCart()
})

// ------------------
function jqueryAddToCart() {
	// jQuery example of how to make add to cart buttons ajaxified
	$("#padloper-cart-add-product-other-ajax-jquery").submit(function (event) {
		// console.log(event)
		event.preventDefault()
		const $form = $(this)
		const url = $form.attr("action")

		// Send the data using post
		const posting = $.post(url, $form.serialize())

		posting.done(function (data) {
			if (data.errors) {
				let str = ""
				$.each(data.errors, function (i, val) {
					str = str + val
				})
				$("#other-ajax-add-to-cart-success-jquery").text(str)
				$("#other-ajax-add-to-cart-success-jquery").fadeOut(6000)
			} else {
				// $("#totalQty").html(data.totalQty)
				// $("#numberOfTitles").html(data.numberOfTitles)
				// $("#totalAmount").html(data.totalAmount)
				$("#other-ajax-add-to-cart-success-jquery").text(
					"Product successfully added to basket"
				)
				// $("#other-ajax-add-to-cart-success").addClass("py-3 px-2")
				$("#other-ajax-add-to-cart-success-jquery").fadeOut(6000)
			}
		})
	})
}

 

Vanilla JavaScript Example

Template File (PHP)

Spoiler
<?php

namespace ProcessWire;


// ++++++++++++++
# FOR DEMO/TESTING VANILLA JAVASCRIPT AJAX 'ADD TO CART', i.e. non-htmx demos
# @NOTE: YOU NEED TO ADD SOME CHECKS TO YOUR CODE; e.g. ensure the product exists, etc
// ++++++++++++++

// @NOTE: hardcoded PRODUCT ID for testing only! Get programmatically in real case
$testProductID = 1895;
// fetch the product page
$testProduct = $padloper->get("id={$testProductID}");

// get the first image for this product
$testProductImage = $testProduct->padloper_images->first();
// create a thumb for the image
$testProductThumb = $testProductImage->height(130);

// get the stock info for this product
$stock = $testProduct->padloper_product_stock;
// get the price of the product formatted as a currency
$price = $padloper->getValueFormattedAsCurrencyForShop($stock->price);

// FOR TESTING NON-AJAX ONLY! FOR REDIRECT BACK HERE
// $testProductName = $sanitizer->pageNameTranslate($testProduct->getUnformatted('title'));
// $testProductURL = "/products/{$testProductName}/";


##################

// Primary content is the page's body copy
$content = $page->body;

// FOR TESTING VANILLA JAVASCRIPT AJAX ADD TO CART
$content .= "<div>" .
	"<h3>TEST VANILLA JAVASCRIPT AJAX ADD TO CART</h3>" .
	"<hr class='my-3'>" .

	"<div id='padloper_add_single_product' class='my-8'>" .
	"<div class='container mx-auto px-6'>" .
	"<div>" .
	"<div>" .
	"<h2 class='text-gray-900 text-3xl title-font font-medium mb-1'>{$testProduct->title}</h2>" .
	"<img class='rounded'" .
	"src='{$testProductThumb->url}' alt='{$testProduct->title}'>" .
	"<div class='lg:w-1/2 w-full lg:pl-10 lg:py-6 mt-6 lg:mt-0'>" .

	// 					<!-- DESCRIPTION -->
	"<p class='leading-relaxed'></p>" .
	$testProduct->padloper_description .

	//					<!-- PRICE -->
	"<div id='padloper_2_demo_product_add_to_cart_wrapper' class='flex mt-4'>" .
	// price
	"<span class='title-font font-medium text-xl text-gray-900'>{$price}</span>" .
	// *** VANILLA JAVASCRIPT AJAX BUTTON ***
	"<form method='post' action='{$config->urls->root}padloper/add/' id='padloper-cart-add-product-other-ajax-vanilla' class='padloper-cart-add-product flex ml-auto'>" .
	// product id hidden input
	"<input type='hidden' name='product_id' value='{$testProduct->id}'>" .
	// csrf hidden input
	$session->CSRF->renderInput() .
	# ++++++++++++
	// FOR TESTING NON-AJAX ONLY! FOR REDIRECT BACK HERE
	// "<input type='hidden' name='padloper_cart_redirect' value='{$testProductURL}'>" .	
	// "<input type='hidden' name='padloper_cart_redirect' value='{$page->id}'>" .
	# ++++++++++++
	// add to cart button
	"<button id='padloper-cart-add-product-other-ajax-vanilla-button' class='text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded' type='button'>Add to Cart (Vanilla JS)</button>" .
	"</form>" .
	"</div>" .
	"</div>" .
	"" .
	"</div>" .
	"</div>" .
	// success/error message
	// VANILLA JS
	"<span id='other-ajax-add-to-cart-success-vanilla' class='font-medium text-lg bg-gray-700 text-white'></span>" .
	"<span id='other-ajax-add-to-cart-error-vanilla'class='font-medium text-lg bg-white-700 text-red-300'></span>" .
	//		<!-- div.container -->
	"</div>" .
	//	<!-- div#padloper_add_single_product -->
	"</div>";

 

JavaScript

Spoiler
/**
 * DOM ready
 *
 */
document.addEventListener("DOMContentLoaded", function (event) {
	console.log("TEST/DEMO: OTHER AJAX ADD TO CART DEMO")
	handleVanillaJavaScriptAddToCart()
})

// ------------------


function handleVanillaJavaScriptAddToCart() {
	const vanillaJavaScriptAddToCartButtonElement = document.getElementById(
		"padloper-cart-add-product-other-ajax-vanilla-button"
	)
	if (vanillaJavaScriptAddToCartButtonElement) {
		// add event listener to autocomplete shipping country
		vanillaJavaScriptAddToCartButtonElement.addEventListener(
			"click",
			vanillaJavaScriptAddToCart,
			false
		)
	}
}

async function vanillaJavaScriptAddToCart() {
	const vanillaJavaScriptAddToCartFormElement = document.getElementById(
		"padloper-cart-add-product-other-ajax-vanilla"
	)
	// -----
	if (!vanillaJavaScriptAddToCartFormElement) {
		return
	}

	// GOOD TO GO
	const addToCartURL = vanillaJavaScriptAddToCartFormElement.action
	const data = new FormData(vanillaJavaScriptAddToCartFormElement)

	try {
		const response = await fetch(addToCartURL, {
			method: "POST",
			// Set the headers
			headers: {
				Accept: "application/json",
				"X-Requested-With": "XMLHttpRequest",
			},
			// Set the post data
			body: data,
		})
		if (!response.ok) {
			throw new Error("Network response was not OK")
		}

		const json = await response.json()
		console.log(
			"TEST/DEMO: OTHER AJAX ADD TO CART DEMO vanillaJavaScriptAddToCart - json",
			json
		)
		const successElement = document.getElementById(
			"other-ajax-add-to-cart-success-vanilla"
		)

		successElement.innerHTML = "Product successfully added to basket"
		toggleFade(successElement)
	} catch (error) {
		console.error("There has been a problem with your fetch operation:", error)
		const errorElement = document.getElementById("other-ajax-add-to-cart-error")

		errorElement.innerHTML =
			"There was an error: Could not add product to basket"
		toggleFade(errorElement)
	}
}

function toggleFade(element, duration = 600) {
	element.style.display = ""
	element.style.opacity = 0
	let last = +new Date()
	const tick = function () {
		element.style.opacity =
			+element.style.opacity + (new Date() - last) / duration
		last = +new Date()
		if (+element.style.opacity < 1) {
			;(window.requestAnimationFrame && requestAnimationFrame(tick)) ||
				setTimeout(tick, 16)
		} else {
			setTimeout(() => {
				fadeOut(element)
			}, 2500)
		}
	}
	tick()
}

function fadeOut(el) {
	setInterval(function () {
		var opacity = el.style.opacity
		if (opacity > 0) {
			opacity -= 0.1
			el.style.opacity = opacity
		}
	}, 50)
}

 

Server Response

{
    "productId": 1895,
    "variationId": 0,
    "productTitle": "Printed Ghanain Skirt",
    "quantity": 1,
    "totalQty": 85,
    "numberOfTitles": 5,
    "totalAmount": "\u00a313,425.45"
}

Please note that the variationId is a legacy response value that is no longer needed in Padloper. It will be removed in the next release.

Screenshots

Please note that in my testing, I combined the code for jQuery and Vanilla JavaScript into one template file.

Demo Product Page

jquery_vanilla_js_add_to_cart_ajax_demo.thumb.png.eaacf8bad447d24d752688129c3b8353.png

Dev Console: Ajax Request

jquery_vanilla_js_add_to_cart_ajax_demo_ajax_request.thumb.png.71be1f54fe1afb5a43e1697b8d1fb35b.png

Dev Console: Ajax Response

Hope this helps. Otherwise, give us a shout.

Cheers

jquery_vanilla_js_add_to_cart_ajax_demo_ajax_response.png

Link to comment
Share on other sites

  • 2 weeks later...

Sorry for the late response, but are learning from the examples. I am also trying to make a sidecart with this examples but cannot refresh the cart when clicking on "add to cart" button.

I can update "$padloper->cart->getNumberOfTitles()" without page reload, but how to do it with a complete render of the cart ?

Is there a place where i can find all functions of padloper like the total quantity of items in the cart (not quantity of products in cart) ?

Thanks

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...