1 // Cross-Browser Rich Text Editor
2 // http://www.kevinroth.com/rte/demo.htm
3 // Written by Kevin Roth (kevin@NOSPAMkevinroth.com - remove NOSPAM)
4 // Visit the support forums at http://www.kevinroth.com/forums/index.php?c=2
7 var isRichText = false;
22 function initRTE(imgPath, incPath, css) {
24 var ua = navigator.userAgent.toLowerCase();
25 isIE = ((ua.indexOf("msie") != -1) && (ua.indexOf("opera") == -1) && (ua.indexOf("webtv") == -1));
26 isGecko = (ua.indexOf("gecko") != -1);
27 isSafari = (ua.indexOf("safari") != -1);
28 isKonqueror = (ua.indexOf("konqueror") != -1);
30 //check to see if designMode mode is available
31 if (document.getElementById && document.designMode && !isSafari && !isKonqueror) {
35 if (!isIE) document.captureEvents(Event.MOUSEOVER | Event.MOUSEOUT | Event.MOUSEDOWN | Event.MOUSEUP);
36 document.onmouseover = raiseButton;
37 document.onmouseout = normalButton;
38 document.onmousedown = lowerButton;
39 document.onmouseup = raiseButton;
43 includesPath = incPath;
46 if (isRichText) document.writeln('<style type="text/css">@import "' + includesPath + 'rte.css";</style>');
48 //for testing standard textarea, uncomment the following line
52 function writeRichText(rte, html, width, height, buttons, readOnly) {
54 if (allRTEs.length > 0) allRTEs += ";";
56 writeRTE(rte, html, width, height, buttons, readOnly);
58 writeDefault(rte, html, width, height, buttons, readOnly);
62 function writeDefault(rte, html, width, height, buttons, readOnly) {
64 document.writeln('<textarea name="' + rte + '" id="' + rte + '" style="width: ' + width + 'px; height: ' + height + 'px;">' + html + '</textarea>');
66 document.writeln('<textarea name="' + rte + '" id="' + rte + '" style="width: ' + width + 'px; height: ' + height + 'px;" readonly>' + html + '</textarea>');
70 function raiseButton(e) {
72 var el = window.event.srcElement;
77 className = el.className;
78 if (className == 'rteImage' || className == 'rteImageLowered') {
79 el.className = 'rteImageRaised';
83 function normalButton(e) {
85 var el = window.event.srcElement;
90 className = el.className;
91 if (className == 'rteImageRaised' || className == 'rteImageLowered') {
92 el.className = 'rteImage';
96 function lowerButton(e) {
98 var el = window.event.srcElement;
103 className = el.className;
104 if (className == 'rteImage' || className == 'rteImageRaised') {
105 el.className = 'rteImageLowered';
109 function writeRTE(rte, html, width, height, buttons, readOnly) {
110 if (readOnly) buttons = false;
112 //adjust minimum table widths
114 if (buttons && (width < 600)) width = 600;
115 var tablewidth = width;
117 if (buttons && (width < 500)) width = 500;
118 var tablewidth = width + 4;
121 if (buttons == true) {
122 document.writeln('<table class="rteBack" cellpadding=2 cellspacing=0 id="Buttons1_' + rte + '" width="' + tablewidth + '">');
123 document.writeln(' <tr>');
124 document.writeln(' <td>');
125 document.writeln(' <select id="formatblock_' + rte + '" onchange="Select(\'' + rte + '\', this.id);">');
126 document.writeln(' <option value="">[Style]</option>');
127 document.writeln(' <option value="<p>">Paragraph</option>');
128 document.writeln(' <option value="<h1>">Heading 1 <h1></option>');
129 document.writeln(' <option value="<h2>">Heading 2 <h2></option>');
130 document.writeln(' <option value="<h3>">Heading 3 <h3></option>');
131 document.writeln(' <option value="<h4>">Heading 4 <h4></option>');
132 document.writeln(' <option value="<h5>">Heading 5 <h5></option>');
133 document.writeln(' <option value="<h6>">Heading 6 <h6></option>');
134 document.writeln(' <option value="<address>">Address <ADDR></option>');
135 document.writeln(' <option value="<pre>">Formatted <pre></option>');
136 document.writeln(' </select>');
137 document.writeln(' </td>');
138 document.writeln(' <td>');
139 document.writeln(' <select id="fontname_' + rte + '" onchange="Select(\'' + rte + '\', this.id)">');
140 document.writeln(' <option value="Font" selected>[Font]</option>');
141 document.writeln(' <option value="Arial, Helvetica, sans-serif">Arial</option>');
142 document.writeln(' <option value="Courier New, Courier, mono">Courier New</option>');
143 document.writeln(' <option value="Times New Roman, Times, serif">Times New Roman</option>');
144 document.writeln(' <option value="Verdana, Arial, Helvetica, sans-serif">Verdana</option>');
145 document.writeln(' </select>');
146 document.writeln(' </td>');
147 document.writeln(' <td>');
148 document.writeln(' <select unselectable="on" id="fontsize_' + rte + '" onchange="Select(\'' + rte + '\', this.id);">');
149 document.writeln(' <option value="Size">[Size]</option>');
150 document.writeln(' <option value="1">1</option>');
151 document.writeln(' <option value="2">2</option>');
152 document.writeln(' <option value="3">3</option>');
153 document.writeln(' <option value="4">4</option>');
154 document.writeln(' <option value="5">5</option>');
155 document.writeln(' <option value="6">6</option>');
156 document.writeln(' <option value="7">7</option>');
157 document.writeln(' </select>');
158 document.writeln(' </td>');
159 document.writeln(' <td width="100%">');
160 document.writeln(' </td>');
161 document.writeln(' </tr>');
162 document.writeln('</table>');
163 document.writeln('<table class="rteBack" cellpadding="0" cellspacing="0" id="Buttons2_' + rte + '" width="' + tablewidth + '">');
164 document.writeln(' <tr>');
165 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'bold.gif" width="25" height="24" alt="Bold" title="Bold" onClick="FormatText(\'' + rte + '\', \'bold\', \'\')"></td>');
166 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'italic.gif" width="25" height="24" alt="Italic" title="Italic" onClick="FormatText(\'' + rte + '\', \'italic\', \'\')"></td>');
167 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'underline.gif" width="25" height="24" alt="Underline" title="Underline" onClick="FormatText(\'' + rte + '\', \'underline\', \'\')"></td>');
168 document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
169 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'left_just.gif" width="25" height="24" alt="Align Left" title="Align Left" onClick="FormatText(\'' + rte + '\', \'justifyleft\', \'\')"></td>');
170 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'centre.gif" width="25" height="24" alt="Center" title="Center" onClick="FormatText(\'' + rte + '\', \'justifycenter\', \'\')"></td>');
171 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'right_just.gif" width="25" height="24" alt="Align Right" title="Align Right" onClick="FormatText(\'' + rte + '\', \'justifyright\', \'\')"></td>');
172 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'justifyfull.gif" width="25" height="24" alt="Justify Full" title="Justify Full" onclick="FormatText(\'' + rte + '\', \'justifyfull\', \'\')"></td>');
173 document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
174 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'hr.gif" width="25" height="24" alt="Horizontal Rule" title="Horizontal Rule" onClick="FormatText(\'' + rte + '\', \'inserthorizontalrule\', \'\')"></td>');
175 document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
176 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'numbered_list.gif" width="25" height="24" alt="Ordered List" title="Ordered List" onClick="FormatText(\'' + rte + '\', \'insertorderedlist\', \'\')"></td>');
177 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'list.gif" width="25" height="24" alt="Unordered List" title="Unordered List" onClick="FormatText(\'' + rte + '\', \'insertunorderedlist\', \'\')"></td>');
178 document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
179 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'outdent.gif" width="25" height="24" alt="Outdent" title="Outdent" onClick="FormatText(\'' + rte + '\', \'outdent\', \'\')"></td>');
180 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'indent.gif" width="25" height="24" alt="Indent" title="Indent" onClick="FormatText(\'' + rte + '\', \'indent\', \'\')"></td>');
181 document.writeln(' <td><div id="forecolor_' + rte + '"><img class="rteImage" src="' + imagesPath + 'textcolor.gif" width="25" height="24" alt="Text Color" title="Text Color" onClick="FormatText(\'' + rte + '\', \'forecolor\', \'\')"></div></td>');
182 document.writeln(' <td><div id="hilitecolor_' + rte + '"><img class="rteImage" src="' + imagesPath + 'bgcolor.gif" width="25" height="24" alt="Background Color" title="Background Color" onClick="FormatText(\'' + rte + '\', \'hilitecolor\', \'\')"></div></td>');
183 document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
184 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'hyperlink.gif" width="25" height="24" alt="Insert Link" title="Insert Link" onClick="FormatText(\'' + rte + '\', \'createlink\')"></td>');
185 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'image.gif" width="25" height="24" alt="Add Image" title="Add Image" onClick="AddImage(\'' + rte + '\')"></td>');
187 document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'spellcheck.gif" width="25" height="24" alt="Spell Check" title="Spell Check" onClick="checkspell()"></td>');
189 // document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
190 // document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'cut.gif" width="25" height="24" alt="Cut" title="Cut" onClick="FormatText(\'' + rte + '\', \'cut\')"></td>');
191 // document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'copy.gif" width="25" height="24" alt="Copy" title="Copy" onClick="FormatText(\'' + rte + '\', \'copy\')"></td>');
192 // document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'paste.gif" width="25" height="24" alt="Paste" title="Paste" onClick="FormatText(\'' + rte + '\', \'paste\')"></td>');
193 // document.writeln(' <td><img class="rteVertSep" src="' + imagesPath + 'blackdot.gif" width="1" height="20" border="0" alt=""></td>');
194 // document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'undo.gif" width="25" height="24" alt="Undo" title="Undo" onClick="FormatText(\'' + rte + '\', \'undo\')"></td>');
195 // document.writeln(' <td><img class="rteImage" src="' + imagesPath + 'redo.gif" width="25" height="24" alt="Redo" title="Redo" onClick="FormatText(\'' + rte + '\', \'redo\')"></td>');
196 document.writeln(' <td width="100%"></td>');
197 document.writeln(' </tr>');
198 document.writeln('</table>');
200 document.writeln('<iframe id="' + rte + '" name="' + rte + '" width="' + width + 'px" height="' + height + 'px" src="' + includesPath + 'blank.htm"></iframe>');
201 if (!readOnly) document.writeln('<br /><input type="checkbox" id="chkSrc' + rte + '" onclick="toggleHTMLSrc(\'' + rte + '\');" /> View Source');
202 document.writeln('<iframe width="154" height="104" id="cp' + rte + '" src="' + includesPath + 'palette.htm" marginwidth="0" marginheight="0" scrolling="no" style="visibility:hidden; display: none; position: absolute;"></iframe>');
203 document.writeln('<input type="hidden" id="hdn' + rte + '" name="' + rte + '" value="">');
204 document.getElementById('hdn' + rte).value = html;
205 enableDesignMode(rte, html, readOnly);
208 function enableDesignMode(rte, html, readOnly) {
209 var frameHtml = "<html id=\"" + rte + "\">\n";
210 frameHtml += "<head>\n";
211 //to reference your stylesheet, set href property below to your stylesheet path and uncomment
212 if (cssFile.length > 0) {
213 frameHtml += "<link media=\"all\" type=\"text/css\" href=\"" + cssFile + "\" rel=\"stylesheet\">\n";
215 frameHtml += "<style>\n";
216 frameHtml += "body {\n";
217 frameHtml += " background: #FFFFFF;\n";
218 frameHtml += " margin: 0px;\n";
219 frameHtml += " padding: 0px;\n";
221 frameHtml += "</style>\n";
223 frameHtml += "</head>\n";
224 frameHtml += "<body>\n";
225 frameHtml += html + "\n";
226 frameHtml += "</body>\n";
227 frameHtml += "</html>";
230 var oRTE = frames[rte].document;
232 oRTE.write(frameHtml);
234 if (!readOnly) oRTE.designMode = "On";
237 if (!readOnly) document.getElementById(rte).contentDocument.designMode = "on";
239 var oRTE = document.getElementById(rte).contentWindow.document;
241 oRTE.write(frameHtml);
243 if (isGecko && !readOnly) {
244 //attach a keyboard handler for gecko browsers to make keyboard shortcuts work
245 oRTE.addEventListener("keypress", kb_handler, true);
248 alert("Error preloading content.");
251 //gecko may take some time to enable design mode.
252 //Keep looping until able to set.
254 setTimeout("enableDesignMode('" + rte + "', '" + html + "', " + readOnly + ");", 10);
260 //contributed by TotalJSNoob and archv1le (thanks guys!)
261 //if the following gets uncommented, indenting and list items will not function correctly
263 // var hack = function () {rteKeyPress(document.getElementById(rte).contentWindow);};
264 // var oRTE = document.getElementById(rte).contentWindow;
265 // oRTE.document.onkeypress = hack;
269 function updateRTEs() {
270 var vRTEs = allRTEs.split(";");
271 for (var i = 0; i < vRTEs.length; i++) {
276 function updateRTE(rte) {
277 if (!isRichText) return;
280 var oHdnMessage = document.getElementById('hdn' + rte);
281 var oRTE = document.getElementById(rte);
282 var readOnly = false;
284 //check for readOnly mode
286 if (frames[rte].document.designMode != "On") readOnly = true;
288 if (document.getElementById(rte).contentDocument.designMode != "on") readOnly = true;
291 if (isRichText && !readOnly) {
292 //if viewing source, switch back to design view
293 if (document.getElementById("chkSrc" + rte).checked) {
294 document.getElementById("chkSrc" + rte).checked = false;
298 if (oHdnMessage.value == null) oHdnMessage.value = "";
300 oHdnMessage.value = frames[rte].document.body.innerHTML;
302 oHdnMessage.value = oRTE.contentWindow.document.body.innerHTML;
305 //if there is no content (other than formatting) set value to nothing
306 if (stripHTML(oHdnMessage.value.replace(" ", " ")) == ""
307 && oHdnMessage.value.toLowerCase().search("<hr") == -1
308 && oHdnMessage.value.toLowerCase().search("<img") == -1) oHdnMessage.value = "";
310 if (escape(oHdnMessage.value) == "%3Cbr%3E%0D%0A%0D%0A%0D%0A") oHdnMessage.value = "";
314 function toggleHTMLSrc(rte) {
315 //contributed by Bob Hutzel (thanks Bob!)
318 oRTE = frames[rte].document;
320 oRTE = document.getElementById(rte).contentWindow.document;
323 if (document.getElementById("chkSrc" + rte).checked) {
324 document.getElementById("Buttons1_" + rte).style.visibility = "hidden";
325 document.getElementById("Buttons2_" + rte).style.visibility = "hidden";
327 oRTE.body.innerText = oRTE.body.innerHTML;
329 var htmlSrc = oRTE.createTextNode(oRTE.body.innerHTML);
330 oRTE.body.innerHTML = "";
331 oRTE.body.appendChild(htmlSrc);
334 document.getElementById("Buttons1_" + rte).style.visibility = "visible";
335 document.getElementById("Buttons2_" + rte).style.visibility = "visible";
338 var output = escape(oRTE.body.innerText);
339 output = output.replace("%3CP%3E%0D%0A%3CHR%3E", "%3CHR%3E");
340 output = output.replace("%3CHR%3E%0D%0A%3C/P%3E", "%3CHR%3E");
342 oRTE.body.innerHTML = unescape(output);
344 var htmlSrc = oRTE.body.ownerDocument.createRange();
345 htmlSrc.selectNodeContents(oRTE.body);
346 oRTE.body.innerHTML = htmlSrc.toString();
351 //Function to format text in the text box
352 function FormatText(rte, command, option) {
357 //get current selected range
358 var selection = oRTE.document.selection;
359 if (selection != null) {
360 rng = selection.createRange();
363 oRTE = document.getElementById(rte).contentWindow;
365 //get currently selected range
366 var selection = oRTE.getSelection();
367 rng = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
371 if ((command == "forecolor") || (command == "hilitecolor")) {
372 //save current values
373 parent.command = command;
376 //position and show color palette
377 buttonElement = document.getElementById(command + '_' + rte);
378 // Ernst de Moor: Fix the amount of digging parents up, in case the RTE editor itself is displayed in a div.
379 document.getElementById('cp' + rte).style.left = getOffsetLeft(buttonElement, 4) + "px";
380 document.getElementById('cp' + rte).style.top = (getOffsetTop(buttonElement, 4) + buttonElement.offsetHeight + 4) + "px";
381 if (document.getElementById('cp' + rte).style.visibility == "hidden") {
382 document.getElementById('cp' + rte).style.visibility = "visible";
383 document.getElementById('cp' + rte).style.display = "inline";
385 document.getElementById('cp' + rte).style.visibility = "hidden";
386 document.getElementById('cp' + rte).style.display = "none";
388 } else if (command == "createlink") {
389 var szURL = prompt("Enter a URL:", "");
391 //ignore error for blank urls
392 oRTE.document.execCommand("Unlink", false, null);
393 oRTE.document.execCommand("CreateLink", false, szURL);
399 oRTE.document.execCommand(command, false, option);
407 //Function to set color
408 function setColor(color) {
409 var rte = currentRTE;
414 oRTE = document.getElementById(rte).contentWindow;
417 var parentCommand = parent.command;
419 //retrieve selected range
420 var sel = oRTE.document.selection;
421 if (parentCommand == "hilitecolor") parentCommand = "backcolor";
423 var newRng = sel.createRange();
429 oRTE.document.execCommand(parentCommand, false, color);
431 document.getElementById('cp' + rte).style.visibility = "hidden";
432 document.getElementById('cp' + rte).style.display = "none";
435 //Function to add image
436 function AddImage(rte) {
441 //get current selected range
442 var selection = oRTE.document.selection;
443 if (selection != null) {
444 rng = selection.createRange();
447 oRTE = document.getElementById(rte).contentWindow;
449 //get currently selected range
450 var selection = oRTE.getSelection();
451 rng = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
454 imagePath = prompt('Enter Image URL:', 'http://');
455 if ((imagePath != null) && (imagePath != "")) {
457 oRTE.document.execCommand('InsertImage', false, imagePath);
462 //function to perform spell check
463 function checkspell() {
465 var tmpis = new ActiveXObject("ieSpell.ieSpellExtension");
466 tmpis.CheckAllLinkedDocuments(document);
469 if(exception.number==-2146827859) {
470 if (confirm("ieSpell not detected. Click Ok to go to download page."))
471 window.open("http://www.iespell.com/download.php","DownLoad");
473 alert("Error Loading ieSpell: Exception " + exception.number);
478 // Ernst de Moor: Fix the amount of digging parents up, in case the RTE editor itself is displayed in a div.
479 function getOffsetTop(elm, parents_up) {
480 var mOffsetTop = elm.offsetTop;
481 var mOffsetParent = elm.offsetParent;
484 parents_up = 10000; // arbitrary big number
486 while(parents_up>0 && mOffsetParent) {
487 mOffsetTop += mOffsetParent.offsetTop;
488 mOffsetParent = mOffsetParent.offsetParent;
495 // Ernst de Moor: Fix the amount of digging parents up, in case the RTE editor itself is displayed in a div.
496 function getOffsetLeft(elm, parents_up) {
497 var mOffsetLeft = elm.offsetLeft;
498 var mOffsetParent = elm.offsetParent;
501 parents_up = 10000; // arbitrary big number
503 while(parents_up>0 && mOffsetParent) {
504 mOffsetLeft += mOffsetParent.offsetLeft;
505 mOffsetParent = mOffsetParent.offsetParent;
512 function Select(rte, selectname) {
517 //get current selected range
518 var selection = oRTE.document.selection;
519 if (selection != null) {
520 rng = selection.createRange();
523 oRTE = document.getElementById(rte).contentWindow;
525 //get currently selected range
526 var selection = oRTE.getSelection();
527 rng = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
530 var idx = document.getElementById(selectname).selectedIndex;
531 // First one is always a label
533 var selected = document.getElementById(selectname).options[idx].value;
534 var cmd = selectname.replace('_' + rte, '');
536 oRTE.document.execCommand(cmd, false, selected);
538 document.getElementById(selectname).selectedIndex = 0;
542 function kb_handler(evt) {
543 var rte = evt.target.id;
545 //contributed by Anti Veeranna (thanks Anti!)
547 var key = String.fromCharCode(evt.charCode).toLowerCase();
550 case 'b': cmd = "bold"; break;
551 case 'i': cmd = "italic"; break;
552 case 'u': cmd = "underline"; break;
556 FormatText(rte, cmd, true);
557 //evt.target.ownerDocument.execCommand(cmd, false, true);
558 // stop the event bubble
559 evt.preventDefault();
560 evt.stopPropagation();
565 function docChanged (evt) {
569 function stripHTML(oldString) {
570 var newString = oldString.replace(/(<([^>]+)>)/ig,"");
572 //replace carriage returns and line feeds
573 newString = newString.replace(/\r\n/g," ");
574 newString = newString.replace(/\n/g," ");
575 newString = newString.replace(/\r/g," ");
578 newString = trim(newString);
583 function trim(inputString) {
584 // Removes leading and trailing spaces from the passed string. Also removes
585 // consecutive spaces and replaces it with one space. If something besides
586 // a string is passed in (null, custom object, etc.) then return the input.
587 if (typeof inputString != "string") return inputString;
588 var retValue = inputString;
589 var ch = retValue.substring(0, 1);
591 while (ch == " ") { // Check for spaces at the beginning of the string
592 retValue = retValue.substring(1, retValue.length);
593 ch = retValue.substring(0, 1);
595 ch = retValue.substring(retValue.length-1, retValue.length);
597 while (ch == " ") { // Check for spaces at the end of the string
598 retValue = retValue.substring(0, retValue.length-1);
599 ch = retValue.substring(retValue.length-1, retValue.length);
602 // Note that there are two spaces in the string - look for multiple spaces within the string
603 while (retValue.indexOf(" ") != -1) {
604 // Again, there are two spaces in each of the strings
605 retValue = retValue.substring(0, retValue.indexOf(" ")) + retValue.substring(retValue.indexOf(" ")+1, retValue.length);
607 return retValue; // Return the trimmed string back to the user
610 //contributed by archv1le (thanks archv1le!)
611 function rteKeyPress(window) {
612 if (window.event.keyCode == 13) {
613 var range = window.document.selection.createRange();
614 var obj = range.parentElement();
615 if (obj.tagName != "LI") {
616 window.event.returnValue = false; // cancel Standard-event
617 range.pasteHTML('<br>');
618 range.select(); // re-sets the cursor to the right position