Over the past decade, mobile devices have become the main means of entering your website. Now, more than 68% of website visitors use a mobile device, which means it’s crucial for every business to create mobile-first websites for their customers.
More than a decade ago, a large web-based business understood this new (at the time) business imperative. In late August 2010, Twitter engineers Mark Otto, and Jacob Thornton released a CSS framework called Bootstrap. It’s since expanded to become one of the most popular development frameworks.
What is Bootstrap?
Bootstrap provides CSS and JavaScript utilities to more efficiently build production-ready responsive websites. It’s a valuable component for mobile-first web design.
Many organizations include it in their productions, with 26% of all websites using Bootstrap. It’s one of several pre-built components (like WYSIWYG editors) that help power websites, but harmony between those components can’t be ignored..
TinyMCE integrates well with several popular frameworks, including Bootstrap, and brings with it functionality that’s useful for mobile devices such as image upload and image editing.
That’s what this tutorial explains – how to build a blog submission web page using Bootstrap –making a responsive page for different screen sizes, and also integrating the TinyMCE WYSIWYG editor to support rich text formatting. Finally, this guide shows how to implement TinyMCE’s image upload feature with a Node.js server.
Note: The code content for this how-to guide is available in this GitHub repository.
Why should you use Bootstrap?
Apart from its mobile-first approach, what else makes Bootstrap stand out?
- It provides a library of CSS components powered by JavaScript, which can be used to build beautiful applications from scratch.
- It supports all major browsers and performs CSS normalization to avoid issues related to browser settings, therefore creating a consistent user experience.
- Bootstrap is open source and anyone can contribute to the code on GitHub – open source projects can respond to change more quickly as an open source project.
What makes images so important to mobile devices?
The truth is, it’s not just mobile devices. Looking at the web broadly, the value that images add to content affects all digital experiences. Images are a vital part of content marketing, especially on blogs and landing pages. They increase visitor engagement and make the content come to life.
Because visual content and images have become so crucial, businesses need reliable components that help them build beautiful blogs fast. That’s where TinyMCE’s image handling functions come in. But to see these functions in action, the first step is creating a project demo.
Step 1. Initializing an npm project
To start, create a new project with npm init —y
. Create a ./public
folder inside your project, then create an index.html file inside that folder with the content below:
<head>
<meta charset="UTF-8" /> <meta
name="viewport" content="width=device-width, user-scalable=no,
initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Create a new blog</title>
</head> <body> </body>
</html>
Step 2: Adding bootstrap to the project
Add Bootstrap CSS to the <head>
element of the HTML file to enable utility classes and components:
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous"
/>
Add Bootstrap JavaScript to the <body>
element of the HTML file to enable interactive components like dialogs:
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"
></script>;
Step 3: Creating a blog submission page
With Bootstrap in place, create a simple layout for the blog submission page in index.html
:
<h1 class="display-2">Your new blog</h1> <div class="form row
mt-4">
<div class="form-group col-sm">
<label for="title">Title</label> <input type="text"
class="form-control" id="title" />
</div>
</div>
<div class="form col mt-4">
<div class="form-group">
<label for="articleBody">Write a ~1500 word article</label>
<textarea id="articleBody" rows="20" cols="150"></textarea>
</div> <div class="form-group mt-4">
<label for="keywords">Keywords</label> <input type="text"
class="form-control" id="keywords" /> <small id="keywordsHelp"
class="form-text text-muted" >Comma separated list of
keywords.</small >
</div>
</div>
<div class="form row mt-4">
<div class="form-group col-sm">
<button
name="submitbtn" class="btn btn-primary"
>
Publish
</button>
</div>
</div>
</div>
The form looks neat, but the article editor is minimal. It also doesn’t support rich text editing or media. The other missing element is image upload. By using TinyMCE to add these elements, it helps create a better user experience, and introduces image functionality.
Step 4: Configuring TinyMCE
To integrate with TinyMCE, first sign up for an API key. You can then add your API key into the TinyMCE JavaScript CDN link. Include the link at the top of the html page inside the head tags:
<script
src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js"
referrerpolicy="origin"
></script>;
Signing up for an API key removes any warnings on domain registration. It also gives you 14 days FREE access to TinyMCE premium plugins.
Initialize the TinyMCE editor inside a <script>
tag with the Bootstrap skin and focus effect. You can get more details on how to enable Bootstrap text editor here. The tinymce.init script here also includes plugins for handling images.
Note: The script includes the TinyMCE Accessibility Checker plugin as including images with correct alt text is essential for websites.
// initialize TinyMCE rich text editor
<script>
tinymce.init({
selector: "textarea#articleBody",
skin: 'bootstrap',
icons: 'bootstrap',
plugins: 'image link media editimage a11ychecker',
toolbar: ' a11ychecker | undo redo | styles | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
a11y_advanced_options: true,
setup: (editor) => {
editor.on("init", () => {
editor.getContainer().style.transition = "border-color 0.15s ease - in - out, box - shadow 0.15 s ease - in - out ";
});
editor.on("focus", () => {
(editor.getContainer().style.boxShadow = "0 0 0 .2rem rgba(0, 123, 255, .25)"),
(editor.getContainer().style.borderColor = "#80bdff");
});
editor.on("blur", () => {
(editor.getContainer().style.boxShadow = ""),
(editor.getContainer().style.borderColor = "");
});
}
});
</script>
To avoid bugs while using a Bootstrap dialog, disable event propagation from the editor to interactive components like Bootstrap dialogs:
// Prevent Bootstrap dialog from blocking focusin
document.addEventListener("focusin", (e) => {
if (
e.target.closest(
".tox-tinymce, .tox-tinymce-aux, .moxman-window,
.tam-assetmanager-root"
) !== null
) {
e.stopImmediatePropagation();
}
});
Step 5: Extracting data from the editor
In the same <script> tag, create a function to extract the content from the editor on the onclick event of the Publish button:
async function handleForm() {
const editor = tinymce.activeEditor; const data = editor.getContent({
format: "html" }); console.log({ data }) // store data into your
database or content management system (CMS)
}
Add the onclick handler function to the Publish button created in the previous step number 3:
<button name="submitbtn" onclick="handleForm()" class="btn btn-primary">
Publish
</button>;
The article submission page looks good, and it supports images as well. Still, you’ll notice two issues:
- The images in the final content have Blob URLs while using the format: 'raw', which won’t be accessible outside of this system
- The images use base64 while using format: 'html', which will increase image sizes
To handle image uploads correctly, you first need to set up a REST API to store images in a remote location. This example uses Express, but you can use whatever framework you’re most comfortable with.
Step 6: Setting up the Express server
Run npm i express@4.18.1 to install Express. Create a server.js file at the root of the project to serve the HTML files:
const express = require("express");
const server = express();
const PORT = 8080;
server.use(express.static("public"));
server.listen(PORT, () => {
console.log(`server is running at port: ${PORT}`);
});
Run node server.js to start the server. You’ll be able to access the page at http://localhost:8080.
Step 7: Uploading images using a REST API endpoint
Create a ./images folder inside the project. This tutorial uses Multer to handle the multipart/form-data and save uploaded images to the ./images folder. Run npm i multer@1.4.5-lts.1 to install Multer. Then, run npm i nanoid@3.3.4 to install Nano ID in order to generate unique file names for the uploaded images.
Update the server.js code to enable image uploads:
const express = require("express"); const server =
express(); const PORT = 8080; const multer = require("multer");
const { nanoid } = require("nanoid");
// serve HTML file from /public folder at "/" path
server.use(express.static("public"));
// serve the uploaded images from ./images folder at "./images"
path server.use("/images", express.static("images"));
// configure storage options for multer const fileStorageEngine =
multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./images"); // relative path to storage location
}, filename: (req, file, cb) => {
// get the file extension from mimetype const fileExtension =
file.mimetype.split("/")[1]; // create a unique file name using
10 random characters joined with time in milliseconds const
uniqueFileName = `${nanoid(10)}-${Date.now()}.${fileExtension}`;
cb(null, uniqueFileName);
},
});
// initialize multer with the storage engine configuration const
upload = multer({ storage: fileStorageEngine });
// handle image upload using multer and return the `imageUrl` as
response server.post("/upload-image", upload.single("image"),
(request, response) => {
response.send({
imageUrl: `${request.headers.origin}/${request.file.path}`,
});
});
// start the server at http://localhost:{PORT} server.listen(PORT,
() => {
console.log(`server is running at port: ${PORT}`);
});
Step 8: Updating the frontend code to handle image upload
TinyMCE expects an images_upload_handler function to handle image uploads and return the location of the uploaded file. TinyMCE then replaces the image src in the editor content with the received URL.
In your index.html file , add a handleImage() function in your tinymce.init <script> tag to upload an image to the server:
// this function will upload the images to the server // and
return a Promise with file URL function handleImage(blobInfo) {
const formData = new FormData() formData.set('image',
blobInfo.blob())
// upload the image to server and return `imageUrl` return
fetch("/upload-image", {
method: "post", body: formData,
})
.then((response) => response.json()) .then((res) => res.imageUrl);
}
Update the TinyMCE initialization code to specify the images_upload_handler
function:
> // initialize the TinyMCE rich text editor tinymce.init({
selector: "textarea#articleBody", skin: 'bootstrap', //The TinyMCE
Bootstrap skin images_upload_handler: handleImage, // // ....other
configurations
});
Update handleForm()
to call editor.uploadImages()
and ensure all images are uploaded:
async function handleForm() {
const editor = tinymce.activeEditor; // make sure all images
are uploaded on the server await editor.uploadImages(); const
data = editor.getContent({ format: "html" }); // store data
into your database or content management system (CMS)
}
You can see that the image src is the server location and can be accessed directly. See it in action:
Bootstrap image upload and TinyMCE
TinyMCE effectively integrates into a Bootstrap web page, and handles both the crucial image upload and serving functions needed across websites. The Express server is just one method to handle images with the TinyMCE API. Other methods involve angular or jQuery. We’ll cover these methods in future articles.
To ensure reliability and faster access time, you can extend this example further by integrating with a Cloud storage provider like Tiny Drive. It’s another service available to support your application development.
[1] - https://www.perficient.com/insights/research-hub/mobile-vs-desktop-usage