UPDATE 9-18-2014: I stopped using cabinet and have been using stapler instead... Just FYI. This changes your controller and model a little, but it was pretty easy to figure out.

I ended up using cabinet with http://blueimp.github.io/jQuery-File-Upload/ for my forms. By default cabinet comes with dropzone as a way to upload files.

This is great, but I wanted to be able to add an upload field to any of my forms and have the uploads associated w/ the data in the form. So to do that, I don't even use cabinets default upload method. I use cabinet to save and delete files with ajax.

In this example, I will have a model called Institution (basically just a form with information about a school). On this form, I want to upload the institutions logo.

The institution database looks somethign like:

    // creates the institutions codes table
    Schema::create('institutions', function(Blueprint $table) {
        $table->increments('id')->unique();
        $table->string('title');
        $table->text('body');
        $table->string('topcolor');
        $table->string('topfontcolor');
        $table->string('logo');
        $table->text('extracomments');
        $table->timestamps();
        $table->softDeletes();
    });

In the "logo" string, I am only storing the file ID that comes from cabinet. Not the file name/path like you normally would in a form. I'll let cabinet handle that stuff.

Front End Stuff

Here is my create institution blade template:

@extends('templates.main') <!-- this is just header,footer, css & js-->

@section('title')
Create Institution
@endsection

@section('content')

    <div class="page-header">
        <h1 class="text-center">Create Institution</h1>
    </div>

    @include('institutions.form') 

@endsection

@include('institutions.form') is where the form stuff is at. I separated this out into its own template file so that I can use the same form for create and edit.

Here is my form blade template (its using former):

<div class="row">
    <div class="col-md-12">

        {{ Former::open_for_files()->id('inst_create')->rules(['title' => 'required'])->method('post') }}
        {{ Former::text('title')->autofocus()->help('Title of the institution') }}
        {{ Former::text('body')->label('Top Welcome Message') }}
        {{ Former::text('topcolor')->label('Top color')->class('form-control minicolors') }}
        {{ Former::text('topfontcolor')->label('Top Text color')->class('form-control minicolors') }}
        <div class="form-group">
            <label for="" class="control-label col-lg-2 col-sm-4">Logo Upload</label>
            <div class="col-lg-10 col-sm-8">
                @include('uploads.form')
            </div>
        </div>
        {{ Former::textarea('extracomments')->label('Extra Comments') }}
        {{   Former::actions()->large_primary_submit('Submit')
        ->large_inverse_reset('Delete') }}
        {{Former::close()}}
        <div id="addjunk"></div>
    </div>
</div>

Most of the form is just normal form stuff... text fields, text areas, etc... The part we care about is @include('uploads.form').

I created yet another template... this contains only the upload text field. That way I can use that template on any other form on the site and not have to duplicate code (DRY).

My institution edit.blade.php:

@extends('templates.main')

@section('title')
Create Institution
@endsection

@section('content')
<div class="page-header">
    <h1 class="text-center">Create Institution</h1>
</div>
{{ Former::populate( $institution ) }}
@include('institutions.form')
<script>
    var existingfiles = {{$json}};
</script>
@endsection

This is almost the same as create.blade.php with 2 extra thigns added. I added {{ Former::populate( $institution ) }} before the form so it will automatically populate the form (cool hu) and I added

<script>
    var existingfiles = {{$json}};
</script>

So I can prepopulate my previously uploaded file.

Here is my upload/form.blade.php:

<!-- The file upload form used as target for the file upload widget -->
<div id="fileupload">
        <!-- Redirect browsers with JavaScript disabled to the origin page -->
        <noscript><input type="hidden" name="redirect" value="http://blueimp.github.io/jQuery-File-Upload/"></noscript>
        <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
        <div class="row fileupload-buttonbar">
            <div class="col-lg-7">
                <!-- The fileinput-button span is used to style the file input field as button -->
                <span class="btn btn-success fileinput-button">
                    <i class="glyphicon glyphicon-plus"></i>
                    <span>Add files...</span>
                    <input type="file" name="file" multiple>
                </span>
                <button type="submit" class="btn btn-primary start">
                    <i class="glyphicon glyphicon-upload"></i>
                    <span>Start upload</span>
                </button>
                <button type="reset" class="btn btn-warning cancel">
                    <i class="glyphicon glyphicon-ban-circle"></i>
                    <span>Cancel upload</span>
                </button>
                <button type="button" class="btn btn-danger delete">
                    <i class="glyphicon glyphicon-trash"></i>
                    <span>Delete</span>
                </button>
                <input type="checkbox" class="toggle">
                <!-- The global file processing state -->
                <span class="fileupload-process"></span>
            </div>
            <!-- The global progress state -->
            <div class="col-lg-5 fileupload-progress fade">
                <!-- The global progress bar -->
                <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100">
                    <div class="progress-bar progress-bar-success" style="width:0%;"></div>
                </div>
                <!-- The extended global progress state -->
                <div class="progress-extended">&nbsp;</div>
            </div>
        </div>
        <!-- The table listing the files available for upload/download -->
        <table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
    </div>

<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
    <tr class="template-upload fade">
        <td>
            <span class="preview"></span>
        </td>
        <td>
            <p class="name">{%=file.name%}</p>
            <strong class="error text-danger"></strong>
        </td>
        <td>
            <p class="size">Processing...</p>
            <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="progress-bar progress-bar-success" style="width:0%;"></div></div>
        </td>
        <td>
            {% if (!i && !o.options.autoUpload) { %}
                <button class="btn btn-primary start" disabled>
                    <i class="glyphicon glyphicon-upload"></i>
                    <span>Start</span>
                </button>
            {% } %}
            {% if (!i) { %}
                <button class="btn btn-warning cancel">
                    <i class="glyphicon glyphicon-ban-circle"></i>
                    <span>Cancel</span>
                </button>
            {% } %}
        </td>
    </tr>
{% } %}
</script>
<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
    <tr class="template-download fade">
        <td>
            <span class="preview">
                {% if (file.thumbnailUrl) { %}
                    <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" data-gallery><img src="{%=file.thumbnailUrl%}"></a>
                {% } %}
            </span>
        </td>
        <td>
            <p class="name">
                {% if (file.url) { %}
                    <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a>
                {% } else { %}
                    <span>{%=file.name%}</span>
                {% } %}
                {% if (file.fileID) { %}
                    <input class="upload-field-ids" type="hidden" name="fileid[]" value="{%=file.fileID%}">
                {% } %}
            </p>
            {% if (file.error) { %}
                <div><span class="label label-danger">Error</span> {%=file.error%}</div>
            {% } %}
        </td>
        <td>
            <span class="size">{%=o.formatFileSize(file.size)%}</span>
        </td>
        <td>
            {% if (file.deleteUrl) { %}
                <button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}>
                    <i class="glyphicon glyphicon-trash"></i>
                    <span>Delete</span>
                </button>
                <input type="checkbox" name="delete" value="1" class="toggle">
            {% } else { %}
                <button class="btn btn-warning cancel">
                    <i class="glyphicon glyphicon-ban-circle"></i>
                    <span>Cancel</span>
                </button>
            {% } %}
        </td>
    </tr>
{% } %}
</script>

The above is taken directly from the "basic plus ui" example on http://blueimp.github.io/jQuery-File-Upload/.... with 1 exception. I added:

            {% if (file.fileID) { %}
                <input class="upload-field-ids" type="hidden" name="fileid[]" value="{%=file.fileID%}">
            {% } %}

This adds an input field everytime a file is uploaded with the fileID from cabinet (if I passed it back to my form in my upload controller, you will see that down below).

This fileID is what we will store in the database for the "logo" string in institutions model.

The last thing we need on frontend stuff is CSS and JS

This part was actually kind of a pain. Here my tip.... load your JS files in the right order

Load them in the order they are shown in http://blueimp.github.io/jQuery-File-Upload/. Not to complicate things more, but I used my gulpfile to add these.

I recommend adding the JS them directly to your templates first before you do anything fancy with an asset manager.

The other thing that might screw you up is if the noscript css is loading when it shouldn't be.

My javascript for jQuery file upload

Here it is:

/*
 * jQuery File Upload stuff
 * jQuery minicolors stuff
 */

/* global $, window */

$(function () {
    'use strict';
    var jupload = $('#fileupload');
    // Initialize the jQuery File Upload widget:
    jupload.fileupload({
        // Uncomment the following to send cross-domain cookies:
        //xhrFields: {withCredentials: true},
        url: '/upload/store'
    });

    // from http://stackoverflow.com/a/21728472
    if (typeof existingfiles !== 'undefined'){
        jupload.fileupload('option', 'done').call(jupload, $.Event('done'), {result: existingfiles});
    };

});

In the code above, you set your "store" route for cabinet at url: '/upload/store'.

The code below is used on an edit page. It basically loads the images into the form on an edit page. Up above in the edit.blade.php I added existingfiles which is used here.

// from http://stackoverflow.com/a/21728472
if (typeof existingfiles !== 'undefined'){
    jupload.fileupload('option', 'done').call(jupload, $.Event('done'), {result: existingfiles});
};

Backend Stuff

My store method in UploadController.php

/**
   * Stores new upload
   *
   */
  public function store()
  {
      $file = Input::file('file');

      $upload = new Upload;


      try {
          $upload->process($file);
      } catch(Exception $exception){
          // Something went wrong. Log it.
          Log::error($exception);
          $error = array(
              'name' => $file->getClientOriginalName(),
              'size' => $file->getSize(),
              'error' => $exception->getMessage(),
          );
          // Return error
          return Response::json($error, 400);
      }

      // If it now has an id, it should have been successful.
      if ( $upload->id ) {
          $newurl = URL::asset($upload->publicpath().$upload->filename);

          // this creates the response structure for jquery file upload
          $success = new stdClass();
          $success->name = $upload->filename;
          $success->size = $upload->size;
          $success->url = $newurl;
          $success->thumbnailUrl = $newurl;
          $success->deleteUrl = action('UploadController@delete', $upload->id);
          $success->deleteType = 'DELETE';
          $success->fileID = $upload->id;

          return Response::json(array( 'files'=> array($success)), 200);
      } else {
          return Response::json('Error', 400);
      }
  }

The $success object above is used to create a json response that looks like whats defined in the documentation here: https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#using-jquery-file-upload-ui-version-with-a-custom-server-side-upload-handler

My delete method in UploadController.php:

public function delete($id)
{
    $upload = Upload::find($id);
    $upload->delete();

    $success = new stdClass();
    $success->{$upload->filename} = true;

    return Response::json(array('files'=> array($success)), 200);
}

I added an extra method to my Upload model to easly get the public path:

// return a path without starting with /public
//   the path begins and ends in a / (slash)
public function publicpath() {
    return str_replace('/public/', '/', URL::asset($this->path) . '/');
}

My store method in InstitutionsController.php:

/**
     * Store a newly created institution in storage.
     *
     * @return Response
     */
    public function store()
    {
        //$alldata = Input::all();/*   this gets all*/
        $data = Input::only('title', 'body', 'topcolor', 'topfontcolor','currentdi','extracomments');
        $data['logo'] = json_encode(Input::get('fileid'));

        // validation rules
        $rules = array(
            'title'      => 'required',
            'logo' => 'required',
        );
        $validator = Validator::make($data, $rules);

        if ($validator->fails())
        {
            return Redirect::back()->withErrors($validator)->withInput();
        }

        // @todo: I need to move the file out of tmp to someplace else.
        Institution::create($data);

        // @todo: route to the "show" of the institution
        return Redirect::route('institutions.index');
    }

You can see that my form does not submit a logo field. It instead submits a fileid field (this could be 1 or more ID's).

I convert my fileid's to json to save in my logo field with $data['logo'] = json_encode(Input::get('fileid'));.

My edit method in InstitutionsController.php:

/**
 * Show the form for editing the specified institution.
 *
 * @param  int  $id
 * @return Response
 */
public function edit($id)
{
    $institution = Institution::find($id);
    $fileids = json_decode($institution->logo);

    $files = array();

    foreach ($fileids as $fileid) {
        $upload = Upload::find($fileid);
        if (isset($upload)){
            $newurl = URL::asset($upload->publicpath().$upload->filename);

            $success = new stdClass();
            $success->name = $upload->filename;
            $success->size = $upload->size;
            $success->url = $newurl;
            $success->thumbnailUrl = $newurl;
            $success->deleteUrl = action('UploadController@delete', $upload->id);
            $success->deleteType = 'DELETE';
            $success->fileID = $upload->id;
            $files[] = $success;
        }
    }

    $json = json_encode(array('files'=> $files));

    return View::make('institutions.edit', compact('institution','json'));
}

For edit, I created a $files array that stores a $success object for each file that has been uploaded. This $files array is sent to the blade template as json.

The update method:

/**
 * Update the specified resource in storage.
 *
 * @param  int  $id
 * @return Response
 */
public function update($id)
{
    $institution = Institution::findOrFail($id);

    //get form data
    $data = Input::only('title', 'body', 'topcolor', 'topfontcolor','currentdi','extracomments');
    $data['logo'] = json_encode(Input::get('fileid'));

    // validation rules
    $rules = array(
        'title'      => 'required',
        //'body'     => '',
        //'topfontcolor'  =>'required|between:4,8',
        //'topcolor'  => 'required|between:4,8',
        'logo' => 'required',
        //'currentdi' => '',
        //'extracomments' => '',
    );
    $validator = Validator::make($data, $rules);

    if ($validator->fails())
    {
        return Redirect::back()->withErrors($validator)->withInput();
    }

    $institution->update($data);

    return Redirect::to_action('InstitutionsController@show', array($id));
}

The End

I know I missed a bunch of stuff... but this should get you a good head start.

Just in case your still confused, here is a summary:

For a "create" type of form:

  • institution/create.blade.php displays a form with jQuery file upload javascript on it.
  • files are uploaded by sending ajax to UploadController@store.
  • UploadController@store returns json that jQuery file upload uses to add an input field with the name of fileid[]
  • When the institution is saved... it POSTS the form data (including the fileid's) to InstitutionsController@store

For an "edit" type of form:

  • InstitutionsController@edit loads the Institution model based on institution ID
  • InstitutionsController@edit loads the files based on fileIDs stored in the logo database field. These fileid's are sent to the template.
  • institution/edit.blade.php displays a form with jQuery file upload javascript on it and loads in the fileIDs showing the previously uploaded files.
  • files are uploaded by sending ajax to UploadController@store.
  • UploadController@store returns json that jQuery file upload uses to add an input field with the name of fileid[]
  • When the institution is saved... it POSTS the form data (including the fileid's) to InstitutionsController@edit

Lastly, you should add some kind of AUTH to this. The way it stands now, anyone can add and upload stuff.