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

Upload images in admin with drag and drop #1553

Merged
merged 12 commits into from
Nov 7, 2016
Merged

Upload images in admin with drag and drop #1553

merged 12 commits into from
Nov 7, 2016

Conversation

mtomov
Copy link
Contributor

@mtomov mtomov commented Oct 22, 2016

I wanted to see how much effort it would be to do a modern drag and drop image upload (#1518), and that pretty much does it. Code is not ideal, but wanted to post it here if somebody wants to try it out, have any comments, etc ..

In summary:

  • The JavaScript is just about 100 lines (grew to ~ 200 with Backbone), even non-coffee-script (as I started with a non coffee example, but can convert it around).
  • Code I started from includes tests for feature detection (Drag and Drop, File Upload, Progress Bar). I decided to leave them for reference, but they can be removed, as I think that admin users should run the very latest of browsers, and if they don't - they can use the old functionality.
  • All of the old way of uploading images is in place, so if someone is stuck with some unsupported functions, they can always fallback to that.
  • There's a javascript response from the server, which appends a row in the images table .. not sure if it's an accepted practice
  • I need to add a capybara test, but shouldn't be difficult, as I have also left in place a Browse button if somebody doesn't want to drag and drop
  • Image is automatically assigned the master variant
    • so to make it a truly nice experience, the rows in the images table will have an select dropdown field with ability to change the variant, and click a Tick button on the right, similarly to the Stock Items page, and other in-place row edits.

And some pictures:

screenshot from 2016-10-27 23 30 25

screenshot from 2016-10-27 23 40 48

or in boxes:

screenshot from 2016-11-03 21 40 05

@mtomov mtomov changed the title Upload images in admin with drag and drop WIP: Upload images in admin with drag and drop Oct 22, 2016
@mtomov mtomov changed the title WIP: Upload images in admin with drag and drop Upload images in admin with drag and drop Oct 28, 2016
@mtomov
Copy link
Contributor Author

mtomov commented Oct 28, 2016

Hello,

This one is ready for official review. I've cleaned up the javascript using Backbone, and have placed the supporting bits (css, js, translations) to their relevant places.

This is first time I'm using Backbone and I'm quite pleasantly surprised how it put a nice structure overall. The events mapping is very clean, and the view and its model work very well together to provide for live data binding.

You might've noticed that I'm using the underscore's templating function _template() instead of the customary Handlebars, and the reason is that I don't like how all handlebar templates are placed in a god forsaken directory separated from their actual location of usage. In terms of performance - since the template is only parsed and evaluated once on the initial page load, and I'm using the simpler template syntax with Underscore, I don't think there is a negative burden on the page.

In terms of design - current one is a bit plain. An alternative is to make use of Bootstrap's Cards. So, either multiple images per row, or again a single image per row, but wrapped in a round border.

Thank you!

@tvdeyen tvdeyen self-assigned this Oct 28, 2016
@tvdeyen
Copy link
Member

tvdeyen commented Oct 28, 2016

@mtomov thanks! I will have a look later.

<p id="formdata">XHR2's FormData is not supported</p>
<p id="progress">XHR2's upload progress isn't supported</p>

<script type="text/template" id="upload-progress-tmpl">
Copy link
Contributor

@jhawthorn jhawthorn Oct 28, 2016

Choose a reason for hiding this comment

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

Elsewhere we've used handlebars templates which can be thrown in spree/backend/templates which are precompiled and made available to the admin JS.

Edit: Sorry, I see you left a comment noting this.

I would prefer this used handlebars. Both for consistency and because I think it is better here.

Copy link
Contributor

@Sinetheta Sinetheta left a comment

Choose a reason for hiding this comment

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

@mtomov this is very cool 🎉 and I left some minor feedback.

I understand your desire to avoid the handlebars template structure over just using a quick underscore template, but this component seems

  1. Just big enough to be noticeable in this file.
  2. Something very generic that we might want to use elsewhere.

},

onDragOver: function() {
this.el.className = 'hover';
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe something other than .hover , since the action here is .drag-over.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right so. I've gone for that dragClass: 'with-images'

},

onDragLeave: function() {
this.el.className = '';
Copy link
Contributor

Choose a reason for hiding this comment

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

If we use jquery's addClass and removeClass we can be more descriptive of what's happening and avoid crushing unrelated classnames.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed so. Used the native's el.classList.remove/add


onDragOver: function() {
this.el.className = 'hover';
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to be more specific that return false, here we need to e.preventDefault() to allow dropping a file, but we don't need to stopPropagation of the event.

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 observation. Corrected to that


onDragLeave: function() {
this.el.className = '';
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to stop or prevent this event.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for looking in those details. Corrected now.

this.el.className = '';
e.preventDefault();

for (var i = 0; i < e.originalEvent.dataTransfer.files.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could use one of our helper libraries for iterating.

_.each(e.originalEvent.dataTransfer.files, this.upload, this);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

did it as per your example - all excellent working. Thanks!

}
},

tests: {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if we need the browser support checking, since these APIs exist on all modern browsers including Microsoft Edge and we're not doing anything in the event of failure, just showing some words.

If we did want the support checking, maybe a custom build of Modernizr would be less application code to support? cc @jhawthorn

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not in favour of adding more libs. The code for those random pieces of text is ~20 links. As you see fit..

data: formData,
processData: false, // tell jQuery not to process the data
contentType: false, // tell jQuery not to set contentType
xhr: function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding progress to ajax is cool, we may want to make this generic to all Spree.ajax in the future. Make it a callback and trigger an event.

Copy link
Contributor Author

@mtomov mtomov Nov 3, 2016

Choose a reason for hiding this comment

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

I think it's too early at this point for anything such. As far as I understand, one only gets progress for large user submitted requests, i.e. files. For regular post requests when the server is just slow .. that's a different story that would likely need server support.

@@ -0,0 +1,22 @@
<tr id="<%= spree_dom_id image %>" data-hook="images_row" class="<%= cycle('odd', 'even')%>">
Copy link
Contributor

Choose a reason for hiding this comment

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

As we add more long lived pages cycle('odd', 'even') is really gonna show it's limitations, as new uploads don't get striped.

   - image is automatically assigned the master variant

   - use latest browsers to make best use of it
  .. after successful upload

  On error - display error message below uploading row


  - Place a permanent Browse button to allow users to also upload images
    without drag and drop. Also allow for selecting multiple
  - also, display error messages if any from the server
  - use underscore's template function, but replace interpolation tags
   from the default <% %> to {{ }}

  - use progressModel and progressView to manage the progress div row
  * By not updating the full progress partial, but only the progress value
@mtomov
Copy link
Contributor Author

mtomov commented Nov 3, 2016

Thanks @Sinetheta and John for the elaborate look. I've indeed corrected all points that you mentioned in 31632505f4e3ced01618717a51d5be4b4401fa0f - switched to Handlebars, etc ..

Furthermore:

  1. In 8887673 - I added a small piece out of stack overflow for the translation helper in Handlebars, which allows to resolve keys with dots inside. Ex {{t 'admin.images.index.image_process_failed' }} - that wasn't possible before

  2. In 61a62ec - I've given it a try to use Bootstrap's cards with image inside. I think it looks better. From code perspective only thing I don't like is that I had to place className: 'col-sm-6 col-md-4 margin-bottom-one', inside the javascript due to Backbone wrapping the element in a div.

    I've uploaded image at the top of how the page looks with 3 boxes per row. Naturally, I can get rid of the commit if it's too much. Thanks again!

Copy link
Contributor

@jhawthorn jhawthorn left a comment

Choose a reason for hiding this comment

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

Thank you, this is excellent. Other than two minor changes this looks good to go by me.

  * With this, the full class is modeled via backbone classes
   - extract CSS out in product.css

   - extract customer facing text to en.yml

   - correct minor errors on the create.js

   - move image preview to the right of the progress bar
  - Use Handlebars instead of underscore for templating

  - User underscore's `_.each` to loop through files

  - Use a better class for dragged over state
  - Update the Handlebar's custom #t helper to handle
    string keys with dots.

    Source: http://stackoverflow.com/a/22129960/4405214
Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

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

Great. Finally we have this. Awesome work 🏋️‍♀️

@jhawthorn jhawthorn merged commit da3100c into solidusio:master Nov 7, 2016
@mtomov mtomov deleted the ajax-uploaded-images branch February 26, 2019 15:53
@Sanjana6221
Copy link

Sanjana6221 commented Jul 24, 2019

As I can see when I try to upload large image around 20 MB, I see the progress bar show 100% but the loader still running, and when I check it with the server logs it looks like paperclip is still trying to create that file.

Could you please let me know is this working as expected as it implemented?

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.

5 participants