What We Do

Company

Resources

Events

Blog

Free Consultation

ahoy@headway.io

(920) 309 - 5605

3 min
How to use Trix and Shrine for WYSIWYG Editing with Drag-and-Drop Image Uploading
Subscribe

How to use Trix and Shrine for WYSIWYG Editing with Drag-and-Drop Image Uploading

Noah Settersten
Senior Developer
wysiwyg drag and drop

## Why Would WYSIWYG Be Helpful for a client?

Whatever your level of technical experience, being able to edit content while seeing the live results can be a huge time saver. WYSIWYG editing (for What You See Is What You Get) can be a much more intuitive experience for clients as well, since no HTML or markdown experience is necessary and they can work much as they would in a word processing tool like Google Docs.

Recently, at Headway we have encountered a number of situations where integrating WYSIWYG editing has been a necessity for our projects. In addition to the text and content formatting this allowed for our users, the ability to drag and drop images and files directly into the editor has been a great extension to the basic functionality.

Within the Ruby on Rails ecosystem there are myriad tools for accomplishing this goal, but below we'll focus on using Trix for WYSIWYG editing and Shrine for file uploading within a Ruby on Rails 5 web app.

### How Trix Helps You Edit Dynamic Content.

[Trix](http://trix-editor.org) is a WYSIWYG editing gem that came out of work on the Basecamp web application. It has a narrow focus for features, and is simple and reliable as a result. Integrating it into a Rails application is very easy.

### How Shrine Helps You Make File Uploads Happen

Ruby as well as Rails has a long history of tools for uploading and processing files. [Shrine](http://shrinerb.com) is actively maintained and emerging as a frontrunner for these tools today.


## Building It:

### Install and Configure Shrine and Trix Dependencies

1. Setup Shrine:

First, add the Shrine gem to your application's `Gemfile`:

   -- CODE line-numbers language-rb --

   <!--
     gem 'shrine'
   -->

Then, create a file at `config/initializers/shrine.rb` within your app for setting up Shrine's options. Here we'll use local filesystem storage for uploaded files, but in a production environment using S3 or another cloud storage is a great option:

   -- CODE line-numbers language-rb --

   <!--
     require 'shrine'
     require 'shrine/storage/file_system'
 
     Shrine.storages = {
       # temporary storage
       cache: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/cache'),
 
       # permanent storage
       store: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/store'),
     }
 
     Shrine.plugin :activerecord
     Shrine.plugin :cached_attachment_data # for forms
   -->

Lastly, create an uploaded class in `app/uploaders/photo_uploader.rb`:

   -- CODE line-numbers language-rb --

     <!--
       class PhotoUploader < Shrine
       end
     -->

2. Setup Trix:

Add Trix to your `Gemfile`:

   -- CODE line-numbers language-rb --

     <!--
       gem 'trix'
     -->

Add the JS and CSS requires to your manifest files:

`app/assets/stylesheets/application.css`:

   -- CODE line-numbers language-css --

   <!--
     *= require trix
   -->

`app/assets/javascripts/application.js`:

   -- CODE line-numbers language-js --

   <!--
     //= require trix
   -->

For any form fields you'd like add WYSIWYG editing to, use the `trix_editor` form helper:

   -- CODE line-numbers language-rb --

   <!--
     f.trix_editor :body
   -->

### Create an Image Resource for Uploads and an Associated Controller

1. Next, let's create a general Image model that will represent our uploaded images:

Run: `rails generate model image image_data:text`

Within `app/models/image.rb` add the following line to set it up for Shrine uploads:

   -- CODE line-numbers language-rb --

   <!--
     # adds an `image` virtual attribute
     include ::PhotoUploader::Attachment.new(:image)
   -->

2. For our purposes, we only need to have one controller to handle the image upload actions. We're only using this new controller for storing the uploaded images and then returning the url of the saved file. Trix will take it from there.

Create a new controller at `app/controllers/images_controller.rb` with the following code:

   -- CODE line-numbers language-rb --

   <!--
   class ImagesController < ApplicationController
     respond_to :json
 
     def create
       image_params[:image].open if image_params[:image].tempfile.closed?
 
       @image = Image.new(image_params)
 
       respond_to do |format|
         if @image.save
           format.json { render json: { url: @image.image_url }, status: :ok }
         else
           format.json { render json: @image.errors, status: :unprocessable_entity }
         end
       end
     end
 
     private
 
     def image_params
       params.require(:image).permit(:image)
     end
   end
   -->

This controller sets up a basic `create` action that we can POST to via JavaScript. If the image saving is successful, it then returns back JSON with the destination URL of the saved image. We'll use this returned JSON later in our JavaScript to send the file's location to Trix.

3. Lastly, to finish setting up our image uploading we need to add a route where we can POST to this action. Add the following to:

`config/routes.rb`

   -- CODE line-numbers language-rb --

   <!--
     resources :images, only: [:create]
   -->

### Add JavaScript Handling for Trix:

When a file is dragged and dropped onto the Trix editor, it fires a JavaScript event with the file information and content. Now that we have all of the necessary Rails model and controller setup in place, the final step to wire everything together is to add JavaScript to handle this attachment event and upload the file to our server.

1. Create the following file:

`app/assets/javascripts/trix_attachments.js`

   -- CODE line-numbers language-js --

   <!--
     $(document).ready(function() {
       Trix.config.attachments.preview.caption = {
         name: false,
         size: false
       };
 
       function uploadAttachment(attachment) {
         var csrfToken = $('meta[name="csrf-token"]').attr('content');
         var file = attachment.file;
         var form = new FormData;
         var endpoint = "/images";
         form.append("Content-Type", file.type);
         form.append("image[image]", file);
 
         xhr = new XMLHttpRequest;
         xhr.open("POST", endpoint, true);
         xhr.setRequestHeader("X-CSRF-Token", csrfToken);
 
         xhr.upload.onprogress = function(event) {
           var progress = event.loaded / event.total * 100;
           return attachment.setUploadProgress(progress);
         };
 
         xhr.onload = function() {
           if (this.status >= 200 && this.status < 300) {
             var data = JSON.parse(this.responseText);
             return attachment.setAttributes({
               url: data.url,
               href: data.url
             });
           }
         };
 
         return xhr.send(form);
       };
 
       document.addEventListener("trix-attachment-add", function(event) {
         var attachment = event.attachment;
         if (attachment.file) {
           return uploadAttachment(attachment);
         }
       });
     });
   -->

Our JavaScript attaches to Trix's `trix-attachment-add` event that gets called whenever a file is dropped onto the editor. We then take that file and use an XMLHttpRequest to upload it to the server asynchronously. If the server returns a successful status, we parse the URL of the uploaded file from the response's JSON and return it back for Trix to use.

2. Now, include this new JavaScript file in your main manifest file:

`app/assets/javascripts/application.js`:

   -- CODE line-numbers language-rb --
   <!--
         //= require trix_attachments
   -->


## See It All in Action

Spin up your server if you haven't already, and then browse to the page where you added the Trix editor tag. Dropping an image onto the editor will POST to your server, create an Image resource with the attached image file, and then send the updated URL back to Trix. When you save the Trix-enabled form, the uploaded Image's URL will then be saved within the string content of that field. Now the tools for dynamically editing content can be in your client's hands!


## Other Resources

- [Trix's Official Documentation on File Uploads](https://github.com/basecamp/trix#storing-attached-files)
- [GoRails Video Tutorial](https://gorails.com/episodes/trix-editor?autoplay=1)

Asking Better Questions About Your Product

Download our free guide to begin implementing feedback loops in your organization.

By filling out this form, you agree to receive marketing emails from Headway.

Scaling products and teams is hard.

In this free video series, learn how the best startup growth teams overcome common challenges and make impact.

Scaling products and teams is hard.

In this free video series, learn how the best startup growth teams overcome common challenges and make impact.

You don’t need developers to launch your startup

In this free video series, learn proven tactics that will impact real business growth.

By filling out this form, you agree to receive marketing emails from Headway.

Make better decisions for your product

Dive deeper into the MoSCoW process to be more effective with your team.

By filling out this form, you agree to receive marketing emails from Headway.

A mindset for startup growth

In this free video series, learn the common mistakes we see and give yourself a greater chance for success.

By filling out this form, you agree to receive marketing emails from Headway.

The ultimate UX audit kit

Everything you need for a killer DIY audit on your product.

  • UX Audit Guide with Checklist
  • UX Audit Template for Figma
  • UX Audit Report Template for Figma

Enjoyed this post?

Other related posts

See all the ways we can help you grow through design, development, marketing, and more.

View All

Listen and learn from anywhere

Listen and learn from anywhere

Listen and learn from anywhere

The Manifest

Level up your skills and develop a startup mindset.
Stay up to date with the latest content from the Headway team.