multifile

Pretty HTML5 multiple file upload with Bootstrap, jQuery, Twig and Silex

There are a number of ways to achieve multiple file upload functionality, but I like HTML5 way of doing it, and it will be supported across all major browsers when IE10 ships. Also, Twitters’ Bootstrap helped me achieve the look without problems. I used a bit of jQuery for help with events. Alongside vanilla html, I will put Twig form syntax to achieve this, together with Symfony2 Form component, for server side.

Demo: Pretty File Boilerplate

HTML

<span class="prettyFile">
    <input type="file" name="form[files][]" multiple="multiple">
    <div class="input-append">
       <input class="input-large" type="text">
       <a href="#" class="btn">Browse</a>
    </div>
</span>

You can add required or accept attributes as needed.

CSS

.prettyFile > input { display: none !important; }
/*  The rest is from Twitter Bootstrap */
input,
.input-append { display: inline-block; vertica-align: middle; }
 
.input-large {
    border: 1px solid rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(82, 168, 236, .6);
    border-radius: 3px 0 0 3px;
    font-size: 14px;
    height: 20px;
    color: #555;
    padding: 4px 6px;
    margin-right: -4px;
    width: 210px;
}
.btn {
    background-image: -webkit-linear-gradient(top, white, #E6E6E6);
    background-repeat: repeat-x;
    border: 1px solid rgba(0, 0, 0, 0.14902);
    box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.0470588) 0px 1px 2px 0px;
    color: #333;
    display: inline-block;
    font-family: Tahoma, sans-serif;
    font-size: 14px;
    margin: 0 0 0 -1px;
    padding: 4px 14px;
    height: 20px;
    line-height: 20px;
    text-align: center;
    text-decoration: none;
    text-shadow: rgba(255, 255, 255, 0.74902) 0px 1px 1px;
    vertical-align: top;
    width: 47px;
}

Twig

<form action="/path/" {{ form_enctype(form) }} method="post" >
 
    {{ form_errors(form) }}
 
    <div>
        {{ form_label(form.files) }}
        {{ form_errors(form.files) }}
        {# Append [] to the full name of the form name - this is still an issue: https://github.com/symfony/symfony/issues/1400 #}
        {{ form_widget(form.files, { 'full_name': form.files.get('full_name') ~ '[]' }) }}
    </div>
 
    {{ form_rest(form) }}
 
    <input type="submit" />
</form>

Javascript

// Pretty file
if ($('.prettyFile').length) {
    $('.prettyFile').each(function() {
        var pF          = $(this),
            fileInput   = pF.find('input[type="file"]');
 
        fileInput.change(function() {
            // When original file input changes, get its value, show it in the fake input
            var files = fileInput[0].files,
                info  = '';
            if (files.length > 1) {
                // Display number of selected files instead of filenames
                info     = files.length + ' files selected';
            } else {
                // Display filename (without fake path)
                var path = fileInput.val().split('\\');
                info     = path[path.length - 1];
            }
 
            pF.find('.input-append input').val(info);
        });
 
        pF.find('.input-append').click(function(e) {
            e.preventDefault();
            // Make as the real input was clicked
            fileInput.click();
        })
    });
}

PHP

namespace Acme;
 
use Symfony\Component\HttpFoundation\Request;
 
class Controller
{
    public function uploadAction(Request $request)
    {
        $form    = $app['form.factory']->createBuilder('form')
                    ->add('files', 'file', array(
                        'label'     => 'Files',
                        'attr' => array(
                            'multiple'  => 'multiple',
                            // 'accept'    => 'image/*'
                        )
                    ))
                    ->getForm();
 
        if ($request->getMethod() == 'POST') {
            $form->bind($request);
 
            if ($form->isValid()) {
                $data  = $request->files->get($form->getName());
                foreach ($data['files'] as $file) {
                    // Do whatever with the file
                }
            }
 
        }
    }
}

I have used Silex /  Symfony here, but anything will do.

Todo

  • HTML5 XHR drag and drop upload
  • cross-browser testing
  • vanilla javascript implementation
  • neil

    Your styles crash the twitter bootstrap styles

  • http://www.anorgan.com Anorgan

    Hi Neil, I’ve uploaded the demo, you can find the link in the post (http://blog.anorgan.com/2012/09/30/pretty-multi-file-upload-bootstrap-jquery-twig-silex/pretty_file_boilerplate/).

    Nothing crashes, since only style is: .prettyFile > input { display: none !important; }

    If you want, you can add a class to the file input and rewrite the selector as .prettyFileInput { ... }.

  • Alle

    I’m not using Symfony, how about PHP Basic ?

  • http://www.anorgan.com Anorgan

    Hi Alle, this is not Symfony specific, I just used it as an example. For multiple file upload using “vanilla” PHP, consult the documentation: http://php.net/manual/en/features.file-upload.multiple.php

  • http://enargentine.wordpress.com bertin laurent

    Hi, i’ve tried your code with my symfony 2.1 install but i got an error on the line where it binds the form:
    “Expected argument of type “array”, “Symfony\Component\HttpFoundation\Request” given

    any idea ?

  • http://www.anorgan.com Anorgan

    Hi Bertin, do you mean the “$form->bind()” part? It should work by passing the request instead of the array as per http://symfony.com/blog/form-goodness-in-symfony-2-1#no-more-bindrequest – you sure you have 2.1? :)

    Anyway, follow the guide on http://symfony.com/doc/current/book/forms.html#handling-form-submissions and you should be OK.

  • Onikaru

    Hi, I’m using Symfony2 and I would like to integrate this uploader in my project. How should I proceed ?
    I tried by creating the uploadAction() in my controller and a template “upload.html.twig”, but it doesn’t work.
    What should I do ?

  • http://upload.gae-init.appspot.com Lipis

    Would be nice to support the Bootstrap 3 as well, since the RC1 is out there. getbootstrap.com

  • Dormilich

    As of Symfony 2.5 (that’s what I used) the form widget gets appending the `[]` to the name right if you add the `multiple` option (not the attribute, but that’s included as well).