IdeaEditor

12 Min. Read
Aug 16, 2019

Introduction to WYSIWYG Text Editor

WYSIWYG(abbreviation) is a content editing tool. In WYSIWYG editors the edited content, whether text or graphics, appears in a form close to a final product. So instead of manually writing source code, you deal with a convenient rich text editor in which you manipulate design elements. WYSIWYG editor allows you to see precisely how it will look like. It is an extremely useful tool to give your clients ability for editing and updating their website without bothering coders.

Inception of IdeaEditor

Several editors were used and experimented with. But none of them seemed to meet our expectation. I tried experimenting with editors such as CKEditor, but soon found out that CKEditor came with only a handful of features when our project required so much more. The project required multiple images to be embedded via the editor, however CKEditor offered images to be embedded only via a URL and did not allow user to embed image from their local computers. Similarly, I also tried experimenting with Froala editor, but found out that the user needed to upload the image, when we wanted the image to be embedded. Also, Froala editor was quite messy to deal with as each feature required a distinct plugin which wasn’t really ideal in the long run.

Unsatisfied with the editors provided online, we finally decided to create our own editor. Although the editors had a lot of features and did meet our expectations to a certain extent, it lacked in a lot of other aspects.

Conception of IdeaEditor

IdeaEditor is completely written in JavaScript and offers an UI and experience like most word processors. It offers all the basic functionalities and formats like headings, bold, italic. Other available actions include indentation, font name, font-size, embedding multiple images from a computer and inserting tables and consequently manipulating the table.

Here is a screenshot of how IdeaEditor looks like:

Logical Design of IdeaEditor

The whole input element was wrapped inside a div element which was defined as contenteditable

1
.text-container contenteditable="true"

The buttons through which actions could be carried out were also placed inside a div element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
.toolbar#toolbar
  button#alignLeft title="Align Left"
    i.fa.fa-align-left
  button#alignRight title="Align Right"
    i.fa.fa-align-right
  button#alignCenter title="Align Center"
    i.fa.fa-align-center
  button#alignJustify title="Align Justified"
    i.fa.fa-align-justify

  input#embedImage class="tool-items fa fa-file-image-o" type="file" accept="image/*" style="display: none;"
  button onclick="$('#embedImage').click()"
    label.fa.fa-file-image-o for="embedImage"
  |  
  |  
  |  
  button#bold title="Bold"
    i.fa.fa-bold
  button#italic title="Italic"
    i.fa.fa-italic
  button#underline title="Underline"
    i.fa.fa-underline
  button#insert-table title="Insert Table"
    i.fa.fa-table
  |  
  |  
  |  
  .visible-inline-block.table-toolbar
    button#appendCol title="Append Column"
      |<+ C
    button#prependCol title="Prepend Column"
      |+ C >
    button#removeColFromBack title="Remove Column From Back"
      |- C >
    button#removeColFromForward title="Remove Column From Forward"
      |<- C
    button#appendRow title="Append Row"
      |<+ R
    button#prependRow title="Prepend Row"
      |+ R >
    button#removeRowFromBottom title="Remove Column From Bottom"
      |- R >
    button#removeRowFromTop title="Remove Column From Top"
      |<- R
  select#fontStyle
    option value="h1" H1
    option value="h2" H2
    option value="h3" H3
    option value="h4" H4
    option value="h5" H5
    option value="h6" H6
    option value="<blockquote>" BlockQuote
    option value="address" Address
    option value="pre" Preformatted
  | &nbsp;
  select#fontSize
    option value="1" Font Size 1
    option value="2" Font Size 2
    option value="3" Font Size 3
    option value="4" Font Size 4
    option value="5" Font Size 5
    option value="6" Font Size 6
    option value="7" Font Size 7

The first four buttons represented the indentation of the text. The image button will allow users to embed images from their computers locally. The following three buttons would enable basic funtionalities such as bold, italic and underlining of the text. On clicking the table button, a table element will appear in the editor and columns and rows can be added and deleted through the subsequent buttons that follows it. To customize the font two options are provided. On clicking the font-size field a selection menu will be shown where the user can choose from the predefined options.


The editor functionalities were then designed using the logic described below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
var $selected = $("#editor");

class FormattingToolbar {
    static bold() {
        document.execCommand("bold", false, "");
    }

    static italic() {
        document.execCommand("italic", false, "");
    }

    static underline() {
        document.execCommand("underline", false, "");
    }

    static alignLeft() {
        document.execCommand("justifyLeft", false, "");
    }

    static alignRight() {
        document.execCommand("justifyRight", false, "");
    }

    static alignCenter() {
        document.execCommand("justifyCenter", false, "");
    }

    static alignJustify() {
        document.execCommand("justifyFull", false, "");
    }

    static fontStyle() {
        document.execCommand("formatBlock", false, $(this).val());
    }

    static fontSize() {
        document.execCommand("fontSize", false, $(this).val());
    }

    static getImage() {
        var file = document.querySelector("#toolbar input[type=file]").files[0];

        var reader = new FileReader();

        let dataURI;

        reader.addEventListener(
            "load",
            function () {
                dataURI = reader.result;
                const img = document.createElement("img");
                img.src = dataURI;
                document.execCommand("insertHTML", true, img.outerHTML);
            },
            false
        );

        if (file) {
            reader.readAsDataURL(file);

        }
    }
}

class TableEditor {
    static insertTable() {
        var tableMarkup = '<table class="table table-bordered w-75 m-auto my-5"><thead><th>Heading1</th><th>Heading2</th></thead><tbody><tr><td>Content</td><td>Content</td></tr></tbody></table>'
        document.execCommand("insertHTML", true, tableMarkup);
    }

    static appendRow() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference

        var row = tbl.insertRow(-1); // append table row
        var i;
        // insert table cells to the new row
        for (i = 0; i < tbl.rows[0].cells.length; i++) {
            let cell = row.insertCell(i);
            cell.classList.add("editable-in-paper");
            cell.textContent = "Value" + (i + 1);
        }
    }

    static prependRow() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference

        var row = tbl.tBodies[0].insertRow(0); // append table row
        var i;
        // insert table cells to the new row
        for (i = 0; i < tbl.rows[0].cells.length; i++) {
            let cell = row.insertCell(i);
            cell.classList.add("editable-in-paper");
            cell.textContent = "Value" + (i + 1);
        }
    }

    static removeRowFromBottom() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference

        var rows = tbl.tBodies[0].rows; // append table row
        rows[rows.length - 1].remove();
    }

    static removeRowFromTop() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference

        var rows = tbl.tBodies[0].rows; // append table row
        rows[0].remove();
    }

    static appendCol() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference
        var rows = tbl.rows;
        var i;
        for (i = 0; i < rows.length; i++) {
            let cell = rows[i].insertCell(0);
            cell.textContent = "Value1";
            if (i == 0) {
                cell.classList.add("editable");
            } else {
                cell.classList.add("editable-in-paper");
            }
        }
    }

    static prependCol() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference
        var rows = tbl.rows;
        var i;
        for (i = 0; i < rows.length; i++) {
            let cell = rows[i].insertCell(-1);
            cell.textContent = "Value1";
            if (i == 0) {
                cell.classList.add("editable");
            } else {
                cell.classList.add("editable-in-paper");
            }
        }
    }

    static removeColFromBack() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference
        var rows = tbl.rows;
        var i;
        for (i = 0; i < rows.length; i++) {
            let count = rows[i].cells.length;
            rows[i].cells[count - 1].remove();
        }
    }

    static removeColFromForward() {
        var tbl = $selected.hasClass("table")
            ? $selected.get(0)
            : $selected.find("table").get(0); // table reference
        var rows = tbl.rows;
        var i;
        for (i = 0; i < rows.length; i++) {
            rows[i].cells[0].remove();
        }
    }
}

$(document)
    .find("#bold")
    .click(FormattingToolbar.bold);
$(document)
    .find("#italic")
    .click(FormattingToolbar.italic);
$("#underline").click(FormattingToolbar.underline);
$("#alignLeft").click(FormattingToolbar.alignLeft);
$("#alignRight").click(FormattingToolbar.alignRight);
$("#alignCenter").click(FormattingToolbar.alignCenter);
$("#alignJustify").click(FormattingToolbar.alignJustify);
$("#fontStyle").change(FormattingToolbar.fontStyle);
$("#fontSize").change(FormattingToolbar.fontSize);
$("#embedImage").change(FormattingToolbar.getImage);

$("#insert-table").click(TableEditor.insertTable);
$("#appendRow").click(TableEditor.appendRow);
$("#prependRow").click(TableEditor.prependRow);
$("#appendCol").click(TableEditor.appendCol);
$("#prependCol").click(TableEditor.prependCol);
$("#removeColFromBack").click(TableEditor.removeColFromBack);
$("#removeColFromForward").click(TableEditor.removeColFromForward);

$("#removeRowFromBottom").click(TableEditor.removeRowFromBottom);
$("#removeRowFromTop").click(TableEditor.removeRowFromTop);

Two classes were defined in order to design the functionalities of the editor. FormattingToolbar dealt with the every functionalities of the editor other than the ones associated with table and its manipulation. The table and its manipulation was handled by TableEditor. The buttons were accessed through jQuery selector and its respective actions were handled by calling the appropriate methods from the specified classes. For embedding of images and table insertion, the innerHTML methods were called on the div element which would enable the images and table to be appended to the .text-editor element.


Summary

The creation of our customized editor definitely helped us to ensure the functionalities that we wanted. The ability to create our own editor will uplift the efficiency in our projects that follow, being that we shall not depend on other editors and can add functionalities according to the need and requirement. The freedom of not having to depend on other editors is the most advantageous of all.