WEB 编程:使用富文本编辑器 Quill 配合 WebBroker 后端
使用 Delphi 的 WebBroker 框架写 Web Server,需要一个前端的富文本编辑器。
评估了好几个,最后选择 Quill 这个开源的。
官方地址:Quill - Your powerful rich text editor
把前端代码,存储为一个单独的文本文件,方便随便哪个页面需要的时候可以使用。相当于封装为一个独立的对象,方便代码重用。
Quill 编辑器的代码如下:
<!-- quill 编辑器的封装 -->
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet">
<style>
.edit_container {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.ql-editor{
height:400px;
}
</style>
<form id="myForm" action="ContentUpdate" method="post">
<div id="editor"></div>
<input type="hidden" name="delta" id="deltaInput">
<input type="hidden" name="html" id="htmlInput">
<input type="submit" value="Submit">
</form>
<script>
const quill = new Quill('#editor', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block'],
],
},
theme: 'snow'
});
document.getElementById('myForm').addEventListener('submit', function() {
// 获取 Quill 编辑器的内容
const delta = JSON.stringify(quill.getContents());
const html = quill.root.innerHTML;
// 将内容放入隐藏输入框中
document.getElementById('deltaInput').value = delta;
document.getElementById('htmlInput').value = html;
});
</script>
把上述代码,保存为文本文件:QuillEditor.txt;
然后,我们来做一个测试用的 html 文件,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quill Form Submission</title>
</head>
<body>
<#Editor>
</body>
</html>
这个文件,保存为 index.html;
这个文件里面的 <#Editor> 是给 WebBroker 的 PageProducer 自动替换的标记。Delphi 的后台程序会将这个标记替换为前面的 QuillEditor.txt 的内容。
Delphi 的代码
首先是如何输出页面
在 Delphi 里面创建一个 WebBroker 工程,
选择 StandAlone 模式,这样方便调试。
在这个工程里面,Delphi 自动帮我们创建了一个 Web 服务器端的核心框架:TWebModule1;
拖一个 PageProducer1 到 TWebModule1 上面。设置它的属性:HTMLFile 为 index.html;
这里的 index.html 就是前面的那个简单的页面框架。这个文件只要放在程序的相同目录下,运行时 PageProducer1 就会自动加载它。当然,也可以指定绝对路径比如:D:\MyHTML\index.html;
然后,选中 PageProducer1 然后把属性面板的页标签切换到 Events,可以看到 PageProducer1 只有一个事件:OnHTMLTag,双击这个事件,Delphi IDE 自动帮我们创建事件的代码框架。在这里我们写代码如下:
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: string; TagParams: TStrings; var ReplaceText: string);
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.LoadFromFile('QuillEditor.txt');
if TagString = 'Editor' then
ReplaceText := SL.Text;
finally
SL.Free;
end;
end;
上述代码的解释:
PageProducer1 在加载 index.html 的时候,会触发上述事件。我们在上述事件里面写代码,首先判断标记:Editor,这个标记就是前面我们写在 index.html 里面的那个 <#Editor>
ReplaceText 则是替换那个标记的内容。这里,我们用前面保存的 Quill Editor 的那个文件的内容来替换掉 Editor 这个标记。这里会将包括尖括号在内的整个 <#Editor> 都替换掉。最终输出的页面内容就是包含 QuillEditor.txt 的内容的一个完整 HTML 页面。
再然后,双击 TWebModule1 这个界面窗口,弹出 Editing WebModule1.Actions 对话框,里面已经有一个默认的 Action,选中它,然后在属性面板里面,把它的 Producer 属性下拉选择为 PageProducer1;这样操作的目的是浏览器访问服务器时,触发这个 Action,这个 Action 最终会把它绑定的 PageProducer1 的内容输出给浏览器。
到此,输出一个包含 Quill Editor 的页面制作完毕。
服务器端接收浏览器提交的内容
在前面提到的 Editing WebModule1.Actions 对话框里面,鼠标右键,下拉菜单:Add,添加一个 Action。
选择这个 Action,设置它的属性 Path 为:ContentUpdate
切换属性面板到事件,它只有一个 OnAction 事件,双击,Delphi IDE 自动产生事件代码框架,在里面写代码如下:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var S: string; RequestBody: string; Delta, Html: string; JSONValue: TJSONObject; SL: TStringList; begin S := Request.ContentFields.Values['html']; SL := TStringList.Create; try SL.Text := S; SL.SaveToFile('abcd.txt'); finally SL.Free; end; Response.SendRedirect('Show'); Handled := True; end;
上述代码解释:
这个 Action 的 Path 之所以设置为 ContentUpdate,是因为页面上的 Quill Editor 里面的代码:
<form id="myForm" action="ContentUpdate" method="post">
页面代码指明了提交给服务器的路径是 ContentUpdate,因此 Delphi WebBroker 服务器里面的这个 Action 的 Path 为 ContentUpdate 的 Action 事件就会被触发。
上述代码中:
S := Request.ContentFields.Values['html'];
这里的 'html' 是对应的 Quill Editor 的 HTML 代码里面的 name 部分的名称:
<input type="hidden" name="html" id="htmlInput">
假设把上述页面代码的 name="html" 改为 "html2",那么,对应的服务器端 Delphi 的代码就应该改为:
S := Request.ContentFields.Values['html2'];
最终,Delphi 把来自浏览器提交的编辑器的内容,保存为一个文本文件 abcd.txt
保存完编辑器提交的内容后,让浏览器跳转到 show 这个路径去:
Response.SendRedirect('Show');
假设浏览器访问的是 http://127.0.0.1:8080/ 就会跳转到 http://127.0.0.1:8080/show
为服务器端增加 Show 这个路径的处理方法
在前面提到的 Editing WebModule1.Actions 对话框里面,鼠标右键,下拉菜单:Add,添加一个 Action。
选择这个 Action,设置它的属性 Path 为:Show
选中这个 Action,切换属性面板到 Events,双击它的 OnAction 事件,IDE 创建事件代码框架,写代码如下:
procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
var SL := TStringList.Create;
try
SL.LoadFromFile('abcd.txt');
Response.Content := SL.Text;
finally
SL.Free;
end;
end;
上述代码解释:
这里我们加载前面保存的浏览器提交过来的 Quill Editor 的内容,也就是保存的 abcd.txt,然后把这个内容作为 Response 返回给浏览器。
如果在浏览器的地址栏里面输入:http://127.0.0.1:8080/show 就会直接触发服务器端的这个事件方法,浏览器页面上就能看到之前我们在 Quill Editor 里面输入的内容。
比较有意思的是:图片
在浏览器端的富文本编辑器里面插入图片,通常是要上传图片,然后把 URL 插入到编辑器里面。但是这样又会增加不少代码工作。比如服务器端需要有个专门的 Action 来接收上传的图片文件,把图片文件保存到 Web 服务器的某个目录里面,再返回 URL;在前端需要写不少 JavaScript 代码,用来上传图片,获得 URL,然后把 URL 插入到编辑器的对应位置。等等,很麻烦。
对于 Quill Editor 来说,前面我给出的代码,用户点击它的工具栏的图片按钮,选择本地硬盘的图片文件,然后,它会自动把图片贴进编辑框的正确位置,完成图文混排。点击提交按钮后,整个文本编辑框的内容包括图片数据,一起提交给服务器。这里的图片数据是编码为 Base64 的,并且格式复合 HTML 的标准的页面嵌入BASE64图片的格式,不完整但能体现这一点的代码片段数据如下:
src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAYEBQYF
因此,服务器端获得的完整的字符串,就是编辑器的完整内容,包含图片。把这个字符串保存后,作为文本输出给浏览器,图片也能完整地显示出来。
这样一来,图片和文字的混合编辑最终输出的就是一段字符串。在前端不需要写额外的 JavaScript 代码,后端也不需要额外的代码来处理图片文件的保存。
在实际的项目里面,后端可以把前端提交的字符串(包含图片和格式化的文字),直接原本保存到数据库的字段里面。需要显示时,直接从数据库的字段里面读取内容,输出给页面。
One more thing
前面提到的服务器端加载 Quill Editor 的代码,使用了 TStringList,重复一下代码片段:
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: string; TagParams: TStrings; var ReplaceText: string);
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.LoadFromFile('QuillEditor.txt');
if TagString = 'Editor' then
ReplaceText := SL.Text;
finally
SL.Free;
end;
end;
应该把上述代码,封装为一个对象,需要的地方,直接调用对象就好了。避免每个不同的 WEB 项目里面都要重复写上述代码。封装为对象,就可以实现代码重复使用。
代码如下:
unit UQuillEditor;
interface
uses
System.SysUtils, System.Classes, System.IOUtils;
type
TQuillEditor = class
private
public
class function GetHtml(const QuillEditorFileName: string): string;
end;
implementation
{ TQuillEditor }
class function TQuillEditor.GetHtml(const QuillEditorFileName: string): string;
var
SL: TStringList;
begin
if (not FileExists(QuillEditorFileName)) then raise Exception.Create('文件不存在!' + QuillEditorFileName);
SL := TStringList.Create;
try
SL.LoadFromFile(QuillEditorFileName);
Result := SL.Text;
finally
SL.Free;
end;
end;
end.
结论
Quill Editor 作为一个前端的开源的富文本编辑器,用起来还是很方便的。后端使用 Delphi WebBroker 框架来开发,可以简单地封装为一个文本文件和一个简单的 Delphi 对象,在需要的地方就可以直接使用了。