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

Support file upload line item properties on Product Forms and Cart Notifications #745

Merged
merged 3 commits into from
Oct 15, 2021

Conversation

LucasLacerdaUX
Copy link
Contributor

@LucasLacerdaUX LucasLacerdaUX commented Oct 6, 2021

Why are these changes introduced?
Add support for type="file" inputs on Product Forms.
Adds line item properties to cart notifications

Fixes #731

What approach did you take?
Using FormData, we are now able to send files to the cart_add_url endpoint. Submitting the request as FormData is necessary to support file uploads when adding items to cart by AJAX. Otherwise, the browser would replace the File object with an invalid string such as C:\fakepath\filename.png.

This is how it works:

  1. Check if browser is compatible with FormData.
  2. If so, remove the Content-Type as we're no long passing a json as the request's body.
  3. Convert the form to FormData.
  4. Add extra attributes used by cart notification, such as sections and sections_url.
  5. Set the request.body to use the FormData object.

How to test 🎩

  1. Add the following code to main-product.liquid and featured-product.liquid product forms:
<div>
  <input type="file" id="file" name="properties[File]" form="{{ product_form_id }}">
  <label class="field__label" for="file">Custom Image</label>
</div>

<div class="field">
  <input type="checkbox" id="isMatte" name="properties[isMatte]" form="{{ product_form_id }}">
  <label class="field__label" for="isMatte">Matte option</label>
</div>

<div class="field">
  <input class="field__input" type="text" id="engraving" name="properties[engraving]" form="{{ product_form_id }}">
  <label class="field__label" for="engraving">Engraving</label>
</div>
  1. Test different product types (no variants, multiple variants) and custom properties (engraving / no engraving, matte ON/OFF, file / no file)
  2. Test in multiple browsers (Safari, IE11, Chrome, Firefox and mobile browsers)
  3. Test on both featured product and product page.

Expected results:

Demo links

Checklist

@LucasLacerdaUX LucasLacerdaUX changed the title Support file upload line item properties on Product Forms and art notification Support file upload line item properties on Product Forms and Cart Notifications Oct 6, 2021
KaichenWang
KaichenWang previously approved these changes Oct 7, 2021
Copy link
Contributor

@KaichenWang KaichenWang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works well!

const formData = new FormData(this.form);
formData.append('sections', this.cartNotification.getSectionsToRender().map((section) => section.id));
formData.append('sections_url', window.location.pathname);
config.body = formData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a snippet of code above that could be moved into an else { ... }. Since config.body would only need to be set either as formData or as JSON string?

if (FormData) {
  ...
} else {
  config.body = JSON.stringify({
    ...JSON.parse(serializeForm(this.form)),
    sections: this.cartNotification.getSectionsToRender().map((section) => section.id),
    sections_url: window.location.pathname
  });
}

Copy link
Contributor Author

@LucasLacerdaUX LucasLacerdaUX Oct 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

Your comment made me realize that serializeForm function was already using FormData. There's no point in keeping the two functions there, in that case. If the browser doesn't support that API, neither methods would work.

Also, my main concern with FormData was related to specific browsers that only offer partial support for this API, like IE11 and some older mobile browsers. But I just noticed that I used formData.append, which is supported by all of them (IE10+), and not the often incompatible formData.set, so we should be fine :)

I removed the old implementation and the serializeForm function and kept the new approach. Sorry for dismissing your reviews, but I believe that's a better way to handle this :DD
(please make sure re-test on multiple browser)

@ludoboludo ludoboludo self-requested a review October 7, 2021 18:19
{%- assign property_first_char = property.first | slice: 0 -%}
{%- if property.last != blank and property_first_char != '_' -%}
<div class="product-option">
<dt>{{ property.first }}: </dt>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should ask the translation team about the use of :. Does it mean the same thing everywhere ? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. We actually use colon the same way in a few areas of the theme, I would be curious to know how best to handle this for i18n

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good question. I think the scope would be a bit broader than this PR, though. I'm just reproducing the same decision we've made for cart items here.

I'll create a separate issue so we can review this and update in multiple parts of the theme if needed.

@LucasLacerdaUX LucasLacerdaUX requested review from ludoboludo and removed request for ludoboludo October 7, 2021 18:51
KaichenWang
KaichenWang previously approved these changes Oct 7, 2021
Copy link
Contributor

@KaichenWang KaichenWang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works great!

@@ -12,7 +12,7 @@ if (!customElements.get('product-form')) {
onSubmitHandler(evt) {
Copy link
Contributor

@KaichenWang KaichenWang Oct 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be handled in a separate issue/PR - In Safari (iOS and macOS) the spinner inside the button when adding-to-cart is not visible.

I believe setting an explicit width to the svg fixes this, but requires more testing in all instances where it's being used.

.loading-overlay__spinner svg {
  width: 100%;
}

Kapture 2021-10-07 at 15 16 10

Copy link
Contributor Author

@LucasLacerdaUX LucasLacerdaUX Oct 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix this in a separate PR. Thanks for sharing a potential fix - will test it :D

@@ -20,6 +20,23 @@
<dd>{{ option.value }}</dd>
</div>
{%- endfor -%}
{%- for property in item.properties -%}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all this markup is inside the {%- unless item.product.has_only_default_variant -%}, does that mean if the product has line item properties then item.product.has_only_default_variant will output false? Asking because otherwise the line item properties will not be displayed for products with only 1 variant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I moved the {%- unless -%} inside the <dl> tag to prevent this. Should now be working for both single and multi-variant products.

@M00NSH0T
Copy link

M00NSH0T commented Oct 14, 2021

sorry to chime in here since I know this is more an internal thread, but I downloaded the fix-line-item branch and uploaded it to my development site. When I add a file upload using the div code you provide for testing to a custom liquid code block in the page:
<div> <input type="file" id="file" name="properties[File]" form="{{ product_form_id }}"> <label class="field__label" for="file">Custom Image</label> </div>
the button shows up and I'm able to upload an image, and it successfully adds to cart. However, when I go to the cart, I don't see the image attached to the item, like in the "expected result" image.

I note your testing directions indicate I should instead be modifying the main-product.liquid and featured-product.liquid files, rather than add it via one of these new nifty liquid code blocks in the UI. I have no problem doing that, but can you be a bit more specific about where exactly I should insert those div snippets? When I experimented around, I couldn't get them in the right spot, and I was observing the same behavior with the images not apparently making it through the 'add to cart' process.

thanks!

@ludoboludo
Copy link
Contributor

Hey @M00NSH0T, what you could do is paste it in the quantity_selector block on main-product.liquid for example. Here is a screenshot. I also changed the form attribute value to be form="product-form-{{ section.id }}" instead of form="{{ product_form_id }}"

Copy link
Contributor

@ludoboludo ludoboludo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👌

I was having some issues at first but I think it's because I needed to change the form attribute to point for the main-product.liquid to product-form-{{ section.id }}

sections: this.cartNotification.getSectionsToRender().map((section) => section.id),
sections_url: window.location.pathname
});
delete config.headers['Content-Type'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, do you mind explaining why we're removing this specific entry from the config.headers? 😬

Copy link
Contributor Author

@LucasLacerdaUX LucasLacerdaUX Oct 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it's because we're no longer sending a Content-Type application/json request. We're now using FormData directly, without converting to JSON like we did before.

The function that returns these config headers is still being used in other endpoints across the theme, and they're still sending JSON requests, so that's why I only removed this from the /cart request.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah great. Thanks for the explanation 🙂

@RedPlugDesign
Copy link

RedPlugDesign commented Feb 9, 2022

@LucasLacerdaUX While experimenting with this in Chrome, I found an odd behavior for the file. Using dawn and your test setup, the files do pass via AJAX, but if you click those same files, the file saves as a WEBP, despite the actual file extension. I disabled the AJAX, https://www.screencast.com/t/4AtCSRoVewx, and tried again with a similar file, and when going straight to cart, the file will download as it should. Firefox was not an issue.

While not a big deal, it will confuse the average customer

EDIT: Seems this is only an issue with PNG's

@robertosenabre
Copy link

Hey @RedPlugDesign! I think it saves as a WEBP file because of the Shopify CDN: https://cdn.shopify.com/. It automatically detects which file formats are supported on the client-side and sends the best option

@RedPlugDesign
Copy link

@aresena Last I tested, when the file was passed to the order admin, it saved as a WEBP, and I could not open it by simple clicking on it as a link for the order admin. Since a merchant may need to download this file via the order admin, if they could not reformat the file or download it at all, it would be an issue for them to fulfill the order.

If you have a demo set up, try JPEG's & PNG's via AJAX carts and non on different browsers. You may find varying results. If you dont, great, I will have to go back and test again. Ultimate;y, I was hoping your article addressed the issue

@robertosenabre
Copy link

@RedPlugDesign True, you need to download it to see it, and it's a WEBP. Good point that some merchants could have difficulties opening it!

@MELO-06
Copy link

MELO-06 commented Jun 11, 2024

Hey, its been a long time since this pull was created

I was wondering if its possible to change this so the client can upload multiple files and they all show as photos separated by commas

Thanks in advance

phapsidesGT pushed a commit to Gravytrain-UK/gt-shopify-dawn-theme that referenced this pull request Sep 3, 2024
@poblabs
Copy link

poblabs commented Sep 9, 2024

This is a great option, thank you for this. However, once an image is uploaded to the cdn, how does the shop owner retain control over it?

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

Successfully merging this pull request may close these issues.

Support file upload line item properties on Add To Cart JS
8 participants