Jump to content

Sélection et ajout multiple au panier de déclinaisons sur une fiche produit


Recommended Posts

Bonjour, je travaille actuellement sur un site sous Prestashop 1.7.8.8, sur lequel j'aimerais faire en sorte que sur toute fiche produit comportant des déclinaisons, l'utilisateur puisse sélectionner plusieurs déclinaisons ainsi que leur quantité, et que le tout s'ajoute au panier lors du clic sur le bouton "Ajouter au panier".

J'ai trouvé ce tutoriel dont la majorité semble être fonctionnelle, mais il me reste quelques soucis quant à la sélection multiple ainsi qu'à l'ajout au panier. Voici à quoi ressemble actuellement ma page de produit :

image.thumb.png.19ef30ed51498b7b932a1494a1a10e91.png

Je ne peux toujours pas sélectionner plusieurs produits (par conséquent, je ne peux pas non plus entrer plusieurs quantités) et lorsque j'essaie de réaliser l'une de ces deux actions, un refresh Ajax réinitialise toutes les entrées. Au niveau de l'ajout au panier, je pense que mon problème vient également de l'impossibilité de sélection multiple : peu importe pour quelle déclinaison j'entre une quantité, disons 20, cela m'ajoutera bien 20 entrées dans le panier, mais avec la déclinaison par défaut et non celle que j'ai sélectionné. J'ai aussi un problème d'affichage : seul le champ d'entrée de quantité apparaît, mais pas les boutons + et - à côté...

Voici tous les fichiers que j'ai modifié pour en arriver là :

themes/classic/templates/catalog/_partials/product-add-to-cart.tpl :

{**
 * Copyright since 2007 PrestaShop SA and Contributors
 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License 3.0 (AFL-3.0)
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/AFL-3.0
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to [email protected] so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to https://devdocs.prestashop.com/ for more information.
 *
 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
 * @copyright Since 2007 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
 *}
<div class="product-add-to-cart js-product-add-to-cart">
  {if !$configuration.is_catalog}
  
    <span class="control-label">{l s='Quantity' d='Shop.Theme.Catalog'}</span>

    {if $product.id_product_attribute > 0}
      {block name='product_quantity'}
        <div class="product-quantity clearfix">
          <div class="add">
            <button
              class="btn btn-primary add-to-cart"
              data-button-action="multi-add-to-cart"
              type="submit"
              {if !$product.add_to_cart_url}
                disabled
              {/if}
            >
              <i class="material-icons shopping-cart">&#xE547;</i>
              {l s='Add to cart' d='Shop.Theme.Actions'}
            </button>
          </div>
          {hook h='displayProductActions' product=$product}
        </div>
      {/block}

      {block name='product_availability'}
        <span id="product-availability" class="js-product-availability">
          {if $product.show_availability && $product.availability_message}
            {if $product.availability == 'available'}
              <i class="material-icons rtl-no-flip product-available">&#xE5CA;</i>
            {elseif $product.availability == 'last_remaining_items'}
              <i class="material-icons product-last-items">&#xE002;</i>
            {else}
              <i class="material-icons product-unavailable">&#xE14B;</i>
            {/if}
            {$product.availability_message}
          {/if}
        </span>
      {/block}

      {block name='product_minimal_quantity'}
        <p class="product-minimal-quantity js-product-minimal-quantity">
          {if $product.minimal_quantity > 1}
            {l
            s='The minimum purchase order quantity for the product is %quantity%.'
            d='Shop.Theme.Checkout'
            sprintf=['%quantity%' => $product.minimal_quantity]
            }
          {/if}
        </p>
      {/block}

    {else}
      <!-- Si le produit n'a pas d'attribut, le bouton reste standard -->
      {block name='product_quantity'}
        <div class="product-quantity clearfix">
          <div class="qty">
            <input
              type="number"
              name="qty"
              id="quantity_wanted"
              inputmode="numeric"
              pattern="[0-9]*"
              {if $product.quantity_wanted}
                value="{$product.quantity_wanted}"
                min="{$product.minimal_quantity}"
              {else}
                value="1"
                min="1"
              {/if}
              class="input-group"
              aria-label="{l s='Quantity' d='Shop.Theme.Actions'}"
            >
          </div>

          <div class="add">
            <button
              class="btn btn-primary add-to-cart"
              data-button-action="add-to-cart"
              type="submit"
              {if !$product.add_to_cart_url}
                disabled
              {/if}
            >
              <i class="material-icons shopping-cart">&#xE547;</i>
              {l s='Add to cart' d='Shop.Theme.Actions'}
            </button>
          </div>
          {hook h='displayProductActions' product=$product}
        </div>
      {/block}
    {/if}

  {/if}
</div>

 

themes/classic/templates/catalog/_partials/product-variants.tpl :

{**
 * Copyright since 2007 PrestaShop SA and Contributors
 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License 3.0 (AFL-3.0)
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/AFL-3.0
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to [email protected] so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to https://devdocs.prestashop.com/ for more information.
 *
 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
 * @copyright Since 2007 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
 *}
<div class="product-variants js-product-variants">
  {foreach from=$groups key=id_attribute_group item=group}
    {if !empty($group.attributes)}
    <div class="clearfix product-variants-item">
      <span class="control-label">{$group.name}{l s=': ' d='Shop.Theme.Catalog'}
          {foreach from=$group.attributes key=id_attribute item=group_attribute}
            {if $group_attribute.selected}{$group_attribute.name}{/if}
          {/foreach}
      </span>
      {if $group.group_type == 'select'}
        <select
          class="form-control form-control-select"
          id="group_{$id_attribute_group}"
          aria-label="{$group.name}"
          data-product-attribute="{$id_attribute_group}"
          name="group[{$id_attribute_group}]">
          {foreach from=$group.attributes key=id_attribute item=group_attribute}
            <option value="{$id_attribute}" title="{$group_attribute.name}"{if $group_attribute.selected} selected="selected"{/if}>{$group_attribute.name}</option>
          {/foreach}
        </select>
        <div class="num">
				                        <span type="button" class="reduc btn btn-md"><i class="icon-minus" id="reduc"></i></span>
				                        <input type="text" class="multi_product_quantity" value="0"/>
				                        <span type="button" class="add btn btn-md"><i class="icon-plus" id="add"></i></span>
			                   </div>
      {elseif $group.group_type == 'color'}
        <ul id="group_{$id_attribute_group}">
          {foreach from=$group.attributes key=id_attribute item=group_attribute}
            <li class="float-xs-left input-container">
              <label aria-label="{$group_attribute.name}">
                <input class="input-color" type="radio" data-product-attribute="{$id_attribute_group}" name="group[{$id_attribute_group}]" value="{$id_attribute}" title="{$group_attribute.name}"{if $group_attribute.selected} checked="checked"{/if}>
                <span
                  {if $group_attribute.texture}
                    class="color texture" style="background-image: url({$group_attribute.texture})"
                  {elseif $group_attribute.html_color_code}
                    class="color" style="background-color: {$group_attribute.html_color_code}"
                  {/if}
                ><span class="attribute-name sr-only">{$group_attribute.name}</span></span>
              </label>
              <div class="num">
				                        <span type="button" class="reduc btn btn-md"><i class="icon-minus" id="reduc"></i></span>
				                        <input type="text" class="multi_product_quantity" value="0"/>
				                        <span type="button" class="add btn btn-md"><i class="icon-plus" id="add"></i></span>
			                   </div>
            </li>
          {/foreach}
        </ul>
      {elseif $group.group_type == 'radio'}
        <ul id="group_{$id_attribute_group}">
          {foreach from=$group.attributes key=id_attribute item=group_attribute}
            <li class="input-container float-xs-left">
              <label>
                <input class="input-radio" type="radio" data-product-attribute="{$id_attribute_group}" name="group[{$id_attribute_group}]" value="{$id_attribute}" title="{$group_attribute.name}"{if $group_attribute.selected} checked="checked"{/if}>
                <span class="radio-label">{$group_attribute.name}</span>
              </label>
              <div class="num">
				                        <span type="button" class="reduc btn btn-md"><i class="icon-minus" id="reduc"></i></span>
				                        <input type="text" class="multi_product_quantity" value="0"/>
				                        <span type="button" class="add btn btn-md"><i class="icon-plus" id="add"></i></span>
			                   </div>
            </li>
          {/foreach}
        </ul>
      {/if}
    </div>
    {/if}
  {/foreach}
</div>

 

themes/classic/assets/js/custom.js :

$(document).ready(function () {
    // Object to hold selected quantities per attribute combination
    let selectedQuantities = {};

    // Function to store quantity in memory
    function updateSelectedQuantities($input, idAttributeGroup, idAttribute) {
        const quantity = parseInt($input.val(), 10);
        if (!selectedQuantities[idAttributeGroup]) {
            selectedQuantities[idAttributeGroup] = {};
        }
        selectedQuantities[idAttributeGroup][idAttribute] = quantity;
    }

    // Function to restore quantity from memory
    function restoreSelectedQuantities() {
        $('.multi_product_quantity').each(function () {
            const $input = $(this);
            const idAttributeGroup = $input.closest('.product-variants-item').find('[data-product-attribute]').attr('data-product-attribute');
            const idAttribute = $input.closest('.product-variants-item').find('[data-product-attribute]').val();
            if (selectedQuantities[idAttributeGroup] && selectedQuantities[idAttributeGroup][idAttribute] !== undefined) {
                $input.val(selectedQuantities[idAttributeGroup][idAttribute]);
            }
        });
    }

    // Function to reset default combination from being added
    function resetDefaultCombination() {
        $('input[name="group[default]"]').prop('checked', false);
    }

    // Increment quantity and update memory
    $(".icon-plus").each(function () {
        $(this).click(function () {
            const $input = $(this).parent().prev();  // The quantity input field
            let quantity = parseInt($input.val(), 10);
            quantity++;
            $input.val(quantity);

            const idAttributeGroup = $(this).closest('.product-variants-item').find('[data-product-attribute]').attr('data-product-attribute');
            const idAttribute = $(this).closest('.product-variants-item').find('[data-product-attribute]').val();

            updateSelectedQuantities($input, idAttributeGroup, idAttribute);

            // Check the corresponding color, radio, or select input
            var attributeInput = $(this).closest('.product-variants-item').find('.input-color, .input-radio, select');
            if (quantity > 0 && attributeInput.length > 0) {
                attributeInput.prop('checked', true).trigger('change');
            }

            // Reset the default combination
            resetDefaultCombination();
        });
    });

    // Decrement quantity and update memory
    $(".icon-minus").each(function () {
        $(this).click(function () {
            const $input = $(this).parent().next();  // The quantity input field
            let quantity = parseInt($input.val(), 10);
            if (quantity > 0) {
                quantity--;
                $input.val(quantity);

                const idAttributeGroup = $(this).closest('.product-variants-item').find('[data-product-attribute]').attr('data-product-attribute');
                const idAttribute = $(this).closest('.product-variants-item').find('[data-product-attribute]').val();

                updateSelectedQuantities($input, idAttributeGroup, idAttribute);

                // Uncheck the color, radio, or select input if quantity is 0
                if (quantity === 0) {
                    var attributeInput = $(this).closest('.product-variants-item').find('.input-color, .input-radio, select');
                    if (attributeInput.length > 0) {
                        attributeInput.prop('checked', false).trigger('change');
                    }
                }
            }

            // Reset the default combination
            resetDefaultCombination();
        });
    });

    // Handle changes in dropdowns or radio buttons and restore quantities
    $('.product-variants-item select, .product-variants-item .input-radio').change(function () {
        restoreSelectedQuantities();
    });

    // Handle multi-add-to-cart button
    const $body = $('body');
    $body.on('click', '[data-button-action="multi-add-to-cart"]', (event) => {
        event.preventDefault(); // Prevent the default form submission
        const $form = $(event.target.form);
        const actionURL = $form.attr('action');

        // Find all quantity inputs with value > 0
        var wantedItems = $('.multi_product_quantity').filter(function () {
            return parseInt($(this).val(), 10) > 0;
        });

        if (wantedItems.length === 0) {
            alert("Please select at least one product with quantity.");
            return;
        }

        // Prevent default combination from being added
        resetDefaultCombination();

        // Collect all requests
        let requests = wantedItems.map(function (index, item) {
            var $item = $(item);
            var quantity = parseInt($item.val(), 10);
            if (quantity > 0) {
                // Get the associated product attribute (color, radio, or select)
                var attributeInput = $item.closest('.product-variants-item').find('.input-color, .input-radio, select');
                if (attributeInput.length > 0) {
                    var idProduct = document.getElementsByName('id_product')[0].value;
                    var idProductAttribute = attributeInput.val();
                    var group = attributeInput.attr('data-product-attribute');

                    // Build the query string for this combination
                    var query = `${$form.serialize()}&add=1&action=update&id_product=${idProduct}&qty=${quantity}&group[${group}]=${idProductAttribute}&ipa=${idProductAttribute}`;

                    // Return the AJAX request promise
                    return $.post(actionURL, query, null, 'json');
                }
            }
        }).get();

        // Execute all requests
        $.when.apply($, requests)
            .done(function () {
                prestashop.emit('updateCart', {
                    reason: {},
                    resp: arguments,
                });
                // Optionally, refresh the page after all requests are completed
                setTimeout(function () {
                    location.reload(); // Refresh the page after a delay
                }, 2000); // Adjust the delay as needed
            })
            .fail(function (resp) {
                prestashop.emit('handleError', { eventType: 'addProductToCart', resp });
            });
    });
});

 

et également, ces quelques lignes dans themes/classic/assets/css/theme.css :

.num{
  padding-left:10px;
  display: inline-block;
  float: right;
  }
  .icon-plus .icon-minus{
  color: black !important;
  font-size: 1.5rem;
}

 

Si quelqu'un peut m'aider ou m'expliquer comment résoudre ces problèmes, c'est avec grand plaisir :)

Link to comment
Share on other sites

  • 4 weeks later...

Bonjour

Je cherches à faire la même chose pour ma boutique. (voir fichier joint) Je suis d'ailleurs étonné qu'aucun module (ou alors j'ai mal cherché) ne propose d'ajouter cette fonction.

Merci pour le lien vers le tuto, je vais tenter de le mettre en place sur mon dev et je tacherais de vous faire un retour, si ça peut aider !

capturequantité.jpg

Link to comment
Share on other sites

  • 2 weeks later...

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
×
×
  • Create New...