{ ;

Using Froala to Upload Images with Laravel 5.2

After many hours of delight and then frustration, I have figured out how to use Froala with Laravel, including image upload. Let's see what I had to do.

First, You have to download froala (free for trial just has giant ugly banner until license is purchased).

Second, you must import all of the necessary css/js from your downloaded package. move the files into the proper location (for me, public/js and public/css), then link to them in the header (css) and footer (js) of your page (not all files are needed unless you use all features):

To keep it clean I created includes/_froala-js.blade.php:

  <!-- Include JS files. -->
  <script type="text/javascript" src="{{ asset('js/froala_editor.min.js') }}"></script>

  <!-- Include Code Mirror. -->
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.3.0/codemirror.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.3.0/mode/xml/xml.min.js"></script>

  <!-- Include Plugins. -->
  <script type="text/javascript" src="{{ asset('js/plugins/align.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/char_counter.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/code_beautifier.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/code_view.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/colors.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/emoticons.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/entities.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/file.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/font_family.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/font_size.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/fullscreen.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/image.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/image_manager.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/inline_style.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/line_breaker.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/link.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/lists.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/paragraph_format.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/paragraph_style.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/quick_insert.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/quote.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/table.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/save.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/url.min.js') }}"></script>
  <script type="text/javascript" src="{{ asset('js/plugins/video.min.js') }}"></script>

To continue with my clean file structure/code, I created _froala-css.blade.php:

  <!-- Include Font Awesome. -->
  <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />

  <!-- Include Editor style. -->
  <link href="{{ asset('/css/froala_editor.min.css') }}" rel="stylesheet" type="text/css" />
  <link href="{{ asset('css/froala_style.min.css') }}" rel="stylesheet" type="text/css" />

  <!-- Include Code Mirror style -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.3.0/codemirror.min.css">

  <!-- Include Editor Plugins style. -->
  <link rel="stylesheet" href="{{ asset('css/plugins/char_counter.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/code_view.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/colors.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/emoticons.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/file.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/fullscreen.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/image.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/image_manager.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/line_breaker.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/quick_insert.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/table.css') }}">
  <link rel="stylesheet" href="{{ asset('css/plugins/video.css') }}">

The _ is a reminder that this is a partial file, I believe I first heard that suggestion from Jeffrey Way. Make sure your asset is pointing to the right path to your files, this is my configuration. These two files are what hold our js and css for Froala. Now, we must actually include them in our page. By default, I believe Laravel comes with app.blade.php, but I have a file called main.blade.php, and you import like this:

// You can put this in a location of your preference, 
// but I placed it at the end of my header.
@include('frontend.includes._froala-css')


// You can put this in a location of your preference, 
// but I placed it at the end of my <body>.
@include('frontend.includes._froala-js')

This will get the scripts included in your page. Now, it is time to use the script. Just as it says on the froala documentation, it is as easy as creating a form with a textarea, and telling Fraola the ID to use:

<script>
    // Replace the <textarea id="body"> with a Froala
    // instance, using default configuration.
    $('#body, #excerpt').froalaEditor({
      toolbarButtons: ['undo', 'redo', 'html', '-', 'fontSize', 'paragraphFormat', 'align', 'quote', '|', 'formatOL', 'formatUL', '|', 'bold', 'italic', 'underline', '|', 'insertLink', 'insertImage', 'insertVideo', 'insertFile', 'insertTable']
    });
</script>

In this case, I have 2 textareas like so:

<div class="col-sm-12">
  {!! Form::label('body', 'Article Body') !!}
  {!! Form::textarea('body', null, ['class' => 'form-control', 'placeholder' => 'Enter Article Content']) !!}

  {!! Form::label('excerpt', 'Article Excerpt') !!}
  {!! Form::textarea('excerpt', null, ['class' => 'form-control', 'placeholder' => 'Enter Article Excerpt (255 character limit). An Excerpt is the preview a user will see before they click to read the entire article.']) !!}
</div>

Our script selects these two textareas by id, then passes in the toolbarButtons array with which buttons we want, as we don't need what comes by default (every button available).

All of the task up to this point was just like the Froala documentation specified, and I was delighted! Very rarely do things go this smoothly. Then I move on to being able to upload images. Wow was that a chore.

To look at why we do what I'm about to show you, we must understand how Froala uploads and accesses the uploaded image. Froala is just the frontend to interact with the server, so you must know how to access the image out of the request and move it to the correct location (plenty of tutorials on that). When you drag and drop an image, or select one, Froala submits an ajax request with the image named 'file' by default. It then expects to be returned a working link to that images new location via JSON with the format {"link":"http://example.com/img/image.jpg"} . I spent several hours on this part, not understanding why I couldnt get it to work. I did it the laravel way, but couldn't get my app to return the json, just kept saying try again. I finally stumbled across a forum post reminding me (duh) that laravel requires a token or it will refuse the request. So I had to pass a token (see line 15 below) named '_token' just as I would have to with a regular form, or it wouldn't allow me to connect:

<script>
    // Replace the <textarea id="body"> with a Froala
    // instance, using default configuration.
    $('#body, #excerpt').froalaEditor({
      toolbarButtons: ['undo', 'redo', 'html', '-', 'fontSize', 'paragraphFormat', 'align', 'quote', '|', 'formatOL', 'formatUL', '|', 'bold', 'italic', 'underline', '|', 'insertLink', 'insertImage', 'insertVideo', 'insertFile', 'insertTable'],
      heightMin: 300,
      imageMove: true,
      imageUploadParam: 'file',
      imageUploadMethod: 'post',
      // Set the image upload URL.
      imageUploadURL: '/fileUpload/post',
      imageUploadParams: {
        froala: 'true', // This allows us to distinguish between Froala or a regular file upload.
        _token: "{{ csrf_token() }}" // This passes the laravel token with the ajax request.
      }
    });
</script>

This actually allows the post request to be posted to the route you specified on the imageUploadURL option, in this case '/fileUpload/post'. Now we go to the controller.

In my case, I had a FileUploadsController where I handle all file uploads, so I went to the store method (a post request to fileUpload/post goes to FileUploadsController@store) and after the file upload succeeded, I checked to see if the file came from a froala uploader, and if so, I return the JSON it expects:

if($input['froala'] == 'true'){
     return stripslashes(response()->json(['link' => $completePath])->content());
}

Here we return a json response and title the parameter 'link' and pass in the complete path to the image (ex. http://example.com/img/image.jpg). The two things that weren't immediately obvious to me were:

  1. By default, laravel's json response includes a header, which throws an error. So we must use content() to get only the content.
  2. The json_encode() escapes the backslashes, so it comes out like this: {"link":"http:\/\/intrafile.ergon.corp\/profilePhotos\/tapp-casey-2016.jpg"} which is very weird looking first of all, but second Froala doesn't know what to do with it, so we must wrap it in stripslashes() which will place it into the proper format: {"link":"http://intrafile.ergon.corp/profilePhotos/tapp-casey-2016.jpg"}

Soon I will be implementing the image selector folder for Froala, where you can see all previous uploads, as well as delete those no longer necessary, so stay tuned!