Search code examples
shopifyliquidshopify-appshopify-template

Multi file uploader in Shopify Debut theme


I'm customizing my page to upload multiple files (photos). This is the code that I have so far and according to this link it should work: https://shopify.dev/tutorials/customize-theme-get-customization-information-for-products in the link go to "Allow file uploads" if you want to check.

{% form 'product', product, class:form_classes, data-product-form: '', enctype:"multipart/form-data" %}
            {% unless product.has_only_default_variant %}
              <div class="product-form__controls-group">
                {% for option in product.options_with_values %}
                  <div class="selector-wrapper js product-form__item">

                    <select class="single-option-selector single-option-selector-{{ section.id }} product-form__input"
                      id="SingleOptionSelector-{{ forloop.index0 }}"
                      data-index="option{{ forloop.index }}"
                    >
                      {% for value in option.values %}
                        <option value="{{ value | escape }}"{% if option.selected_value == value %} selected="selected"{% endif %}>{{ value }}</option>
                      {% endfor %}
                    </select>
                  </div>
                {% endfor %}
              </div>
            {% endunless %}

            <select name="id" id="ProductSelect-{{ section.id }}" class="product-form__variants no-js">
              {% for variant in product.variants %}
                <option value="{{ variant.id }}"
                  {%- if variant == current_variant %} selected="selected" {%- endif -%}
                >
                  {{ variant.title }}  {%- if variant.available == false %} - {{ 'products.product.sold_out' | t }}{% endif %}
                </option>
              {% endfor %}
            </select>

            {% if section.settings.show_quantity_selector %}
            {% comment %}
              <div class="product-form__controls-group">
                <div class="product-form__item">
                  <input type="number" id="Quantity-{{ section.id }}"
                    name="quantity" value="1" min="1" pattern="[0-9]*"
                    class="product-form__input product-form__input--quantity" data-quantity-input
                  >
                </div>
              </div>

                <div class="qtydiv">
                {% comment %}<label for="Quantity" class="quantity-selector">Quantity</label>{% endcomment %}
                <div class="qtybox">
                  <span class="btnqty qtyminus icon icon-minus">-</span>
                  <input type="text" id="quantity" name="quantity" value="1" min="1" class="quantity-selector quantity-input" readonly="">
                  <span class="btnqty qtyplus icon icon-plus">+</span>
                </div>
              </div>
        {% endcomment %}
            {% endif %}

            <div class="product-form__error-message-wrapper product-form__error-message-wrapper--hidden{% if section.settings.enable_payment_button %} product-form__error-message-wrapper--has-payment-button{% endif %}"
              data-error-message-wrapper
              role="alert"
            >
              <span class="visually-hidden">{{ 'general.accessibility.error' | t }} </span>
              {% include 'icon-error' %}
              <span class="product-form__error-message" data-error-message>{{ 'products.product.quantity_minimum_message' | t }}</span>
            </div>

            <div class="product-form__controls-group product-form__controls-group--submit">
              <div class="qtydiv">
                {% comment %}<label for="Quantity" class="quantity-selector">Quantity</label>{% endcomment %}
                <div class="qtybox">
                  <span class="btnqty qtyminus icon icon-minus">-</span>
                  <input type="text" id="quantity" name="quantity" value="1" min="1" class="quantity-selector quantity-input" readonly="">
                  <span class="btnqty qtyplus icon icon-plus">+</span>
                </div>
              </div>
              <div class="product-form__item product-form__item--submit
                {%- if section.settings.enable_payment_button %} product-form__item--payment-button {%- endif -%}
                {%- if product.has_only_default_variant %} product-form__item--no-variants {%- endif -%}"
              >
                <button type="submit" name="add"
                  {% unless current_variant.available %} aria-disabled="true"{% endunless %}
                  aria-label="{% unless current_variant.available %}{{ 'products.product.sold_out' | t }}{% else %}{{ 'products.product.add_to_cart' | t }}{% endunless %}"
                  class="btn product-form__cart-submit{% if section.settings.enable_payment_button %} btn--secondary-accent{% endif %}"
                  data-add-to-cart>
                  <span data-add-to-cart-text>
                    {% unless current_variant.available %}
                      {{ 'products.product.sold_out' | t }}
                    {% else %}
                      {{ 'products.product.add_to_cart' | t }}
                    {% endunless %}
                  </span>
                  <span class="hide" data-loader>
                    {% include 'icon-spinner' %}
                  </span>
                </button>
                {% if section.settings.enable_payment_button %}
                  {{ form | payment_button }}
                {% endif %}
              </div>
                    <input required class="required product-form__input" id="photo" type="file" name="properties[Photo]" multiple>
              </div>
            </div>
          {% endform %}

This is my form that according to the link must have the attribute

enctype = "multipart / form-data".

At the bottom of the form it is in input type = "file".

<input required class="required product-form__input" id="photo" type="file" name="properties[Photo]" multiple>

And this is the code in the cart-template.liquid.

<div class="list-view-item__title">
                      <a href="{{ item.url }}" class="cart__product-title" data-cart-item-title>
                        {{ item.product.title }}<br>
                        {% assign property_size = item.properties | size %}
                          {% if property_size > 0 %}
                            {% for p in item.properties %}
                                {{ item.properties.count }}
                              {% assign first_character_in_key = p.first | truncate: 1, '' %}
                              {% unless p.last == blank or first_character_in_key == '_' %}
                                {{ p.first }}:
                                {% if p.last contains '/uploads/' %}
                                  <a class="lightbox" href="{{ p.last }}">{{ property_size }}</a>
                                {% else %}
                                  {{ p.last }}
                                {% endif %}
                                <br>
                              {% endunless %}
                            {% endfor %}
                          {% endif %}
                      </a>
                    </div>

The problem is the property_size variable gets value one even if I upload two or more images, when in fact I should return the amount of properties that the item has. Can anyone help me please? What I am doing wrong?


Solution

  • The Debut theme probably has changed since this tutorial was uploaded, and since it is not supported, probably won't be updated.

    If you add a type="text" input instead of a type="file" the property gets added as expected. This happens because the _initAddToCart function (assets/theme.js line 6447) gets the form HTML element and pass it along to another function.

    var $data = $(this.selectors.productForm, this.$container);
    this._addItemToCart($data);
    

    Then in the _addItemToCart function (assets/theme.js line 6513), the form is serialized to a string:

    var params = {
      url: '/cart/add.js',
      data: $(data).serialize(),
      dataType: 'json',
    };
    $.post(params).done(...
    

    This function will ignore the file inputs, as explained in the jQuery documentation:

    Data from file select elements is not serialized.
    

    To send the file, the FormData browser API can be used. But to use it with the jQuery AJAX call, some properties must be set:

    var params = {
      url: '/cart/add.js',
      // data: $(data).serialize(),
      dataType: 'json',
      // Disable the jQuery data processing, and send the FormData object
      contentType: false,
      processData: false,
      data: new FormData($(data).get()[0])
    };
    $.post(params).done(...
    

    However, since the HTML file <input> has name="properties[Photo]", only the last of the uploaded files will be added to the cart item's Photo property. I tried with name="properties[Photo][]", but the cart/add.js call only returns an error 500 with the message "Internal Server Error".

    So to upload multiple files you could add some custom code to the _addItemToCart function, to get each File from the <input> and add to the FormData with a different key for each file.