Задача: при редактировании данных в визуальном редакторе иметь возможность вставлять произвольные элементы форматирования, например, заголовки.
- Простейший WYSIWYG (визуальный редактор)
- WYSIWYG: вставляем произвольный HTML-код
Диалог с пользователем — создаем ссылки
Усложним код нашего простейшего висивига, добавив туда кнопку для добавления ссылок. Прежде чем добавить ссылку, необходимо спросить у пользователя её URL. Сделаем это с помощью метода prompt. Всего несколько строк кода необходимо добавить к текущему коду висивига, чтобы заработало добавление ссылок:
document.write("<input type='button' value='Link' onclick='setLink()' class='under' />");
// Запишем код функции для выставления форматирования
// Используется метод execCommand объекта document
function setLink() {
var url = prompt("Введите URL:", "http://");
if (!url) return;
iWin.focus();
iWin.document.execCommand("CreateLink", null, url);
}
Пример простейшего ВИСИВИГа с функцией добавления ссылок
Как видно из примера, метод execCommand принимает в качестве третьего аргумента параметр команды CreateLink — URL ссылки. Не только создание ссылки требует диалога с пользователем. Чтобы задать цвет текста или цвет фона (команды ForeColor и BackColor соот-но), необходимо попросить юзера выбрать этот цвет (обычно из какого-то списка), а затем передать его методу в формате #RRGGBB.
Вставляем произвольный HTML-контент
Вам необходимо вставить в визуальный редактор вполне конкретный HTML-код, например такой:
Метод execCommand бессилен для решении такой задачи. Необходимо собственное решение, например, такой обходной путь:
- Чтобы поставить произвольное форматирование на выделенный фрагмент, воспользуемся командой forecolor, которая выставит цвет текста. Цвет специально подберем такой, которым пользуются нечасто.
- В зависимости от браузера цвет выставляется либо с помощью <font color="#RRGGBB"></font> (IE, Opera), либо с помощью <span style="color:rgb(RR,GG,BB)"></span> (Gecko). Чтобы привести цвет к единому формату #RRGGBB воспользуемся функцией rgbNormal().
- Пройдемся по всем узлам DOM-дерева документа, найдем фонты и спаны с нужным цветом и добавим в них необходимый HTML-код, не забывая почистить за собой. Обход по узлам — дело не простое. Т.к. мы удаляем узлы с нужным цветом, необходимо идти от самых вложенных элементов вверх по иерархии узлов, чтобы не получилось сбоя. Для этого восопользуемся функцией nodeList, которая отдаст массив узлов с указанием степени их вложенности, который можно отсортировать.
Новый код:
// ШАГ 5: Форматирование произвольным HTML-контентом
// ***********************
// nodeList - формирует массив всех узлов с указанием степени их вложенности
function nodeList(parentNode, list, level) {
var i, node, count;
if (!list) list = new Array();
level++;
for (i = 0; i < parentNode.childNodes.length; i++) {
node = parentNode.childNodes[i];
if (node.nodeType != 1) continue;
count = list.length;
list[count] = new Array();
list[count][0] = node;
list[count][1] = level;
nodeList(node, list, level);
}
return list;
}
// rgbNormal - приводит цвет к стандарту #RRGGBB
function rgbNormal(color) {
color = color.toString();
var re = /rgb\((.*?)\)/i;
if(re.test(color)) {
compose = RegExp.$1.split(",");
var hex = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'];
var result = "#";
for (var i = 0; i < compose.length; i++) {
rgb = parseInt(compose[i]);
result += hex[parseInt(rgb / 16)] + hex[rgb % 16];
}
return result;
} else return color;
}
function execCommandImitation(start, end) {
// Cтавим ForeColor-форматирование с помощью специального цвета
iDoc.execCommand("ForeColor", false, "#f5F856");
// Получаем все элементы форматируемого документа
var allNodes = nodeList(iDoc.body, false, 0);
// Сортируем их по уровню вложенности
var maxLevel = 0;
for (i = 0; i < allNodes.length; i++) {
maxLevel = allNodes[i][1] > maxLevel ? allNodes[i][1] : maxLevel;
}
// 4. Для всех элементов заменяем FONT и SPAN со специальным цветом на переданный код
var node, newnode, color, parent;
for (j = maxLevel; j >= 1; j--) {
for (i = 0; i < allNodes.length; i++) {
if (allNodes[i][1] != j) continue;
node = allNodes[i][0];
sname = node.nodeName.toLowerCase();
color = node.color ? rgbNormal(node.color) : rgbNormal(node.style.color);
if (color) color = color.toLowerCase();
if (sname == "font" || sname == "span" && color == "#f5f856") {
try {
node.innerHTML = start + node.innerHTML + end;
} catch(e) {}
parent = node.parentNode;
while (node.childNodes.length > 0) parent.insertBefore(node.firstChild, node);
parent.removeChild(node);
}
}
}
iWin.focus();
}
Пример ВИСИВИГа c произвольным HTML-форматированием
Чтобы заголовок был оранжевым, при формировании стилей документа был добавлен класс:
Поверено в WIN: IE6, IE7, FF2, Opera 9.5, Safari 3.
Задача решена!

И мне пока непонятно как передавать в редактор данные и получать их обратно в такой реализации ?
В качестве готового продукта можно использовать редактор tinymce. Если интересуют более легкие решения, или решения с кодом, который можно изменять - следите за обновлениями статей на фасткодере.
Громадное спасибо за вашу работу. Среди груды мусора, вкаченной в поисковики, ваша разработка единственно понятна и проиллюстрированна. Особенно радует список команд.
И еще вопрос, можно ли использовать вашу разработку для создания собственного редактора? То есть апгрейдить его на основе вашего кода?
спасибо за внимание, с уважением, sstib.
Модифицировать код, естественно,можно как угодно под свои нужды. Будет замечательно, если потом поделитесь ссылкой на получившийся результат.
Опубликуете в комментарии вашу реализацию? Интересно было бы взглянуть.
//Шаг 1: создание елемента.
var newElement = frames.myIframe.document.createElement("H1");//создаём тег H1
var createNode = frames.myIframe.document.all.myBody.appendChild(newElement);//вставляем тег H1 в тело фрейма
//Шаг 2: создание текстовой области.
var range = frames.myIframe.document.body.createTextRange();//новая текстовая область
range.move("textedit");//перемещение курсора в начало текстовой области, то есть между тегами <H1></H1>
range.select();//выделение текстовой области
}
Таков мой подход к реализации форматирования заголовков и обзацов в визуальном редакторе.НО! должен оговориться, что объект TextRange() можно применять только для IE4+.Для Safari,Opera и Mozilla следует применить объект Range(). Если будут какие-нибудь интересные соображения пишите))
Дополнил так на основе вашего скрипта
iWin.focus();
execCommandImitation("<div style='border: 2px solid rgb(255, 0, 0); padding: 3px;'>", "</div>");
}
Но проблема вот в чём.... если вставить текст в редактор и потом выбрать рамку то рамка появляется у каждой новой строки. Не подскажите можно ли это поправить и как?
Если я правильно понял суть проблемы - поможет.
То есть вот так теперь во фрейме
1) Заменить стиль на class.
2) Сделать обертку из внешнего ДИВа без стиля.
Мой опыт создания висивигов говорит, что лучше использовать первый фикс.
Если не получится - покажите страницу с примером.
Я попробовал внести изменения в код
try {
i == 0 ? start = start : start = "";
i == allNodes.length ? end = end : end = "";
node.innerHTML = start + node.innerHTML + end;
} catch(e) {}
Но я в яваскрипте не ахти, к сожалению код не работает. Всё равно закрывает конечный тег каждый раз. Очень на вас надеюсь, в интернте единственный вменяемый скрипт который избавлен от лишнего ненужного мне функционала. Есть идеи какие-нибудь по исправлению?
Если всё таки понадобится сама страничка с примером после 14 по Москве выложу в инет.
Сначала я был занят, а потом забыл.
Пока этот комментарий висит в топе на морде - не забуду. Первым делом напишу вам скрипт.
"Пока этот комментарий висит в топе на морде - не забуду"
:))
Можете здесь продублировать?
Долго пытался прикрутить готовые решения по WYSISYG к своей админке , чего терпеть не могу делать, уже было остановился на TinyMCE, но крутился по ночам. Сильно буржуйский и сильно много для меня кода там. Как ни странно толковых статей на эту тему в инете по пальцам можно пересчитать. С языком java знаком совсем чуть-чуть, но подробные коменты и логичная последовательность информации в статье помогла разобраться со всем.
Теперь понятно куда копать и какой ширины напильник брать
Зарегился только, чтобы сказать автору СПАСИБО! )
А TinyMCE, кстати, вы зря обижаете, очень правильный визуальный редактор с хорошо выверенным кодом и богатым набором возможностей. Но чего не сделаешь ради душевоного спокойствия :-)
Еще поправка. Язык, на котором написан wysiwyg, называется JavaScript (джаваскрипт). Язык Java - это совсем другая песня.
На счет языка, конечно же яваскрипт )
уж простите за некорректное сокращение. Разницу я вроде как понимаю )))
Еще все визивиги которые я посмотрел не умеют корректно чистить код MS Word
Начал наполнять один сайтик контентом через визивиг. Контент копипастил из Worda
Команда RemoveFormat убирает не весь мусор
в итоге после очистки такое предложение
превращается в
<xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:PunctuationKerning/> <w:ValidateAgainstSchemas/> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:Compatibility> <w:BreakWrappedTables/> <w:SnapToGridInCell/> <w:WrapTextWithPunct/> <w:UseAsianBreakRules/> <w:DontGrowAutofit/> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> </w:WordDocument></xml><![endif]--><!--[if gte mso 9]>
<xml> <w:LatentStyles DefLockedState="false" LatentStyleCount="156"> </w:LatentStyles></xml><![endif]--><style><!-- /* Font Definitions */ @font-face {font-family:Calibri; panose-1:2 15 5 2 2 2 4 3 2 4; mso-font-charset:204; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-1610611985 1073750139 0 0 159 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:Calibri;}@page Section1 {size:612.0pt 792.0pt; margin:2.0cm 42.5pt 2.0cm 3.0cm; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;}div.Section1 {page:Section1;}--></style><!--[if gte mso 10]>
<style> /* Style Definitions */ table.MsoNormalTable {mso-style-name:"Обычная таблица"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman"; mso-ansi-language:#0400; mso-fareast-language:#0400; mso-bidi-language:#0400;}</style><![endif]-->пылесос для влажной и сухой уборки, нержавеющийкорпус, с возможностью подключения ручного инструмента
соображения: пройтись по всем узлам и удалить их сразу после вставки, потом продолжать редактировать. Либо изначально вставлять текст в textarea а при начале форматирования переключение в iframe? А может я велосипед изобретаю?...)
Если хочется оставить часть разметки (заголовки, ссылки), то можно сделать так:
1) Получить все узлы документа из висивига: iDoc.getElementByTagName('*');
2) Идти итерационно от самых вложенных (это важно!) узлов и при нахождении неугодного безжалостно его заменять на текстовый узел.
Примерно так.
2)Тоже сталкнулся с такой проблемой, я сделал так:
function removeFormat() {
subject=iWin.document.body.innerHTML;
re = /<.*?[^<>]>/gi;
iWin.document.body.innerHTML=subject.replace(re, "");
}
- удаляет все теги HTML с помощью рег.выражений.
НО! Нужно оставить разрывы строк для этого предварительно меняем "<BR>" на "\r\n", например так str=str.split("<BR>").join("\r\n"); После удаления всех тегов возвращаем обратно - str=str.split("\r\n").join("<BR>");
В остальных браузерах можно "руками" ставить <font> с нужным фоновым цветом, например, при помощи функции execCommandImitation.
В лисе сейчас это: <i style="color: rgb(245, 248, 86);">разметкой</i>
Есть команда useCSS, хотя она в статусе deprecated, но вроде помогает:
try { iDoc.execCommand('useCSS', false, true) } catch(e) {}
По статье в целом: очень интересно, хотя код действительно стоит переписать, слишком уж неоптимально.
1. Не понял, зачем вам нужны циклы? Мы сначала выделяем нужный кусок текста, закрашиваем его, создаем нод. => Самый первый тег будет font или span, значит можно сразу присвоить
2. У вас, при выделении области, которая обрамлена h1 и повторного нажатия кнопки - область снова обрамляется h1. ИМХО нелогично. Выделение h1 должно убираться. После некотрых мучений, пришел к выводу, что в функцию надо передавать еще и сам тег. Прогонять в цикле
Короче говоря найти момент, где h1 надо тереть мне удалось. Но выполнить само удаление не вышло. Догадываюсь, что нужно чистить все от найденного блока от тегов вообще, (например встроенным в АПИ RemoveFormat), потом обойти еще раз ноды и по создавать удаленные элементы заново (без h1, естественно). Короче говоря идея кривая, можете предложить что-нибудь поинтереснее?
Кстати, в chrome обнаружил баг с RemoveFormat - после юзания команды все пробелы заменяются неразрывными (((
А по поводу таблиц - нужна функция по вставке произвольного кода не по выделению текста...
1) Пусть у вас есть форма со скрытой текстарией:
<textarea id="codeId" name="code" style="display:none;"></textarea>
<input type="submit" value="Отправить" />
</form>
2) При сабмите происходит вызов ф-ии getCode:
document.getElementById("codeId").value = iDoc.body.innerHTML;
}
3) В скрытую текстарию запишется HTML-код, который уйдет на сервер.
<head>
<meta http-equiv="content-type" content="text/html; charset=cp1251" />
<title>Простой ВИСИВИГ (WYSIWYG)</title>
<link type="text/css" rel="stylesheet" href="http://fastcoder.org/style.css" />
<style type="text/css">
body {margin:10px;}
iframe {
width:600px; height:150px;
border:1px solid #000;
margin-bottom:5px;
}
input {margin-right:5px; padding:3px;}
.bold {font-weight:bold;}
.ital {font-style:italic;}
.under {text-decoration:underline;}
</style>
</head>
<body>
<h1>post message</h1>
<script type="text/javascript">
// ***********************
// ШАГ 1: вывод iframe и получение доступа к нему
// ***********************
// Выводим в HTML-поток iframe
document.write("<iframe scrolling='no' frameborder='no' src='#' id='frameId' name='frameId'></iframe><br/>");
// Определим Gecko-браузеры, т.к. они отличаются в своей работе от Оперы и IE
var isGecko = navigator.userAgent.toLowerCase().indexOf("gecko") != -1;
// Получаем доступ к объектам window & document для ифрейма
var iframe = (isGecko) ? document.getElementById("frameId") : frames["frameId"];
var iWin = (isGecko) ? iframe.contentWindow : iframe.window;
var iDoc = (isGecko) ? iframe.contentDocument : iframe.document;
// ***********************
// ШАГ 2: Добавим на пустую страницу ифрейма произвольный HTML-код
// ***********************
// Формируем HTML-код
iHTML = "<html><head>\n";
iHTML += "<style>\n";
iHTML += "body, div, p, td {font-size:12px; font-family:tahoma; margin:0px; padding:0px;}";
iHTML += "body {margin:5px;}";
iHTML += ".oranzh {color:#FF6300;}";
iHTML += "</style>\n";
iHTML += "<body></body>";
iHTML += "</html>";
// Добавляем его с помощью методов объекта document
iDoc.open();
iDoc.write(iHTML);
iDoc.close();
// ***********************
// ШАГ 3: Инициализация свойства designMode объекта document
// ***********************
if (!iDoc.designMode) alert("Визуальный режим редактирования не поддерживается Вашим браузером");
else iDoc.designMode = (isGecko) ? "on" : "On";
// ***********************
// ШАГ 4: Простейшие элементы редактирования: жирность, курсив, подчеркивание
// ***********************
// Выведем HTML-код этих элементов
document.write("<input type='button' value='Ж' onclick='setBold()' class='bold' />");
document.write("<input type='button' value='К' onclick='setItal()' class='ital' />");
document.write("<input type='button' value='Ч' onclick='setUnder()' class='under' />");
document.write("<input type='button' value='Link' onclick='setLink()' class='under' />");
// Запишем код функции, для выставления форматирования
// Используется метод execCommand объекта document
function setBold() {
iWin.focus();
iWin.document.execCommand("bold", null, "");
}
function setItal() {
iWin.focus();
iWin.document.execCommand("italic", null, "");
}
function setUnder() {
iWin.focus();
iWin.document.execCommand("underline", null, "");
}
function setLink() {
var url = prompt("Введите URL:", "http://");
if (!url) return;
iWin.focus();
iWin.document.execCommand("CreateLink", null, url);
}
function setH1() {
iWin.focus();
execCommandImitation("<h1 class='oranzh'>", "</h1>");
}
// ***********************
// ШАГ 5: Форматирование произвольным HTML-контентом
// ***********************
// nodeList - формирует массив всех узлов с указанием степени их вложенности
function nodeList(parentNode, list, level) {
var i, node, count;
if (!list) list = new Array();
level++;
for (i = 0; i < parentNode.childNodes.length; i++) {
node = parentNode.childNodes[i];
if (node.nodeType != 1) continue;
count = list.length;
list[count] = new Array();
list[count][0] = node;
list[count][1] = level;
nodeList(node, list, level);
}
return list;
}
// rgbNormal - приводит цвет к стандарту #RRGGBB
function execCommandImitation(start, end) {
// Cтавим ForeColor-форматирование с помощью специального цвета
iDoc.execCommand("ForeColor", false, "#f5F856");
// Получаем все элементы форматируемого документа
var allNodes = nodeList(iDoc.body, false, 0);
// Сортируем их по уровню вложенности
var maxLevel = 0;
for (i = 0; i < allNodes.length; i++) {
maxLevel = allNodes[i][1] > maxLevel ? allNodes[i][1] : maxLevel;
}
// 4. Для всех элементов заменяем FONT и SPAN со специальным цветом на переданный код
iWin.focus();
}
function getCode() {
document.getElementById("codeId").value = iDoc.body.innerHTML;
}
</script>
<form onsubmit="getCode();" method='POST'>
<textarea id="codeId" name="code" style="display:none;"></textarea>
<input type="submit" value="Отправить" />
</form>
<?php
print "$_POST[code]";
?>
</body>
</html>
если писать
iWin.focus();
iWin.document.execCommand("bold", null, ""); }
то текст передается не отформатированый, а если заменить
execCommandImitation("<h1 class='oranzh'>", "</h1>");
iWin.focus();
execCommandImitation("<b class='bold'>", "</b>"); }
то в фрейме форматирования, при задании жирности, ее нельзя отменить =(, но зато передается POSTom форматированный..
как быть? ( JS вообще впервые вижу, освоил только Php)
Сформулируйте его более четко.
text.nodeValue = "Пример абзаца.";
var elem = document.createElement("P");
elem.appendChild(text);
iWin.focus();
iWin.document.execCommand('CreateLink', null, 'http://1234567890/');
var list = iWin.document.body.getElementsByTagName("a");
for (var i = 0; i < list.length; i++)
if (list[i].href == 'http://1234567890/')
{
list[i].parentNode.replaceChild(elem, list[i]);
}
Суть в том, чтобы отметить выделенный текст по какому-то параметру затем найти его элемент по этому параметру и заменить, определив его через ссылку на родителя. Бывают проблемы, если выделенный текст принадлежит разным форматам, например [b]111[i]222[\b]333[\i] но такого при нормальной верстке не бывает, поэтому я пользуюсь этим вариантом.