Skip to main content

28 Using the Trix Editor plus File Upload Attachments

Episode 209 · October 3, 2017

Learn how to use the Trix editor for editing wysiwyg content and upload attachments like images using Javascript and Shrine

Gems Javascript


Transcripts

What's up guys? This episode we're going to be talking about the basecamp Trix editor, which is a WYSIWYG editor. It isn't markdown or anything like that, it generates html and we're going to talk about implementing this and adding file uploads to it in your rails app. This is really pretty simple to implement, and it's especially simple if you're not looking to do file uploads, all of this is really really easy to just drop in, it is one of the easiest editors, and it has a really good API, so if you want to add more buttons to do different features, that are custom to your applications, you can write all of that with some really easy to use JavaScript, and they have good examples of how you implement things like using blockquotes and code and all that stuff, you can see source of all of these and it has undo and redo functionality, which is really handy.

So we're going to be implementing this in a very simple application that I have right here, we have posts, they have a title and a body that we want to be able to format, and so we're going to be implementing Trix, and I'm going to leave this text area visible for you. Normally, this becomes a hidden field instead, and Trix will become the UI, but in this case we're going to use this text area, so you can actually watch Trix at work, and you can see what it's generating as the html gets edited. So it's pretty cool, and then we're going to go add photo uploading to it, but we're going to do that in a little bit because we want to just get Trix up and running first.

Now of course, this comes with a gem, called Trix. All you have to do is install that, run bundle and start up your rails server again, and then we want to go into our

application.scss

//*= require trix 

application.js

//= require trix

Then, if we go to our post form, we can go to the area where we have that body, and then it's as simple as adding:

app/views/posts/_form.html.erb

<div class="field">
    <%= form.label :body %>
    <%= form.text_area :body, id: post_body %> 
    <trix-editor input>
</div>

it will attach to that and then use that content that's in there, so let's take a look at that and we'll close off that tab, and refresh our page now, and that is as simple as it is to install trix. Now, it's going to take a little bit to compile that JavaScript and everything and scss again, but now we have trix here, and if we type something. "Hello world", you'll see it visibly right here, so that's really cool, so let's expand this a little bit, so you can see that, if we go in bold "Hello world", you'll see that it inserts a **strong** tag around hello world, you can go do the same thing for that, and you can go edit a bunch of stuff, and you'll see that this will try to generate the best version of the html based upon what the user has done, and then you can hit the undo and redo buttons to go back and forth and see your history, that's pretty cool and it works really simply, that's all you have to do to get it up and running, and normally, of course you're going to want to change this from a text area to just a hidden field, and you'll be good to go, so we know that it's updating the content of that hidden field now, so if we were to type "Hello World" here, and let's make "world" bold and just have a title of "Test", we then need to go into our **show.html.erb**, now that we have content here, that's html, we're going to want to sanitize this, don't ever use html safe or raw here because if someone put JavaScript in there maliciously or any other tags that you don't want, then they will be able to attack other users on your site, so always use sanitize and make sure that you sanitize that content. Now if we create this post, you'll see that "world" is bold and all of that is being rendered out exactly like we saw in the text editor, so that's really cool, and it's generating the divs and the strong tags and all of that just like we saw in that text area, everything works, and you want to just make sure that you always use sanitize here, just for safety. Now, this works great so far, but we don't have file uploads and it's often the case where you want to be able to upload images as part of your content, and so we're going to be working on that next. Now, we have to first set up file uploading in our application before we can even use that for trix just so that we have file uploading working, and then we can focus on the JavaScript the trix is going to require for this. So what I'm going to do, is I'm going to generate a new model, let's also generate a scaffold with this so we have the create action, we're going to have photos and photos are going to have image data on them, which will be a text column, we're going to use shrine for uploading, so this is going to generate that model, we'll be able to access all of that, and then we'll run `rails db:migrate`, and then if we go into our photos controller

**app/controllers/photos_controller.rb** 
```ruby
def photo_params 
    params.require(:photos).permit(:images)
end

app/models/photo.rb

class Photo < ApplicationRecord 
    include ImageUploader[ :image ]
end

app/models/image_uploader.rb

class ImageUploader < Shrine 
end 

Last but not least we need to make sure we also install Shrine, so we'll run bundle to install the gem, and then before we restart our rails server, let's create a config/initializers/shrine.rb, and I'm going to paste in some configuration here, basically we need to tell Shrine where to store our files, and I'm going to have them just stored on the file system here, and turn on the Active Record plug in so that we have access to that, so once we do that, we can restart the rails server, it will load all that config, and then we can go to our photos form, and if we modify this instead of image data for these things, we can have it as a file field so that we can test this out and just make sure that our file uploads are working before we go and create those with trix.

So because we've created the scaffold for this, we now have photos/new, and we have the image field that we can upload from here, we can create a photo, and we're going to get that image data and the photo should be saved, and if we go to the photos/show.html.erb, we can then put in a little tag

<%= image_tag @photo.image_url %> 

And there we go. So our photos are currently uploading correctly, so we know all of that works, and it's good to confirm this before we go into trix just to make sure that we're not dealing with photo uploading problems at the same time as you're trying to mess with the JavaScript for trix. So now we want to go back to this, and make this form so that you can drag and drop a photo onto it, and then it will upload that photo asynchronously and then show the preview inside of your content here.

So as a quick side note I want to mention here that when we edit a post, trix is automatically loaded that content from before and put it into the rich text editor and we can edit that stuff automatically like we would normally would, so if we update posts, it's going to edit just like you would expect, and that all comes for free because we've set the input attribute on the trix editor to match the id of the hidden field, so it's able to look at that for the content, it looks at the value, rails is automatically going to populate the value because we're using the hidden field tag and we've set the model in our form, so all of that is the standard rails stuff, but trix is smart enough to say: Well, if you tell me what form field to read and write from, I will go ahead and do that and preload all the content, and then any updates you make we will send it to that value on the hidden field as well, so that all works out of the box, and is really really easy to use.

Now, one thing I do find useful is to change this back to a text area when we're working on the trix upload JavaScript, because if anything goes wrong, you will see that the text area value has not changed and that is important to see in case anything went wrong with the file uploading, so let's take a look at how we go implement all of that jazz.

Now it's time to start building the trix upload javascript, and the first thing that we want to do is create a file called

app/assets/javascripts/trix_uploads.js

document.addEventListener("trix-attachment-add", function(event) {
  var attachment = event.attachment;
  if (attachment.file) {
    return uploadAttachment(attachment);
  }
});

This, we don't have to put inside the tubolinks load because as soon as the page loads we'll set up this listener, which will listen to any trix attachment add events, so you could have trix add itself to other pages and whatever, this only needs to be added one time. We don't need to re add this listener every single page that happens, so we're going to have that, and then we're going to have a function up here called Function.attachment(attachment) and we're going to have this do the processing and submission to the server, and then take the result of that, the url of the image and then tell trix what the actual url is. This is really all we have to fill out, but we have to then figure out how do we get the attachment here. We can get the attachment by saying var attachment = event.attachment and then if the attachment.file exists, then we want to upload the attachment, and that's really it, and then we're going to call that and then it will have this function, which we will set up with some form data, so we need to basically submit that file, and we're going to do that using the form data object, and then take an XHR request and submit that over. Now, normally, what I would recommend is using the rails ujs library so you would want to have the rails.ajax method here and do all that normal stuff, but, in this situation, rails.ajax doesn't provide us right now at least with an easy way of adding listeners for the upload progress, which we want to use in order to tell trix how the progress is going for the file upload, so if you have a big file upload, it will display the image that you're uploading or video or whatever it is, that attachment will be displayed there in a progress bar and we want to, ideally be sending that progress information over to trix. So the rails ajax method isn't really going to do a great job for us here, and so we're going to set up an xhr request manually instead.

app/assets/javascripts/trix_uploads.js

function uploadAttachment(attachment) {
  // Create our form data to submit
  var file = attachment.file;
  var form = new FormData;
  form.append("Content-Type", file.type);
  form.append("photo[image]", file);

Now we'll have the form data object set up so that we can create a new xhr request:

// Create our XHR request
  var xhr = new XMLHttpRequest;
  xhr.open("POST", "/photos.json", true);
  xhr.setRequestHeader("X-CSRF-Token", Rails.csrfToken());

// Report file uploads back to Trix
  xhr.upload.onprogress = function(event) {
    var progress = event.loaded / event.total * 100;
    attachment.setUploadProgress(progress);
  }

We can finish off our xhr upload now, but we need to define this ahead of tie so that that is handled once we fire off the request. We need to add one more function to handle progress here

  // Tell Trix what url and href to use on successful upload
  xhr.onload = function() {
    if (xhr.status === 201) {
      var data = JSON.parse(xhr.responseText);
      return attachment.setAttributes({
        url: data.image_url,
        href: data.url
      })
    }
  }

If all of this is set up correctly and I didn't make any mistakes, then we should be good to go. This xhr stuff is really hard to remember, so don't feel bad about copying and pasting this stuff because I never remember it and have to reference this all the time, and that's really why rails and jQuery have those wrappers around this stuff because it's really tricky to remember the exact names or the upload on progress or whatever. All this stuff can be a little confusing to remember. Now, one last thing is that we should look at the photos controller and take a look at that create action. This is going to render json if it's successful, and so it's going to have a render show, and this is going to render the app/views/photos/show.json.jbuilder file, which in turn renders a partial app/views/photos/_photo.json.jbuilder, and this is where you're going to define the attributes and stuff that you send back, and anytime that you serialize one of these photos to json. Now, this photo url is actually set to the url of the photo object, so this would point to /photos/1/json or whatever, but not the image itself, and so we have a couple options, we can either use the url like I wrote it in here, or for better sake it's probably better for us to say json.image_url photo.image_url which will access the shrine image url, so let's change this to image url for both of those, and if we save that, and we go back to our browser, if everything works correctly, we can pull up an image and drag and drop this on to our form, and we should see that the image shows up. Now this is going to be shown as a preview just because you can do that in JavaScript without having to actually upload the file first, so don't trust the visual image that you see immediately, you want to actually trust that the href was added to your content here. So I have this set up so that we can see this a little bit more visibly, and here you can see that there's an big href that wraps a lot of content and this is actually what we want to see. We want to see /uploads/store and the jpeg name, and that means that we are posting that image url, it's been saved in the database and all of that, and you should see in your rails side of things as well, if you want to double check that, you should see that you got a post to photos.json, you should see that it inserted a photo, and then rendered out that show.json.jbuilder file, and if all of that was correct then you have successfully loaded a file and it has been added to your content, so then you can update posts and you can see your image here as well, and it automatically generates and shows the filename and the size, and so you will have that caption by default, but you can also edit that out with a little bit of JavaScript.

So to actually remove that trix preview with the caption, you can have the name and size attribute set to false, with this Trix.config.attachments.preview.caption and you are going to have all of that set up like so, and so that is going to be run, and if we do this again, we should see that now if we drag and drop a new photo on here, let's grab one, so we'll drag and drop this one, it uploads and there is no caption here, and you can write your caption if you would like and click update, and this is a giant photo and it saves our caption as we would expect. So in this situation, we have that and you have the two options for those attributes that you passed in, so for example with this url, this is the image url, and the href is actually the link that wraps that image, so when you click on the image, it will actually open up a link automatically and so if you set this href o a different url than just the image, so we've set it to the exact same thing, if you click on the image, it will load the image on it's own page, so if you had a special page that you wanted to link to for those, you can change that as well and do that however you would like if we change this photo url to format as html and you set url there, we could change this href to url, and that would point us to the photos show page for htlm, so if we try this now, we can go edit, let's delete this photo, let's drop another one in, let's put that same one we did before, this time, if we update posts, we have no caption if we didn't enter one, and if we click this, it goes to photos/6.html, which is our photo show page, so if you ever have pages like we built where we have a photo section, you can click these and then you can go see the bigger picture of that or whatever that's not inside of your content. That is how you add file uploads to trix. It's not terribly complicated, but it is more confusing than you might expect when you're diving into something like that. All of those WYSIWYG editors generally work the same way, when you do a file upload, it will trigger a callback like what we saw with the trix attachment add, and that's going to then tell you to run whatever code you want and then you report back with the successful upload and then all of that is good.

That's it for this episode, I hope you enjoyed it and I will talk to you in the next one. Peace v

Transcript written by Miguel

Discussion