Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stock displayed for an attribute option isn't updated correctly on the product page #1

Open
nattywebdev opened this issue Dec 11, 2024 · 8 comments

Comments

@nattywebdev
Copy link

[transferred from https://github.com/backdrop-contrib/ubercart/issues/521 once I realised this was more relevant here]

I'm working on a new site with a large number of products (around 3000) - currently it's a live D7 Commerce site that I am rebuilding with Backdrop and Ubercart.

A key issue for my client is stock, which us a product variation level in D7. I'm rebuilding the product collection with attributes and options to replace variations and stock per option is needed.

I have found that the stock quantity displayed on the add to cart form isn't displayed correctly when you switch from one option to another.

I assume this is a bug so am reporting it here - unless I'm doing something wrong (if so I'd appreciate guidance!).

Images below show the relevant detail.

Test product default (on page load) - shows 'in stock' before any option selected:
image

Test product with 'Fat Quarter' selected - stock should be 9 (9 is correctly displayed):
image

Test product with 'Fat Eighth' selected - stock should be 5 (9 is incorrectly displayed):
image

Stock displayed from editing node:
image

@laryn
Copy link
Member

laryn commented Dec 11, 2024

Thanks @nattywebdev -- I'm not currently using this module on any sites so I'm going to have to rely on you to do some lifting.

Any Javascript errors in the console, or information that may be relevant in the watchdog log or PHP log?

It's also possible that the JS that comes with this module doesn't account for attributes/options being changed on the fly, in which case the JS needs improvement. Have you checked the D7 queue for this module?

@nattywebdev
Copy link
Author

@laryn will do. I'll report back here.

@nattywebdev
Copy link
Author

nattywebdev commented Dec 17, 2024

This issue on the D7 queue describes the same problem:
https://www.drupal.org/project/uc_out_of_stock/issues/2476957

although that refers to when the stock reaches zero. In my case I see that but also the stock not being correctly displayed for the first attribute option after the second one has been selected and you then revert back to the first.

There is a supplied patch, which I've manually applied to the uc_out_of_stock.js file in the Backdrop module, but it appears not to fix the problem. I see no difference in the behaviour after making the changes.

The patch is here:
https://www.drupal.org/files/issues/uc_out_of_stock.js_.20150618.patch

@laryn might you be able to take a quick look at that to see if there needs to be other changes (I'm thinking Backdrop-specific) that I'm not seeing?

I'll also add my modified uc_out_of_stock.js file as an attachment. Oh I see I can't do that, so I'll add the code here below.

(function ($) {
  Backdrop.behaviors.ucOutOfStock =  {
    attach: function(context) {
      
      var ucOutOfStockForms = ucOutOfStockForms || new Array();

      if(typeof console === "undefined") {
        console = {log: function() {}};
      };

      // Your code here
      attrid = 'edit-attributes';

      function checkStock(forms) {
        var form_ids = new Array();
        var node_ids = new Array();
        var attr_ids = new Array();
        $.each(forms, function(index, form) {
          var product = new Object();
          var attributes = new Object();

          var nid = form.attr('id').match(/(?:uc-product-add-to-cart-form-|catalog-buy-it-now-form-)([0-9]+)/)[1];

          attributes.found = new Object();
          attributes.value = new Object();

          $("[name*=attributes]", form).filter(':input:not(:text):not(:checkbox)').each(function(index){
            // We are assuming the value has to be there, seems to be working for radios and checkboxes
            if ($(this).attr('name').match(/attributes\[([0-9]*)\]/)) {
              id = $(this).attr('name').match(/attributes\[([0-9]*)\]/)[1];
              if ($(this).is(':radio')) {
                attributes.found['attr'+id] = 1;
                if ($(this).is(':checked')) {
                  if ($(this).val()) {
                    attributes.value['attr'+id] = 1;
                    attr_ids.push(nid + ':' + id + ':' + $(this).val());
                  }
                }
              }
              else if ($(this).is('select')) {
                attributes.found['attr'+id] = 1;
                if ($(this).val()) {
                  attributes.value['attr'+id] = 1;
                  attr_ids.push(nid + ':' + id + ':' + $(this).val());
                }
              }
            }
          });

          // find qty
          product['qty'] = 1;
          qty = $(':input[name="qty"]', form).val()
          if (qty) {
            product['qty'] = qty;
          }

          // finding if attributes are found with no value
          attributes.found.length = attributes.value.length = 0;
          for (var i in attributes.found) {
            if (i!='length') {
              attributes.found.length++;
            }
          }

          for (var i in attributes.value) {
              if (i!='length') {
              attributes.value.length++;
            }
          }

          if (Backdrop.settings.uc_out_of_stock.throbber) {
            $(".uc_out_of_stock_throbbing", form).addClass('uc_oos_throbbing');
          }
          form_ids.push(form.attr('id'));
          node_ids.push(nid);
        });

        if (form_ids.length == 0) {
          return;
        }

        var post = { 'form_ids[]': form_ids, 'node_ids[]': node_ids, 'attr_ids[]': attr_ids }
        $.ajax({
          type: 'post',
          url : Backdrop.settings.uc_out_of_stock.path,
          data: post,
          success : function (data, textStatus) {
            $.each(data, function(form_id, stock_level) {
              var form = $('#' + form_id);
              if (stock_level != null && parseInt(stock_level) <= 0) {
                // hide add to cart button
                $("input:submit.node-add-to-cart,input:submit.list-add-to-cart,button.node-add-to-cart,button.list-add-to-cart", form).hide();
                // hide qty label, field and wrapper if present and if it follows
                // default theme output
                $("label[for=" + $(":input[name=qty]", form).attr('id') + "]", form).hide();
                $(":input[name=qty]", form).hide();
                $("#" + $(":input[name=qty]", form).attr('id') + "-wrapper", form).hide();
                // Show out of stock message
                $(".uc_out_of_stock_html", form).html(Backdrop.settings.uc_out_of_stock.msg);

                if (Backdrop.settings.uc_out_of_stock.instock) {
                  $(".uc-out-of-stock-instock", form).hide();
                }
              }
              else {
                // Put back the normal HTML of the add to cart form
                $(".uc_out_of_stock_html", form).html('');
                // show qty elements
                $("label[for=" + $(":input[name=qty]", form).attr('id') + "]", form).show();
                $(":input[name=qty]", form).show();
                $("#" + $(":input[name=qty]", form).attr('id') + "-wrapper", form).show();
                // show add to cart button
                $("input:submit.node-add-to-cart,input:submit.list-add-to-cart,button.node-add-to-cart,button.list-add-to-cart", form).show();
                if (Backdrop.settings.uc_out_of_stock.instock) {
                  $(".uc-out-of-stock-instock", form).html(Backdrop.theme('ucOutOfStockInStock', stock_level));
                  $(".uc-out-of-stock-instock", form).show();
                }
              }

              if (Backdrop.settings.uc_out_of_stock.throbber) {
                $(".uc_out_of_stock_throbbing", form).removeClass('uc_oos_throbbing');
              }
            });
          },
          error : function (jqXHR, textStatus, errorThrown) {
            console.log('uc_out_of_stock: ' + textStatus + ': ' + jqXHR.responseText);
            if (Backdrop.settings.uc_out_of_stock.throbber) {
              $(".uc_out_of_stock_throbbing").removeClass('uc_oos_throbbing');
            }
          },
          dataType: 'json'
        });
      }

      $("form[id*=uc-product-add-to-cart-form], form[id*=uc-catalog-buy-it-now-form]").not('.uc-out-stock-processed').each(function() {
        $(this).addClass('uc-out-stock-processed');
        ucOutOfStockForms.push($(this));
        if (Backdrop.settings.uc_out_of_stock.throbber) {
          $("input:submit.node-add-to-cart,input:submit.list-add-to-cart,button.node-add-to-cart,button.list-add-to-cart", $(this)).before('<div class="uc_out_of_stock_throbbing">&nbsp;&nbsp;&nbsp;&nbsp;</div>');
        }

        if (Backdrop.settings.uc_out_of_stock.instock) {
          if ($(':input[name="qty"][type!="hidden"]', $(this)).length) {
            $(":input[name=qty]", $(this)).after('<div class="uc-out-of-stock-instock"></div>');
          }
          else {
            $("input:submit.node-add-to-cart,input:submit.list-add-to-cart,button.node-add-to-cart,button.list-add-to-cart", $(this)).before('<div class="uc-out-of-stock-instock"></div>');
          }
        }

        $("input:submit.node-add-to-cart,input:submit.list-add-to-cart,button.node-add-to-cart,button.list-add-to-cart", $(this)).after('<div class="uc_out_of_stock_html"></div>');
        var form = $(this);

        $("[name*=attributes]", $(this)).filter(':input:not(:text):not(:checkbox)').change(function(){
          checkStock([form]);
        });
        /* TODO: Feature request - support qty field, would make sense if cart
         * contents are checked in the server as well as just stock
         */
        /*
        $(":input[name=qty]", $(this)).keyup(function(){
          checkStock(eachForm);
        });
        */
      });

      checkStock(ucOutOfStockForms);
    }
  }

  Backdrop.theme.prototype.ucOutOfStockInStock = function (stock) {
    if (stock == undefined) {
      return Backdrop.t('In stock');
    }
    else {
      return Backdrop.t('@stock in stock', {'@stock' : stock});
    }
  };
})(jQuery);

@laryn
Copy link
Member

laryn commented Dec 17, 2024

@nattywebdev Could you submit that patched file as a PR? It would be easier to spot check that way.

@nattywebdev
Copy link
Author

I will if I can find out how. Is there a step-by-step guide I can follow?

@nattywebdev
Copy link
Author

PR #2 created - I hope I've done it right and that this helps.

Please advise if not @laryn !

@laryn
Copy link
Member

laryn commented Dec 18, 2024

@nattywebdev Good job, looks like the PR was created correctly. Now we can compare to the patch more easily. The way I see it, we should:

  • determine if the patch works
  • determine if the approach in the patch is a good one

In spot checking the patch vs. your PR, it looks like they've added this line way at the top of the file, above everything else: var ucOutOfStockForms = ucOutOfStockForms || new Array(); Your PR has it nested inside some other code.

@nattywebdev
Copy link
Author

I'm not able to do any more, so when you say 'we' I hope you mean 'you'! I've looked through the code but I can't really see how it works, never mind why it's failing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants