flashman's avatar

Dropzone - on click upload!¸

Hi, sorry for this thread is not completelly for Laravel. I have one question.

I am building project management site for my company.

I have a view for adding a new task, that task can have many attachments.

I have form fields like: Task name, task description, project, category...and I would like to add another field (div) for dropzone. Ok, I have done that. I dont know JavaScript and Jquery :( I want to save image data to database. I dont know how. When I drag images they are automaticaly uploaded, thats ok, but how to get their data to save them in database, when I click on submit button.

Can I make something like: 1. I drop images on dropzone field. 2. Images are in queue 3. When I click submit button on form, images start to upload. 4.After uploading, page is refreshed, task is saved, images are uploaded.

Thanks!

0 likes
20 replies
bashy's avatar

What's your code so far?

Single or multiple images?

flashman's avatar

Thanks for reply :)

I want to upload multiple image... I have been writeing something but deleted everything.

Database: tasks_table (id, task_name, task_description...) task_images_table (id, task_id, image_name, path....)

So, I want to store task info in database, also upload images, and store image info in task_images database...

So the problem is: How to store them in db? Becouse I am not familiar with JSON, jquery....

bashy's avatar

I normally add this sort of data for the table structures

Tasks

  • tasks
    • id
    • name
    • description

Task images

  • task_images
    • id
    • label
    • filename
    • path
    • filesize
    • dimensions
    • extension

Pivot

  • tasks_task_image
    • id
    • tasks_id
    • task_images_id

If you want custom fields for each image uploaded, you'll need to include them and insert them into the Javascript for when you upload a file.

// Add on extra field to post
var fileLabel = file.previewElement.querySelector('input[name="file_label"]').value;

formData.append('file_label', fileLabel);
formData.append('_token', '{{ csrf_token() }}');
flashman's avatar

Thanks, but...you dont understand me!

I have UploadController, that uploads files in "uploads" directory...

But how to get image data of uploaded images to save them in database?

When I drop images, they are automaticaly uploaded, but image info, and task info is not saved, until I click the Submit button. When I click submit button, task is saved, but how to save images?

pobble's avatar

Generate a random temporary string and use that in hidden form field

When the images are uploaded save them into a folder with the random string name

When form is submitted, look through the folder for the uploaded images and save details to database

Move the images to where they should be

Delete the old folder

flashman's avatar

uff..to much to do! Is there a way to upload images, when I click submit? SO, when I click submit, images are uploading (with progress bar), after upload..page is refreshed?

bashy's avatar

You asked how to store them in the database so I gave you a structure :P

Always best to display the code you already have so we understand better

Appkr's avatar

Why don't you separate file save and meta-data save?

Send files through Dropzone's ajax request -> Save files at FilesController@store and in return, array of file_ids in json format-> Upon json response parse file_ids, and update hidden file_id[] field at view holding Dropzone -> Submit file_id[] and additional task-related form data -> Save task meta data in the TasksController@store and Update task_id at the FilesController@update.

Appkr's avatar

Let me leave my complete code here. Mine is poor though, you can improve more.

create_releases_table:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateReleasesTable extends Migration
{

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('releases', function (Blueprint $table) {
            $table->increments('id')->unsigned();
            $table->integer('app_id')->unsigned()->index();
            $table->string('app_name'); // deliberate duplicate for full-text search
            $table->string('package'); // deliberate duplicate for full-text search
            $table->integer('version_code');
            $table->string('version_name')->nullable();
            $table->timestamp('expires_at')->nullable();
            $table->timestamp('confirmed_at')->nullable();
            $table->text('description')->nullable();
            $table->softDeletes();
            $table->timestamps();

            $table->foreign('app_id')->references('id')->on('apps')->onUpdate('cascade')->onDelete('cascade');
        });

        DB::statement('ALTER TABLE releases ADD FULLTEXT search(app_name, package, version_name, description)');
    }


    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('releases');
    }

}

create_files_table:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateFilesTable extends Migration
{

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('files', function (Blueprint $table) {
            $table->increments('id')->unsigned();
            $table->integer('release_id')->unsigned()->index();
            $table->string('filename');
            $table->string('size');
            $table->softDeletes();
            $table->timestamps();

            $table->foreign('release_id')->references('id')->on('releases')->onUpdate('cascade')->onDelete('cascade');
        });
    }


    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('files');
    }

}

files/create.blade.php

...
<div class="upload-let">
            <!-- Form element for Dropzone -->
            <form action="{{route('files.store')}}" method="post" enctype="multipart/form-data" id="my-awesome-dropzone" class="dropzone" role="form">
                {{Form::hidden('_token', csrf_token())}}
                {{Form::hidden('release_id', $release->id)}}
                {{Form::hidden('app_id', $release->app_id)}}

                <div class="fallback">
                    <input name="file" type="file" multiple/>
                </div>
            </form>
        </div>

        {{Form::open(['route' => ['releases.approveme', $release->id], 'role' => 'form'])}}

            <p class="text-center">
                <button type="submit" class="btn btn-primary my-submit" data-loading-text="{{trans('messages.working')}}">{{trans('releases.done')}}</button>
                <a class="btn btn-default cancel-hook my-submit" data-release-id="{{$release->id}}" data-loading-text="{{trans('messages.working')}}">{{trans('releases.cancel')}}</a>
            </p>

        {{Form::close()}}
...

@section('script')

<!-- Dropzone library -->
<script src="/js/vendor/dropzone/dropzone.min.js"></script>

<script>
    (function() {

        // Dropzone initialization
        Dropzone.options.myAwesomeDropzone = {
            dictDefaultMessage: "<div class=\"text-center text-muted\"><h2>{{trans('messages.dropzone_headline')}}</h2><p>{{trans('messages.dropzone_subhead')}}</p></div>",
            maxFilesize: 100,
            addRemoveLinks: true,

            init: function () {
                this.on("error", function (file, response, xhr) {
                    utility.flashMessage(response.error, "danger", 5000);
                });

                this.on("success", function (file, response) {
                    if (response.data) utility.flashMessage("{{trans('messages.file_saved')}}", "success", 3000);
                    else utility.flashMessage(response.error, "danger", 5000);
                });
            }
        };

    })();
</script>

@stop

FilesController:

...
    /**
     * Store a newly created resource in storage.
     * POST /files
     *
     * @return \Illuminate\Http\Response
     */
    public function store()
    {
        $file = Input::file('file');

        if (! Input::hasFile('file'))
            return Response::json(['error'=>'File too big.'], 422);

        $release = Releases::find(Input::get('release_id'));
        $app = Apps::find(Input::get('app_id'));
        $directory = files_path($app->package, $release->version_code);

        try
        {
            $data = save_file($file, $directory);
            $data = array_merge($data, [
                'release_id' => $release->id
            ]);

            $this->newForm->validate($data);

            $file = $this->repo->create($data);
        }
        catch (FileAlreadyExistsException $e)
        {
            return $this->setStatusCode(400)->respond(['error' => 'Duplicate file name !']);
        }
        catch (FileSaveFailedException $e)
        {
            return $this->setStatusCode(500)->respond(['error' => 'File save failed !']);
        }
        catch (airplug\Exceptions\FormValidationException $e)
        {
            return $this->setStatusCode(422)->respond(['error' => implode(' | ', $e->getErrors())]);
        }

        Event::fire('cache.flush', ['files']);
        Event::fire('logs.record', [json_encode($file->toArray(), JSON_PRETTY_PRINT)]);

        return $this->respond(['data' => $file->toArray()]);
    }

    /**
     * Delete a given file from file system
     *
     * @param \Files $file
     * @throws Illuminate\Filesystem\FileNotFoundException
     * @return bool
     */
    public function deleteFile(\Files $file)
    {
        $app = Apps::find($file->release->app_id);
        $directory = files_path($app->package, $file->release->version_code);
        $path = $directory . '/' . $file->filename;

        if (! File::exists($path)) throw new FileNotFoundException;

        Event::fire('logs.record', ['Deleted-' . $path]);

        return File::delete($path);
    }
...

ReleasesController:

...
    /**
     * Store a newly created resource in storage.
     * POST /releases
     *
     * @return \Illuminate\Http\Response
     */
    public function store()
    {
        $data = array_except(Input::all(), ['_method', '_token']);
        $app = Apps::find($data['app_id']);
        $expires_at = ! Input::get('expires_at') ? : Carbon::now()->addMonths(2)->toDateTimeString();
        $data = array_merge($data, [
            'app_name' => $app->name,
            'package' => $app->package,
            'expires_at' => $expires_at
        ]);

        $this->newForm->validate($data);

        if (! $release = $this->repo->create($data))
        {
            Flash::error('Something went wrong! Please try again.');
            return Redirect::back()->withInput();
        }

        Event::fire('cache.flush', ['releases']);
        Event::fire('logs.record', [json_encode($data, JSON_PRETTY_PRINT)]);

        Flash::success('Metadata saved ! Attach a file(s) now !');

        return View::make('files.create')->with(compact('release'));
    }
...

helpers.php:

...
/**
 * Resize and save image file
 *
 * @param \Symfony\Component\HttpFoundation\File\UploadedFile $file
 * @param $path
 * @param string $prefix
 * @param int $width
 * @return array|bool
 */
function save_image(Symfony\Component\HttpFoundation\File\UploadedFile $file, $path, $prefix = 'img', $width = 720)
{
    $file_name = $prefix . '_' . time() . '_' . $file->getClientOriginalName();
    $save_path = $path . '/' . $file_name;

    $image = Image::make($file->getRealPath());

    if ($image->width() > $width)
    {
        $image->resize($width, null, function ($constraint)
        {
            $constraint->aspectRatio();
        });
    }

    if ($image->save($save_path))
    {
        return [
            'filename' => $file_name,
            'size' => $file->getSize()
        ];
    }

    return false;
}
...
flashman's avatar

Hi, I get this error: Uncaught TypeError: Cannot read property 'addEventListener' of null..I want to make when I cick button to submit form, images to upload.... and this is my code... add.blade.php part:

<div class="col-md-6">
   <button type="submit" id="add" class="btn btn-block btn-primary "><i class="fa fa-check-square-o"></i> Spremi</button>
  </div>

and js file

Dropzone.options.myAwesomeDropzone = { // The camelized version of the ID of the form element

  // The configuration we've talked about above
  autoProcessQueue: false,
  uploadMultiple: true,
  parallelUploads: 100,
  maxFiles: 100,

  // The setting up of the dropzone
  init: function() {
    var myDropzone = this;

    // First change the button to actually tell Dropzone to process the queue.
    this.element.querySelector("button[type=submit]").addEventListener("click", function(e) {
      // Make sure that the form isn't actually being sent.
      e.preventDefault();
      e.stopPropagation();
      myDropzone.processQueue();
    });

    // Listen to the sendingmultiple event. In this case, it's the sendingmultiple event instead
    // of the sending event because uploadMultiple is set to true.
    this.on("sendingmultiple", function() {
      // Gets triggered when the form is actually being sent.
      // Hide the success button or the complete form.
    });
    this.on("successmultiple", function(files, response) {
      // Gets triggered when the files have successfully been sent.
      // Redirect user or notify of success.
    });
    this.on("errormultiple", function(files, response) {
      // Gets triggered when there was an error sending the files.
      // Maybe show form again, and notify user of error
    });
  }

}
flashman's avatar

how to name the submit button to be recognized in js? I dont know js... :(

bashy's avatar

The error means JS can't find the element, either put the JS below the HTML or add a document.ready to it (not that great at JS myself but I know the error is that).

$(document).ready(function() {
  // Handler for .ready() called.
});
flashman's avatar

thanks, solved it! Not, next problem :) Images are uploaded...now how to handle if images are uploaded, but form validation is not working...? code: DocumentController

//Process adding new document
 public function postAdd()
 {
  if(!Document::isValid(Input::all()))
  {
   /*return Redirect::back()->with('errorMessage','Pokušajte ponovno! Dogodila se greška!')
   ->withErrors(Document::$errors);*/
   return Response::json('error',500);
  }

  $document = new Document();
  $document->title = Input::get('title');
  $document->description = Input::get('description');
  $document->project_id = Input::get('project');
  $document->user_id = Auth::user()->id;
  $document->save();

  if(Input::hasFile('file'))
  {
   $files = Input::file('file');

   foreach($files as $file)
   {
    //$file = Input::file('attachment');
    $destinationPath = 'uploads/document-attachments';
    //$filename = $file->getClientOriginalName();

    $upload_success = $file->move($destinationPath, $file->getClientOriginalName());

    if($upload_success)
    {
     $attachment = new DocumentAttachment();
     $attachment->attachmentname = $file->getClientOriginalName();
     $attachment->document_id = $document->id;
     $attachment->attachmentsize = $upload_success->getSize();
     $attachment->format = '1';
     $attachment->save();
    }

   }
  }

  return Redirect::to('documents')->with('successMessage','Dokument je dodan u sustav!');

 }

my-dropzone.js


Dropzone.options.myAwesomeDropzone = { // The camelized version of the ID of the form element // The configuration we've talked about above autoProcessQueue: false, previewsContainer: ".dropzone", uploadMultiple: true, parallelUploads: 25, maxFiles: 25, // The setting up of the dropzone init: function() { var myDropzone = this; // Here's the change from enyo's tutorial... $("#submit-all").click(function (e) { e.preventDefault(); e.stopPropagation(); myDropzone.processQueue(); } ); this.on("successmultiple", function(files, response) { window.location.href="http://pmrinels/documents"; }); this.on("complete", function(file) { myDropzone.removeFile(file); }); this.on("errormultiple", function(files, response) { }); } }

In my from I have two sections, one section for "document name, document description, document project"...other part is dropzone section...where I drop images...

1) I drop images, they are not uploading, thats good, only when I click form submit. 2) But, when I click submit, images are uploaded, document is saved in db....but no redirection. It stucks on document add form. I want: - If everithing is not ok, to Laravel process the form, redirect...show errorr.... - If is ok, show success message with Laravel.

bashy's avatar

Since Dropzone JS does separate POST requests per file, it would be best to use the Javascript for redirection on complete.

this.on("complete", function(file) {
    myDropzone.removeFile(file);
    // Add redirection code here
});
flashman's avatar

1) If I dont want to upload image, form is not posted. I need to add at least one image.
2) If images are uploaded, but form validation for other fields is not passed, it redirects, like it is passed validation. Becouse of JS.
3) Upload container is show over my form fields, how to show it in one "section"
4) :)

bashy's avatar

Ah, I didn't add a redirection on mine so couldn't say it was on full complete or per image. Do you NEED to redirect?

flashman's avatar

I need to solve these 3 questions :) last post...

bashy's avatar

Goes beyond my level of help but I put inputs inside the upload for Dropzone JS. Makes it nice for a "per image" field.

gusti's avatar

hi lukacavic, I have a same problem with upload on click with dropezone, how you could resolve this problem ? I follow your code but I can't upload it to database , did you use Appkr solution by separate file save and meta-data save? or you use another solution . can I look all your code ?

Please or to participate in this conversation.