Introduction
In this guide, we will walk you through the process of integrating image support into your online text editor using a stack that includes Spring Boot, Thymeleaf, and CKEditor. Whether you’re building a wiki, a recipe site, or any application where visual content matters, this tutorial will help you take the next step in improving user engagement and content richness.
We will use the following stack:
Spring Boot: an open-source Java framework that simplifies the development of production-ready, stand-alone, and web-based applications. It provides a streamlined way to build Java-based web applications by offering a set of pre-configured components and a convention-over-configuration approach. Spring Boot is known for its ease of use and rapid development capabilities.
Thymeleaf: a Java-based templating engine that is commonly used in web applications to generate dynamic HTML and XML content. It integrates seamlessly with Spring Boot and other Java frameworks, allowing developers to create web templates using natural and easily readable syntax. Thymeleaf templates can be rendered both on the server-side and the client-side, making it a versatile choice for building web applications.
CKEditor: a popular and powerful WYSIWYG (What You See Is What You Get) text editor that can be integrated into web applications. It provides a user-friendly interface for editing and formatting text content. CKEditor is highly customizable and extensible, making it suitable for various content management scenarios. It also offers plugins and features for adding media, including images, to text content, enhancing the richness of user-generated content.
Initial setup
Let’s start with the edit wiki page
<form th:action="@{/wiki/{name}(name=${wiki.name})}" method="post">
<input type="hidden" name="id" th:value="${wiki.id}"/>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" name="name" class="form-control" th:value="${wiki.name}" required/>
</div>
<div class="form-group">
<label for="content">Content:</label>
<textarea name="content" id="editor" th:text="${wiki.content}" required></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Save</button>
<a th:href="@{/wiki/{name}(name=${wiki.name})}" class="btn btn-secondary">Cancel</a>
</div>
</form>
Here we have a simple form that will show the name of the wiki page and its contents. When we submit the form, the page will be updated with the new content.
To add CKEditor, we need to add the script and CSS. To check the latest version, look here: https://ckeditor.com/ckeditor-5/download/
<script src="https://cdn.ckeditor.com/ckeditor5/39.0.1/classic/ckeditor.js"></script>
<link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/39.0.1/classic/ckeditor.css"/>
Next we need to initialise the editor. This will transform the basic HTML text area into a WISYWIG editor.
<script>
ClassicEditor
.create(document.querySelector('#editor'))
.catch(error => {
console.error(error);
});
</script>
Now that we’ve setup the frontend with Thymeleaf and CKEditor, it’s time to turn our attention to the backend. We will use a Spring Boot controller to serve the webpages. We prepare a wiki to be edited by finding the wiki and adding it to the model. When edited, we find the old wiki and update its contents.
@Controller
@RequestMapping("/wiki")
public class WikiController {
@Autowired
private WikiRepository wikiRepository;
@GetMapping("/")
public String index(Model model) {
List<Wiki> wikiList = wikiRepository.findAll();
model.addAttribute("wikiList", wikiList);
return "wiki/list";
}
@GetMapping("/{name}/edit")
public String editForm(@PathVariable("name") String name, Model model) {
Optional<Wiki> wikiOptional = wikiRepository.findByName(name);
if (wikiOptional.isPresent()) {
Wiki wiki = wikiOptional.get();
model.addAttribute("wiki", wiki);
return "wiki/edit";
} else {
return "redirect:/wiki/";
}
}
@PostMapping("/{name}")
public String update(@PathVariable("name") String name, @ModelAttribute("wiki") Wiki wiki) {
Optional<Wiki> wikiOptional = wikiRepository.findByName(name);
if (wikiOptional.isPresent()) {
Wiki oldWiki = wikiOptional.get();
oldWiki.setName(wiki.getName());
oldWiki.setContent(wiki.getContent());
wikiRepository.save(oldWiki);
}
return "redirect:/wiki/";
}
}
Adding Image support
The first thing we need to do is to add image support to the editor. We do this by adding and initialising CKFinder.
<script>
ClassicEditor
.create(document.querySelector('#editor'), {
ckfinder: {
uploadUrl: '/upload'
}
})
.catch(error => {
console.error(error);
});
</script>
We need to create the backend that will receive the image and create a correct response. We do this by creating a new controller. This endpoint will receive a MultipartFile, ask the FileService to store it, and return the URL where the file can be retrieved. This way CKEditor knows the URL to insert into the HTML page that you are editing.
@Controller
public class FileController {
@Autowired
private FileService fileService;
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("upload") MultipartFile file){
String name = fileService.storeFile(file);
if (name == null) {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body("{\"uploaded\": false, \"error\": \"not implemented yet\"}");
} else {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body("{\"uploaded\": true, \"url\": \"/file/"+name+"\"}");
}
}
}
How do we retrieve the file? We need another method for that. We will get the file with the requested name. We’re assuming that the file type is a JPEG image. If the file doesn’t exist, it returns an 404.
@GetMapping("/file/{name}")
public ResponseEntity<byte[]> getImage(@PathVariable String name){
byte[] file = fileService.getFile(name);
if (file == null){
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(file);
}
Obviously, we need the FileService to store and retrieve the file. The uploaded file has a name. However, if there is a file with that name in our repository, this file will overwrite it. So we change the name to a random UUID, but preserve the extension. Then we store the file on our disk. Reading the file is easier. We should have the file with that specific name on our disk, so we just read it and return its contents.
@Service
public class FileService {
private static final String FILE_STORAGE_DIR = "files";
public String storeFile(MultipartFile uploadedFile){
try{
String originalFilename = uploadedFile.getOriginalFilename();
String extention = originalFilename.substring(originalFilename.lastIndexOf('.'));
String fileName = UUID.randomUUID().toString()+extention;
Path file = Paths.get(FILE_STORAGE_DIR, fileName);
Path parent = file.getParent();
Files.createDirectories(parent);
Files.copy(uploadedFile.getInputStream(), file, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public byte[] getFile(String name){
try {
Path file = Paths.get(FILE_STORAGE_DIR, name);
return Files.readAllBytes(file);
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
}
Conclusion
In this guide, we’ve integrated image support to an online editor.Spring Boot’s ease of use and rapid development capabilities, combined with Thymeleaf’s elegant templating, lay a robust foundation for your project. CKEditor, as a feature-rich WYSIWYG text editor, allows you to provide a user-friendly experience for content creation while seamlessly integrating images.
As you implement the steps outlined in this guide, don’t forget to consider security best practices, such as file type validation and size limits, to protect your application and its users. Additionally, remember that the possibilities extend far beyond the basics presented here. CKEditor offers a wide range of plugins and customization options, enabling you to tailor the editor to your specific needs.