Converting Image Files into Base64 Strings
Now that we have customized our icons and we have the structure in place for being able to encode our uploaded images to our text editor, now we are going to start that process.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

Now I'm gonna warn you, this is a little bit advanced and it's not really the encoding process that's tricky, however, if you are not that familiar with the way that promises work, then the code we're gonna work through is going to probably be a little bit confusing and I don't want that to intimidate you or anything like that.

If you do not understand every single part of the code, do not worry, I would say the best approach is to implement it, follow along, see that it's working and then we'll circle back at the end and if it's still fuzzy after that, I definitely recommend for you to look at the guides that I've created on how promises work, on how you resolve promises because that is exactly what we are gonna be focusing on. We don't have to write a lot of code to get this working but the code we are going to write is going to completely revolve around promises, so let's get started with that.

The very first thing we're going to do is create an encoding function. So we are going to create a function that will do what is called a base64 image conversion. If you've never heard of base64 images, do not worry, that can be a little bit tricky, so let's actually look it up. So I'm gonna say base64 image and you can see there's all kinds of helpful tools for being able to see what they are online including some encoders.

So right now, I'm just going, and you can use any application or any kind of base64 encoder. We're not gonna use any of this, this is just to show you as an example what we're doing. So I'm going to click here and I'm gonna upload a sample image and so what is happening right here is that image is getting converted to what is called a base64 string.

So we cannot simply embed a image file directly into our text string. What we need to do is take the image and convert it into something that can be stored as text and then we can just embed it along with our paragraph tags and everything else. So it looks like this is done. If you click on Show Code right here, you'll see what this generated.

large

It says data image jpg base64 and this giant long string. And as you can see, if you scroll all the way, this is thousands upon thousands of characters long. And so, what this does is a base64 string at a high level is simply a image that is represented and stored as a string. So when a browser sees a base64 string, it is going to take that string and it's gonna say, okay, this is an image, I'm going to render this like an image, browsers have the ability to do that so that is how we're gonna be able to store this as a string.

So that's what we're going to do, that's how we're gonna create it. Now the tool we're going to use is not some external library, thankfully, JavaScript just Vanilla JavaScript has a tool called the FileReader library or FileReader module. So if you type in JavaScript FileReader, you'll see the documentation for FileReader along with some helpful examples and some of the methods that you can use.

Essentially what FileReader allows us to do is we create this FileReader instance and then we can work with it and then we can have it perform this base64. It takes in a file, in this case, it takes in an image, and then it will perform the processing and the encoding for us. Now where the tricky part comes in is that FileReader is completely promise-based. So in order for this to work properly, we have to be able to work with promises, we're gonna have to resolve promises and make changes and updates when they occur. So now that you kinda have a high level idea for all of the pieces to make this work, let's get started with implementing the code.

So first I'm gonna create a function here and I'm gonna call it getBase64 and this is going to take in two arguments, a file and then a callback function. So we're actually gonna send this base64 a function.

rich-text-editor.js

getBase64(file, callback) {

}

Now let's make sure that we tie both upload file and getBase64 file to this 'cause it has to have access to the component instance. So I'll say this.getBase64 equals this.getBase64.bind(this) and then let's do the same thing with this.uploadFile equals this.uploadFile.bind and pass in this.

this.onEditorStateChange = onEditorStateChange.bind(this);
this.getBase64 = this.getBase64.bind(this);
this.uploadFile = this.uploadFile.bind(this);

Okay, now that we have all of that in place, let's actually start creating this. So getBase64 it is going to first call the FileReader module so let's call this and I'm gonna say let reader equals and then new FileReader, you can see this is something we did not have to import, this is just built straight into JavaScript. And now because a FileReader is an asynchronous function, that is the reason why we need to call, and then we need to resolve it.

So the reason for that is because the file reading and conversion process can't happen instantaneously. If you have a large file, it might take a little bit of time in order for that to process. And so, with that knowledge, that's why FileReader uses promises, is because they know that if you pass in a large image, it's not gonna instantly be able to give you back that image code and that base64 string.

It's gonna take it a little bit of time, it may not be any amount of time we even noticed but in the code world, it will take a little bit of time and that's the reason why they built this with promises. So we are going to use our reader instance, and I'm gonna say reader. and you have all of these helpful methods and you can see a lot of them here inside of the auto-complete, and I'll show you all of them.

large

And we specifically first wanna be able to read the data in. So we're gonna take the file and we wanna read it in so the function for this is called readData or readAsDataURL and that takes in a file object. So we're taking that file, we're passing it to the reader and then it in turn is going to read it as a data URL.

Now because of the asynchronous nature, technically this is it, this should be all that we have to do. It is encrypting it but we can't simply call this, we can't just return this value, now is where we get into promises. So I'm gonna say reader.onload and this is going to take a function, so this is going to have a fat arrow function here and we're gonna take the callback function that we're gonna be passing in.

Once again, I know I'm going, I'm going line by line but I know some of this, if you've never done this before, this can be confusing, don't worry, we are gonna circle back. But it takes this callback function and it calls it and in our case, we're gonna pass in reader.result.

Where did result come from? Well, when we passed in the readAsDataURL, it started this process and it said okay, we're gonna go through, we're taking the image, we're tearing it apart, turning it into this string, that eventually gives us our result, so we are passing that into that callback function.

Now if we have any errors, we wanna make sure that we keep track of those, so I'm gonna say reader.onerror and for that one we're simply going to take the error and for right now we're not gonna do anything with it, we're just gonna return a empty object.

rich-text-editor.js

getBase64(file, callback) {
  let reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = () => callback(reader.result);
  reader.onerror = error => {};
}

Okay, like I said, not a lot of code, and we still have to call this but it still can be confusing. I know when I was first learning about promises in JavaScript, it took me quite a bit of time for it to really make sense on what was happening. The easiest way for me to understand it was to try to look at it like a real life promise. If you go to somebody and they promise that they are going to do something, it's probably your expectation when they say that they promise it, is that it's not gonna happen instantly.

They have to go actually make the promise happen, whether it's doing a chore for you, or whatever it is, lending you a book, they need to then in turn go perform some action and then you have two options. They're either going to come and give you what they promised and that's when you accept it, or they lied to you and they do not give you what they promised and that's when you have an error, that's the same way I like to look at promises in JavaScript.

So let's now actually call the getBase64 function. So now we're gonna say return new Promise and then the promise takes two arguments, if you have referenced back to the videos that I created just on promises, we did a deep dive into how the work, a promise takes two arguments, it first takes a resolve and then it takes a reject.

And then we pass in a fat arrow function and then inside of here, this is where we actually call it. So I'm gonna say this.getBase64 we're gonna pass in file and then the second argument we're gonna pass in data resolve, so this is what happens when we expect our promise to come true. Resolve and then that gets an object which is data and then link: data.

rich-text-editor.js

uploadFile(file) {
  return new Promise((resolve, reject) => {
    this.getBase64(file, data => resolve({ data: { link: data } }));
  });
}

Wow, I know, that looks really weird. Don't worry, we're gonna circle back, we're gonna walk through exactly what's happening. Thankfully, this isn't a lot of code so it doesn't take quite as long of time to implement and we can spend more time just trying to understand it.

But now that we have this, let's first make sure it's working and then we'll circle back. So let's come to Google Chrome, hit Refresh just to make sure you're working with the latest version, and then click on your image uploader and click Drop File or you can either drop a file or just click to upload. And I'm going to pick this image and so far so good. We actually generated the image preview and the only way that could happen is if it was encoded properly.

large

So that's looking good, you can add some alt text, you can even add your own customized size. And I'm gonna hit Add here and look at that, it is all working perfectly. So I'm gonna say with rich text image, blog status is draft, and let's also just add some text just to make sure so you can see that we have the text and we have the image. Hit Save, and now at the very top, we have with rich text image and if you click on that you can see our image is there.

large

So that's good, everything is working. However that's only half the piece, if I simply show you the code, and you copy and paste it or you just follow along, then I haven't really done my job. The whole key here is to either understand it completely or to at least be on a path where you feel like you're getting closer to understanding the concept. So let's circle back now and let's look at this code.

So first and foremost, what's happening? Well inside of our editor component, we have this image prop or toolbar prop which has an image object inside of it, that has the uploadCallback. So this uploadCallback is calling a function whenever an image is dropped inside of it or we pick an image from the file system, so that calls this uploadFile function here.

So uploadFile has one job to do, it handles the process of what happens when an image is picked for our rich text editor. So what is that doing exactly? Well, it is creating and returning a promise. So this promise is going to have two options. Now we didn't even implement the reject 'cause I'm not really that concerned with it, that is what would happen if for some reason the image didn't work or there was an error or anything, I'm not concerned about it for this feature.

So all we did was we implemented the resolve and from there, we called getBase64 and we passed it two arguments. We passed it the file which in the last guide, we saw the file was just a file, we were able to see a file name, size, things like that. So we passed it the file and then we passed it a function. So we passed it this function, it is an anonymous function, so we didn't create a function and then call it, we're just passing in a function that has an argument of data and then it has our resolve call.

So we're saying right here is that whenever this promise is done, whenever this is delivered, I want you to run this code and in this case, what we're saying is I want to have this object here returned where we get our data, and it has a key of data, and then it has this link tag, and then we're passing in data.

So where we do get the data exactly? Well, if you go up to the base64 function, that is where that is all happening, that is what gets returned. So we're creating a new instance of FileReader, we are adding in the file itself. So the file is being placed inside, it's getting parsed inside of this readAsDataURL function.

Then from there, with onload, that means when this starts up, when this whole process starts up, what we're doing is we're running the callback. What is a callback? The callback is this function.

data => resolve({ data: { link: data } }));

So we're calling this promise, we're saying hey, whenever you happen to be done encoding this file, turning it into a string, I want you to run this function, I want you to run this process. And what's happening here, it is taking the string that it took in, so it's taking the string that it converted and it's saying here it is, you can do whatever you want with it and we said, we would like this placed inside of an object where the key is data, the nested key is link and then we want that data, that string placed inside of that, and that is the whole process, that is how it works.

So I hope that that makes a little bit more sense and so, you learned quite a bit in this guide. You learned not just how to encode an image as base64 that's only one part of it, but along the way, you also learned quite a bit about promises and callback functions and things like that. And so if this is still fuzzy, I would give you a couple recommendations.

One, go back, revisit the promise guides that are in the Modern JavaScript course because that may give you more of an introductory knowledge on what's happening here with using keywords like resolve and reject. And just understanding the process with a few more base case scenarios. And then after that, return to this example and you might see that a few of these keywords are not quite as fuzzy anymore and you're able to work through it and understand the full process that is happening here.

So either way, great job in going through that, this is not a trivial or a simple feature to build in and you now have it live in your application.

Resources