10/10/2018, 13:24
Xây dựng một Rich Text Editor - Phần 1
Đã có khá nhiều trình soạn thảo văn bản WYSIWYG miễn phí được share trên mạng, nhưng nếu bạn là một web developer đích thực, có thể một lúc nào đó bạn sẽ muốn xây dựng cho riêng mình một Rich Text Editor. Nếu vậy, bài viết này chắc hẳn sẽ đem lại cho bạn một chút gì đó hữu ích.
Thoạt tiên, khi nảy ra ý muốn viết 1 cái RTE, tôi đã nghĩ đến việc sử dụng 1 thẻ DIV để làm khung soạn thảo. Không khó trong việc giả lập con trỏ nhấp nháy, và chuyển các ký tự từ bàn phím vào trong phần tử DIV này. Một ví dụ là chương trình mô phỏng giao diện dòng lệnh của Window mà tôi đặt tên là Web Command Line (nó chỉ để chơi thôi chứ không giải quyết công việc gì ).
Nhưng rồi sau nhiều cuộc tìm kiếm, tôi đã phát hiện ra 1 điều quan trọng khác giúp cho công việc tự đơn giản hóa đi rất nhiều, đó là : DesignMode.
DesignMode là 1 thuộc tính của đối tượng document, giá trị mặc định của nó là "off". Khi bạn sửa đổi giá trị này thành "on", hồ sơ web trở thành dạng có thể chỉnh sửa - editable, và bạn có thể thao tác với trang web trong cửa sổ như đang làm việc trên MS Word ! Đây chính là bí quyết để xây dựng nên những RTE cho các trình duyệt web.
Bây giờ chúng ta sẽ làm 1 trình soạn thảo WYSIWYG đơn giản, gồm một trường nhập text, và các nút lệnh cho phép định dạng văn bản in đậm - B, in nghiêng - I, gạch dưới - U. Ngoài ra, chúng ta cũng thêm vào 2 nút cho phép chèn hình ảnh và liên kết. Sau cùng là một nút để gỡ bỏ các định dạng. Giao diện của editor trông sẽ như thế này :
Khung soạn thảo là 1 iframe, như cách thường dùng của các RTE. Chúng ta cho nó 1 ID và dùng CSS để thiết lập kích thước và khả năng scroll :
Để biến vùng iframe này về dạng editable, chúng ta thiết lập giá trị của thuộc tính designMode thành on bằng script sau :
Đoạn code JavaScript này phải được gọi sau khi iframe đã hiện diện trong trang web.
contentWindow là 1 thuộc tính của các frames, iframes trong hồ sơ HTML.
document.getElementById('textArea').contentWindow cho phép tham chiếu đến đối tượng iframe mà định danh được thiết lập : ID = "textArea".
Trong Mozilla , chúng ta có thể tham chiếu đến 1 đối tượng cửa sổ iframe bằng script :
Script tương ứng cho IE là :
Còn script mà chúng ta dùng sẽ thích hợp cho cả IE, Mozilla và Opera.
Như vậy, chúng ta có 1 biến toàn cục editor, để tham chiếu đến đối tượng document của phần tử iframe giả lập khung soạn thảo văn bản. Dòng lệnh tiếp theo thiết lập designMode của nó thành "on". Các lệnh open, write, close chèn vào iframe các tags cơ bản của 1 hồ sơ HTML, những gì nằm giữa <body> và </body> sẽ hiển thị trên khung soạn thảo như phần text mặc định của Editor.
Sau thiết lập designMode = "on", đối tượng document của iframe sẽ tự kích hoạt một số phương thức hỗ trợ thực hiện định dạng phần văn bản được chọn (selected text) bên trong nó. Chúng bao gồm :
Trong đó, quan trọng hơn cả là 2 phương thức execCommand và queryCommandEnabled.
Syntax của execCommand như sau :
Nếu không có text được chọn, phương thức này không làm gì cả.
Tham số bắt buộc sCommand là 1 chuỗi tên lệnh định dạng (command identifiers), chẳng hạn "bold", "italic"... QuirksMode liệt kê khá chi tiết những lệnh định dạng này, và cả cách chúng làm việc trong từng trình duyệt :
http://www.quirksmode.org/dom/execCommand.html
Tham số thứ hai, bUserInterface, thuộc dạng tùy chọn và mang 1 giá trị boolean (mặc định : false). Trong Mozilla, nếu đặt là true, bạn có thể nhận 1 lỗi (NS_ERROR_NOT_IMPLEMENTED).
Tham số thứ 3, vValue, cũng thuộc dạng tùy chọn. Bạn sẽ phải dùng đến nó trong một số trường hợp mà tham số đầu tiên cần 1 giá trị cụ thể. Chẳng hạn khi muốn định dạng màu chữ, tham số thứ nhất sẽ là "forecolor", và chúng ta cần tham số thứ 3 để cho trình duyệt biết màu gì sẽ được sử dụng. Ví dụ :
Hoặc khi chèn 1 hình ảnh, tham số thứ nhất sẽ là "insertimage", và tham số thứ 3 cho biết đường dẫn của hình ảnh đó :
Để biết 1 command có thể thực thi hay không, chúng ta dùng queryCommandEnabled. Phương thức này nhận 1 tham số là tên của command (như : "bold", "italic", "forecolor", "insertimage"...), và trả về 1 giá trị boolean cho biết khả năng thi hành command đó. Thông thường chúng ta kiểm tra command trước khi gọi nó. Script dưới đây kiểm tra command tạo liên kết, nếu có thể thực hiện thì sẽ chèn vào khung soạn thảo 1 HyperLink (aLink) :
Bây giờ, chúng ta xem xét các button. Chúng là những hình ảnh mà bạn tùy ý trình bày sao cho hợp lý. Thông thường các button định dạng nằm ngay trên khung soạn thảo như trong ví dụ mẫu này. Các lệnh in đậm, in nghiêng, gạch dưới được liên kết với hàm doFormat qua sự kiện click :
Hàm doFormat như sau :
Như vậy, doFormat nhận vào 2 tham số :
- a : tên của command
- b : giá trị của command
Ở đây, editor là biến toàn cục đã định nghĩa bên ngoài hàm như trình bày phía trên. Khi được gọi, doFormat sử dụng phương thức queryCommandEnabled của editor để kiểm tra khả năng thực thi command a. Sau đó, tiếp tục kiểm tra tham số thứ 2, nếu không thấy thì gán cho nó giá trị null. Nếu có thể thực thi command thì gọi phương thức execCommand.
Đối với việc chèn hình ảnh và liên kết, có một chút khác biệt, chúng ta viết 2 hàm addLink và insertImage dành riêng cho nhiệm vụ này. Chúng được gọi theo cách tương tự như 3 nút lệnh trước.
Trong IE, bạn có thể viết :
Hoặc :
Trình duyệt này sẽ hiển thị một hộp thoại để người sử dụng nhập vào các tham số thích hợp :
Tuy nhiên tính năng này chưa được Mozilla và Opera hỗ trợ, vì vậy, một số người lập trình Rich Text Editor đã tự xây dựng các kiểu DialogBox riêng để thay thế. Việc đó không khó, nhưng trong bài viết này, chúng ta sẽ đơn giản hóa công việc bằng 1 hộp thoại prompt.
Dưới đây là hàm addLink :
Và hàm insertImage :
Khi người sử dụng click trên nút [], một hộp thoại prompt bật ra để họ nhập chuỗi siêu liên kết. Hàm addLink kiểm tra lại chuỗi này, nếu có giá trị thị gọi doFormat với
2 tham số : "CreateLink" - tên command, và aLink - URL đích do người dùng cung cấp.
Khi hàm insertImage được gọi, chúng ta sử dụng dòng lệnh đầu tiên để focus vào vùng soạn thảo. (Điều này là cần thiết cho trình duyệt IE, bạn thử bỏ đi và chạy thử chương trình thì sẽ hiểu tại sao !). Tiếp đó, 1 hộp thoại prompt mở ra yêu cầu người sử dụng nhập vào URL của hình ảnh. Cuối cùng, chúng ta gọi doFormat với command "InsertImage" và giá trị iURL mà người dùng đã nhập.
Nút lệnh [] cho phép gỡ bỏ mọi định dạng trên phần text được chọn.
Chúng ta viết hàm xử lý như sau :
Không có gì để giải thích nhiều. Command "removeformat" loại bỏ toàn bộ các thiết lập kiểu dáng văn bản. Command "unlink" gỡ bỏ liên kết cho phần văn bản đó.
Cuối cùng, chúng ta có toàn bộ mã HTML và JavaScript như dưới đây :
Bây giờ thì Editor của chúng ta đã có thể hoạt động trên Mozilla, IE, Opera và Safari.
Demo :
http://sacroyant.uni.cc/Examples/Editor/V1/rte.htm
Phần sau, chúng ta sẽ tìm hiểu sâu hơn một số command khác để mở rộng tính năng cho editor này, đồng thời viết lại kịch bản điều khiển Editor theo kiểu hướng đối tượng. Hẹn gặp lại.
Thoạt tiên, khi nảy ra ý muốn viết 1 cái RTE, tôi đã nghĩ đến việc sử dụng 1 thẻ DIV để làm khung soạn thảo. Không khó trong việc giả lập con trỏ nhấp nháy, và chuyển các ký tự từ bàn phím vào trong phần tử DIV này. Một ví dụ là chương trình mô phỏng giao diện dòng lệnh của Window mà tôi đặt tên là Web Command Line (nó chỉ để chơi thôi chứ không giải quyết công việc gì ).
Nhưng rồi sau nhiều cuộc tìm kiếm, tôi đã phát hiện ra 1 điều quan trọng khác giúp cho công việc tự đơn giản hóa đi rất nhiều, đó là : DesignMode.
DesignMode là 1 thuộc tính của đối tượng document, giá trị mặc định của nó là "off". Khi bạn sửa đổi giá trị này thành "on", hồ sơ web trở thành dạng có thể chỉnh sửa - editable, và bạn có thể thao tác với trang web trong cửa sổ như đang làm việc trên MS Word ! Đây chính là bí quyết để xây dựng nên những RTE cho các trình duyệt web.
Bây giờ chúng ta sẽ làm 1 trình soạn thảo WYSIWYG đơn giản, gồm một trường nhập text, và các nút lệnh cho phép định dạng văn bản in đậm - B, in nghiêng - I, gạch dưới - U. Ngoài ra, chúng ta cũng thêm vào 2 nút cho phép chèn hình ảnh và liên kết. Sau cùng là một nút để gỡ bỏ các định dạng. Giao diện của editor trông sẽ như thế này :
Khung soạn thảo là 1 iframe, như cách thường dùng của các RTE. Chúng ta cho nó 1 ID và dùng CSS để thiết lập kích thước và khả năng scroll :
PHP Code:
<iframe id="textArea" style="width:500px;height:240px;overflow:auto;"></iframe>
PHP Code:
var editor = document.getElementById('textArea').contentWindow.document;
editor.designMode='On';
editor.open();
editor.write('<html><head></head><body></body></html>');
editor.close();
Đoạn code JavaScript này phải được gọi sau khi iframe đã hiện diện trong trang web.
contentWindow là 1 thuộc tính của các frames, iframes trong hồ sơ HTML.
document.getElementById('textArea').contentWindow cho phép tham chiếu đến đối tượng iframe mà định danh được thiết lập : ID = "textArea".
Trong Mozilla , chúng ta có thể tham chiếu đến 1 đối tượng cửa sổ iframe bằng script :
PHP Code:
window.frames***91;"myFrame"***93;
PHP Code:
document.all.myFrame.contentWindow
Như vậy, chúng ta có 1 biến toàn cục editor, để tham chiếu đến đối tượng document của phần tử iframe giả lập khung soạn thảo văn bản. Dòng lệnh tiếp theo thiết lập designMode của nó thành "on". Các lệnh open, write, close chèn vào iframe các tags cơ bản của 1 hồ sơ HTML, những gì nằm giữa <body> và </body> sẽ hiển thị trên khung soạn thảo như phần text mặc định của Editor.
Sau thiết lập designMode = "on", đối tượng document của iframe sẽ tự kích hoạt một số phương thức hỗ trợ thực hiện định dạng phần văn bản được chọn (selected text) bên trong nó. Chúng bao gồm :
- queryCommandEnabled
- queryCommandIndeterm
- queryCommandState
- queryCommandSupported
- queryCommandValue
Trong đó, quan trọng hơn cả là 2 phương thức execCommand và queryCommandEnabled.
Syntax của execCommand như sau :
PHP Code:
editableDocument.execCommand(sCommand ***91;, bUserInterface***93; ***91;, vValue***93;)
Tham số bắt buộc sCommand là 1 chuỗi tên lệnh định dạng (command identifiers), chẳng hạn "bold", "italic"... QuirksMode liệt kê khá chi tiết những lệnh định dạng này, và cả cách chúng làm việc trong từng trình duyệt :
http://www.quirksmode.org/dom/execCommand.html
Tham số thứ hai, bUserInterface, thuộc dạng tùy chọn và mang 1 giá trị boolean (mặc định : false). Trong Mozilla, nếu đặt là true, bạn có thể nhận 1 lỗi (NS_ERROR_NOT_IMPLEMENTED).
Tham số thứ 3, vValue, cũng thuộc dạng tùy chọn. Bạn sẽ phải dùng đến nó trong một số trường hợp mà tham số đầu tiên cần 1 giá trị cụ thể. Chẳng hạn khi muốn định dạng màu chữ, tham số thứ nhất sẽ là "forecolor", và chúng ta cần tham số thứ 3 để cho trình duyệt biết màu gì sẽ được sử dụng. Ví dụ :
PHP Code:
editableDocument.execCommand("forecolor", false, "#0000ff");
PHP Code:
editableDocument.execCommand('insertimage', false, imageURL);
PHP Code:
if(editableDocument.queryCommandEnabled("createlink")){
editableDocument.execCommand('createlink', false, aLink);
}
PHP Code:
<img src="bold.gif" onclick="doFormat('bold');">
<img src="italic.gif" onclick="doFormat('italic');">
<img src="underline.gif" onclick="doFormat('underline');">
PHP Code:
function doFormat(a,b){
if(editor.queryCommandEnabled(a)){
if(!b){b=null;}
editor.execCommand(a,false,b);
}
}
- a : tên của command
- b : giá trị của command
Ở đây, editor là biến toàn cục đã định nghĩa bên ngoài hàm như trình bày phía trên. Khi được gọi, doFormat sử dụng phương thức queryCommandEnabled của editor để kiểm tra khả năng thực thi command a. Sau đó, tiếp tục kiểm tra tham số thứ 2, nếu không thấy thì gán cho nó giá trị null. Nếu có thể thực thi command thì gọi phương thức execCommand.
Đối với việc chèn hình ảnh và liên kết, có một chút khác biệt, chúng ta viết 2 hàm addLink và insertImage dành riêng cho nhiệm vụ này. Chúng được gọi theo cách tương tự như 3 nút lệnh trước.
PHP Code:
<img src="image.gif" onclick="insertImage();">
<img src="link.gif" onclick="addLink();">
PHP Code:
editor.execCommand("CreateLink", true);
PHP Code:
editor.execCommand("InsertImage", true);
Tuy nhiên tính năng này chưa được Mozilla và Opera hỗ trợ, vì vậy, một số người lập trình Rich Text Editor đã tự xây dựng các kiểu DialogBox riêng để thay thế. Việc đó không khó, nhưng trong bài viết này, chúng ta sẽ đơn giản hóa công việc bằng 1 hộp thoại prompt.
Dưới đây là hàm addLink :
PHP Code:
function addLink(){
var aLink=prompt('Enter or paste a link :', ');
if(aLink){
doFormat('CreateLink', aLink);
}
}
PHP Code:
function insertImage(){
document.getElementById('textArea').contentWindow.focus();
var iURL=prompt('Enter or paste a URL :', ');
if(iURL){
doFormat('InsertImage', iURL);
}
}
2 tham số : "CreateLink" - tên command, và aLink - URL đích do người dùng cung cấp.
Khi hàm insertImage được gọi, chúng ta sử dụng dòng lệnh đầu tiên để focus vào vùng soạn thảo. (Điều này là cần thiết cho trình duyệt IE, bạn thử bỏ đi và chạy thử chương trình thì sẽ hiểu tại sao !). Tiếp đó, 1 hộp thoại prompt mở ra yêu cầu người sử dụng nhập vào URL của hình ảnh. Cuối cùng, chúng ta gọi doFormat với command "InsertImage" và giá trị iURL mà người dùng đã nhập.
Nút lệnh [] cho phép gỡ bỏ mọi định dạng trên phần text được chọn.
PHP Code:
<img src="removeformatting.gif" onclick="unformat();">
PHP Code:
function unformat(){
doFormat('removeformat');
doFormat('unlink');
}
Cuối cùng, chúng ta có toàn bộ mã HTML và JavaScript như dưới đây :
PHP Code:
<h1>Rich Text Editor</h1>
<table border="0" cellpadding="0" cellspacing="1" bgcolor="#e1f2ff">
<tr height="20">
<td>
<img src="bold.gif" title="Bold" onclick="doFormat('bold');">
<img src="italic.gif" title="Italic" onclick="doFormat('italic');">
<img src="underline.gif" title="Underline" onclick="doFormat('underline');">
<img src="image.gif" title="Insert Image" onclick="insertImage();">
<img src="link.gif" title="Hyperlink" onclick="addLink();">
<img src="removeformatting.gif" title="Remove Formatting" onclick="unformat();">
</td>
</tr>
<tr>
<td align="center" bgcolor="#ffffff">
<iframe id="textArea" style="width:500px;height:240px;overflow:auto;"></iframe>
</td>
</tr>
</table>
<script type="text/javascript">
var editor = document.getElementById('textArea').contentWindow.document;
editor.designMode='On';
editor.open();
editor.write('<html><head></head><body></body></html>');
editor.close();
function doFormat(a,b){
if(editor.queryCommandEnabled(a)){
if(!b){b=null;}
editor.execCommand(a,false,b);
}
}
function addLink(){
var aLink=prompt('Enter or paste a link :', ');
if(aLink){
doFormat('CreateLink', aLink);
}
}
function insertImage(){
document.getElementById('textArea').contentWindow.focus();
var aLink=prompt('Enter or paste a URL :', ');
if(aLink){
doFormat('InsertImage', aLink);
}
}
function unformat(){
doFormat('removeformat');
doFormat('unlink');
}
</script>
Demo :
http://sacroyant.uni.cc/Examples/Editor/V1/rte.htm
Phần sau, chúng ta sẽ tìm hiểu sâu hơn một số command khác để mở rộng tính năng cho editor này, đồng thời viết lại kịch bản điều khiển Editor theo kiểu hướng đối tượng. Hẹn gặp lại.
Bài liên quan
bold : in đậm text
fontname : font chữ, vValue :tên font. VD : arial, verdana...
fontsize : khổ chữ, vValue : các số từ 1 đến 7.
forecolor : màu chữ. vValue : chuỗi tên hoặc mã màu. VD : #0000ff, navy...
hilitecolor : màu nền (1). vValue : chuỗi tên hoặc mã màu. VD : #0000ff, navy...
italic : làm nghiêng text
subscript : text thấp xuống so với bình thường
superscript : đẩy text lên cao hơn bình thường
underline : gạch dưới text
Định dạng khối văn bản :
heading : Định dạng cho một tiêu đề. vValue : <h1>, <h2>, <h3>, <h4>, <h5>, <h6>.
indent : cho khối văn bản lui vào 1 tab
insertorderedlist : liệt kê theo số thứ tự
insertunorderedlist : liệt kê không đánh số.
justifycenter : căn giữa
justifyfull : dàn đều 2 biên
justifyleft : căn trái
justifyright : căn phải
outdent : cho khối văn bản lui ra 1 tab
Các command khác :
createlink : tạo liên kết. vValue : chuỗi URL
delete : xóa phần selection
inserthorizontalrule : chèn vào 1 phần tử <hr>
inserthtml : chèn vào 1 chuỗi HTML (2)
insertimage : chèn hình ảnh. vValue : đường dẫn đến file ảnh.
removeformat : loại bỏ các định dạng ở phần selection.
unlink : loại bỏ liên kết ở phần selection.
Các command điều khiển editor :
undo : khôi phục lại tình trạng trước khi có 1 thay đổi được ghi nhận (3)
redo : khôi phục lại tình trạng trước khi undo 1 bước.
selectall : chọn toàn bộ nội dung editor.
Danh sách trên chưa đầy đủ, nhưng là những gì cơ bản nhất cho 1 trình soạn thảo văn bản. Chúng là các commands nhận được sự hỗ trợ chung của nhiều trình duyệt.
Nhắc lại cú pháp của execCommand :
editableDocument.execCommand(sCommand [, bUserInterface] [, vValue])
Trong đó :
editableDocument : tên biến tham chiếu đến phần tử document của đối tượng iframe được dùng để giả lập khung soạn thảo.
sCommand : chuỗi tên command. Không phân biệt hoa - thường.
bUserInterface : biến tùy chọn hiển thị các DialogBox. Luôn thiết lập là false để tránh lỗi trong Mozilla và Opera.
vValue : giá trị cho command. Nếu command không cần chỉ định giá trị, sử dụng null.
Thông thường vValue là 1 chuỗi, ngoại trừ giá trị cho kích thước chữ có thể để dạng interger, nhưng nói chung vẫn nên đưa về kiểu string.
* Chú thích :
chỉ nên post khi thực sự cảm thấy rất cần thiết và nên post các bài mang tính xây dựng cho cái editor, chớ đừng post linh tinh, đây là một topic rất có ý nghĩa, ko cần các spammer/comment/suggestion vô thưởng vô phạt
các chủ nhân của những bài viết vô thưởng vô phạt nên tự delete by yourself đi, coi như là 1 hình thức tự kiểm điểm dzậy. Cho đến khi nào bạn sacroyant tuyên bố kết thúc cái tut này thì mọi người cứ "tự nhiên như người Hà Nội"; còn bây giờ nên để cho nó xúc tích và cô đọng và liền mạch hơn
1 point cho bài viết