Utilize flowjs as an Angular Directive

Mike BerrymanI’ve been working on an angular project that has a requirement to allow users to upload documents, pictures and videos to the site.  As a part of the upload process, after the user has selected their file, but before it’s actually uploaded to the server, the user needs to supply some metadata about the file.  Being a responsive, highly stylized site, the client didn’t want the standard input control with type=”file”. Instead they wanted the user to click an “Add file”-type button that would pop up the explorer window for the user to pick a file, and then once the file is picked, prompt the user for the required metadata before performing the actual upload.

I ended up including flowjs (https://github.com/flowjs) in the project, which is quite a nice file upload library, and is usable as an angular directive!  Flowjs has a lot of nice uploading features, but this client had its own custom upload process, so I couldn’t use flowjs for the uploading piece. This meant all I ended up really using it for was to handle prompting the user to select the file, and limiting the file selection options (just images, just videos, all, etc).  It worked great – or so we thought.
Flowjs accomplishes prompting the user to pick a file to upload by dynamically inserting an input field with type=”file” into the page and hiding it.  Then, when the user performs whatever action should trigger the file selection (usually a click event) flowjs would programmatically click the file input field.  This was working great, until I found a bug with the file input field.
Like most field controls in HTML, the way a developer determines if something has changed in the field is listening for the “change” event of the field.  The same is true for the file input field.  Flowjs would trigger an event when the user picks a file by listening for “change” on the file input.  I was listening for this event from flowjs in order to pop up a dialog window prompting the user for metadata about the file.  What was discovered, however, was if the user accidentally closed the dialog window then tried to pick the SAME file they had just picked, the metadata prompt wouldn’t open again!  If the user selected a different file, it would work just fine – it was only if the same file was selected twice in a row that this issue would occur.
The first thing I investigated was flowjs.  Very quickly I realized it wasn’t flowjs that was the issue, it was the file input control itself.  Thinking about it, it makes complete sense – we only know the user selected a file if the “change” event on the file input control fires.  If the user selects the same file that is already selected in the control, obvious nothing changes, right?  So the “change” event wouldn’t occur.  The answer seemed obvious at that point – just clear out the field after the user is done with it.
It ended up not being that simple.  For whatever reason, even if you clear the input file’s value (thus making it LOOK like it’s been reset), the “change” event STILL wouldn’t fire if the user selected the same file again.  A little research quickly revealed that we were far from the first to notice this counter-intuitive behavior.  One of the workarounds I found was to actively remove the file input from the DOM, then dynamically create a new one and insert it, effectively replacing the file input with a “fresh” version.  This of course meant I was now looking to do things outside of what flowjs provides, so it was time to write my own directive.
Since all we had ended up using flowjs for was just handling the user selecting the file, that’s all I needed my new directive to do as well.  I called it the highly original name, “file-picker”.  To start with, I wanted to it to work the same as flowjs’s directive, where you just attach it to the relevant DOM element and a hidden file input field would be added into the DOM.  For my purposes, it only needed to work with buttons (<button> or <input type=”button”>) so that’s what I limited my directive to, but theoretically it could work any DOM element as long as there’s an event to listen for to trigger the file input.  I didn’t really like the idea of removing the file input control and dynamically recreating it every time a user selected a file because that would mean reattaching all my events each time.  Instead, I found a much cleaner way of “resetting” the file input control.  When the “change” event fired on the file input control (IE the user selected a file), I would store the file in a javascript variable, then wrap the file input in a form using jquery’s wrap() function.  Once wrapped, I would call the reset() function on that form, which resets all the fields in the form to their pristine state.  Since the only thing in the form is the file input control, it would reset while I still have the file stored in my javascript variable.  From there, I’d trigger a “fileSelected” event that the user would supply to the directive, passing the file along.
This worked beautifully.  Now the user could select the same file as many times as they wanted in a row and the “change” event would fire correctly every time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s