vsivsi / meteor-file-collection

Extends Meteor Collections to handle file data using MongoDB gridFS.
http://atmospherejs.com/vsivsi/file-collection
Other
159 stars 37 forks source link

Multiple browse/drop targets with different callbacks? #71

Open edemaine opened 8 years ago

edemaine commented 8 years ago

In my application, there are multiple posts, and each post (rendered by its own template) should have its own browse button/drop target to attach files to the post.

Is this possible with file-collection? The package looks really great, except possibly for this issue, possibly caused by a limitation of Resumable.js. assignBrowse and assignDrop accept multiple targets, which is annoying because each post is its own template, but workable -- but then on('fileAdded') doesn't seem to include in the event which one it came from. Perhaps the "right" answer would be to have multiple Resumable instances -- is this possible with (or easy to add to) file-collection?

vsivsi commented 8 years ago

Hi, sure this should be possible with a little customization. The built-in resumable.js instance support is really just a convenience for the most common case.

In your case, I think that you can just setup however many additional resumable.js instances a given page needs, being careful to configure each of them to be compatible with the server-side resumable endpoint: https://github.com/vsivsi/meteor-file-collection/blob/master/src/resumable_client.coffee#L28-L41

edemaine commented 8 years ago

Thanks; this worked. This is probably obvious to you, but I had to set resumable: true in the FileCollection call, even though I'm creating my own Resumable -- because otherwise the server side isn't setup, and the calls fail. Just an extra Resumable hanging around though, so not a big deal.

I'm also wondering whether I could capture the drop target / browse button events myself, mark the files with attributes about where they came from, and send them to a global resumable. I might try that next.

edemaine commented 8 years ago

This also worked well, and is perhaps simpler -- it enables global upload limits etc. Some sample CoffeeScript code:

Template.messageAttach.events
  'change .attachInput': (e, t) ->
    for file in e.target.files
      file.parentMessage = t.data.messageId
      Files.resumable.addFile file, e
...
Files.resumable.on 'fileAdded', (file) ->
  Files.insert
    _id: file.uniqueIdentifier    ## This is the ID resumable will use.
    filename: file.fileName
    contentType: file.file.type
    metadata: parentMessage: file.file.parentMessage
  , (err, _id) ->
      if err
        console.error "File creation failed:", err
      else
        Files.resumable.upload()

Speaking of the last few lines (based on your sample code), could you explain why it's OK to call resumable.upload() after a single file is inserted into Mongo? I'm imagining that, if I select multiple files, several fileAdded events get triggered, and then these insert operations get queued, and I'll get the callback for one before possibly the others have finished, so before the Mongo entry is there for resumable to "append" to. But I've never seen this problem actually arise -- it seems like the files get uploaded sequentially even though I set simultaneousUploads to 10... Well, at least it works!

vsivsi commented 8 years ago

Hi, I've also never seen a problem with that call to resumable.upload(). Resumable doesn't seem to have a way to start uploading a single file, and there's no way (that I'm aware of) to get a count of the files that were dropped. So, yeah, seems a little brittle... The worst case is that a chunk or two would fail to upload until the file record was inserted, but resumable should retry those and recover. Something to think about though....

dpatte commented 8 years ago

edemaine, I am in a similar situation as you. Let me see if I understand your 2nd solution...

You intercept the attachInput message, and pass a messageId in the parent message of each file and do your own addFile call. I assume you also do the same for drop?

Then in file added you pull the parentMessage and use that to determine your next steps?

edemaine commented 8 years ago

@dpatte: Yes, that's exactly what I'm doing. And I do the same for drop. Here's some updated CoffeeScript code, which enables dragging onto an upload button, or clicking the upload button which triggers the browse of the file form input. Id and group fields of the template go into file metadata.

Template.messageAttach.events
  "click .uploadButton": (e, t) ->
    e.preventDefault()
    e.stopPropagation()
    t.find(".#{input}").click()
  "change .uploadInput": (e, t) ->
    attachFiles e.target.files, e, t
    e.target.value = ''
  "dragenter .uploadButton": (e) ->
    e.preventDefault()
    e.stopPropagation()
  "dragover .uploadButton": (e) ->
    e.preventDefault()
    e.stopPropagation()
  "drop .uploadButton": (e, t) ->
    e.preventDefault()
    e.stopPropagation()
    attachFiles e.originalEvent.dataTransfer.files, e, t

attachFiles = (files, e, t) ->
  message = t.data._id
  group = t.data.group
  for file in files
    file.callback = (file2) ->
      Meteor.call 'messageNew', group, message, null,
        format: 'file'
        title: file2.fileName
        body: file2.uniqueIdentifier
    file.group = group
    Files.resumable.addFile file, e

Jade template looks like this: (Let me know if you need HTML.)

template(name="messageAttach")
  input.attachInput(type="file", style="display:none", multiple)
  button.btn.btn-default.attachButton Attach