Главная страница статей --> Хитрости при программировании php, заметки по базам данных

Пишем правильный online WYSIWYG-редактор

Источник: realcoding.net

[1 страница]
Введение и понимание сути проблемы

Зачем это нужно


WYSIWYG (What You See Is What You Get) — это среда, в которой пользователь сразу видит результат своей работы. К примеру, редактор Frontpage — мы сразу видим документ в практически окончательном виде, в отличие от работы с исходным кодом страницы. Сделать редактор онлайновым позволяет поддержка браузерами Microsoft Internet Explorer (версии 5.5 и выше), Mozilla (1.3+)/ Firefox и Opera (9.0+) режима WYSIWYG-редактирования текста (designMode).

Примечание 1: Mozilla и Firefox далее будем объединять именем Gecko (это название их движка).

Примечание 2: Пока Opera 9 не является релизом, поэтому про нее потом напишем. Вообще, реализация designMode в Opera похожа на реализацию такового в Gecko.

WYSIWYG-редактор позволяет значительно облегчить рутинную работу публикации контента. Также он снижает требования к квалификации работающего с контентом персонала. Так что штука это полезная и нужная.

Но есть у него и серьезный недостаток — назовем его "синдромом Ворда" — преобладание визуальности разметки над логикой, когда, условно говоря, заголовок в документе делается путем выставления большого и жирного шрифта. В нашем проекте мы попытаемся избежать этого недостатка (или по крайней мере минимизировать его).

Стоит разграничить верстальщика, который делает шаблоны, сложные конструкции, и оператора, который работает с контентом. Будем считать, что онлайн-редактор стоит делать ориентируясь главным образом именно на оператора, верстальщик пускай верстает в блокноте или специализированном редакторе. Он профессионал, ему виднее. А вот для оператора дополнительные редакторы — лишнее. Поэтому пусть он работает в административном интерфейсе с нашим WYSIWYG-редактором, одной из задач которого будет не выпускать оператора за пределы заданной дизайнером таблицы стилей (и облегчать ему "возвращение на путь истинный"). Не должно быть возможности (или она должна быть затруднена) повесить на сайт "жирный красный комик +3" в обход дизайнера.

Каким это должно быть (Правильный WYSIWYG)


Очевидно, что подавляющее большинство предлагающихся online-редакторов построены по принципу "Выберите шрифт, размер, цвет", т. е. к правильным не относятся. Ибо разметку кода, полученного с их помощью, логической назвать никоим образом нельзя. Значит, мы будем писать редактор, где пользователь оперирует сущностями типа "Заголовок N-го уровня", "Абзац-примечание" и "Важный текст", а как они выглядят — решает дизайнер сайта, прописав свое решение в таблице стилей. Поэтому наш редактор должен будет оперировать разрешенными тегами и стилевыми классами.

Воплощение


Большой проблемой, стоящей перед нами, является то, что стандартные интерфейсы к встроенному редактору, реализованные в браузерах, как раз имеют большой уклон в сторону "шрифта-размера-цвета". Этим, кстати, и обусловлена "неправильность" вышеупомянутых WYSIWYG-редакторов. В интерфейсе есть команда "покрасить шрифт", есть команда "выставить размер шрифта", но нет команды "обрамить выделение нужным тегом с нужным классом". Подробнее об API (Application Program Interface — интерфейс к программированию приложения) встроенных редакторов см. информацию на сайтах Microsoft Developer Network — MSDN и Mozilla.

Так что наша работа будет заключаться в реализации этих дополнительных возможностей.

Примечание: Поскольку война браузеров пока не кончилась, то мы имеем необходимость для разных браузеров писать разный код. Различать браузеры будем путем проверки, какой код они поддерживают, а какой — нет. Это позволит нам не заморачиваться проверкой userAgentов и версий.

Итак, сформулируем, что нам нужно:

Оформление выделения нужным блочным тегом (к счастью, имеется команда formatBlock) с нужным атрибутом class (а тут уже ничего готового нет) или без оного.
Оформление выделения нужным строчным (inline) тегом с классом или без.
Присвоение атрибутов (в основном классов) нетекстовым объектам — картинкам (им еще полезно присваивать src и alt), таблицам, линиям <hr>.
Очистка форматирования, не подходящего под заданную таблицу стилей (полезно при копировании текста с документов Microsoft Office, других web-страниц и т. д.).
Ну и плюс ко всему редактор должен оправдывать звание "WYSIWYG", учитывая при отображении текста CSS-файлы с сайта.

Панель редактирования


Кроссбраузерная панель редактирования представляет собой document, которому свойство designMode установлено в "On". Поскольку обычно нам не нужно, чтобы редактированию подвергалось все содержимое окна браузера, удобно заключать этот document во фрейм (обычный — frame или плавающий — iframe).

Хорошей идеей будет добавить к этому фрейму еще и textarea для возможности переключать редактор из визуального режима в режим работы с HTML-кодом.

Браузеры автоматически заменяют адреса вставляемых в редактор относительных ссылок, преобразуя их в абсолютный вид. То есть, условно говоря, если наш редактор имеет адрес http://www.site.ru/admin/, мы в него вставляем картинку с адресом image.gif, то она автоматически преобразуется в http://www.site.ru/admin/image.gif и картинку мы, скорее всего, не увидим. Это является проблемой, так как для "правильного" редактора очень желательно иметь возможность вставлять относительные ссылки.

Решать эту проблему будем так:

Во-первых, нужно, чтобы у документа, служащего панелью редактирования, адресом был бы адрес той страницы на сайте, которую мы редактируем с точностью до location.search (части адреса после "?"). Тогда относительные ссылки с текста в редакторе и на сайте будут одинаковы.

Во-вторых, следует при переключении редактора в режим HTML-source и при сохранении преобразовывать адреса ссылок в относительный вид (как минимум, удалять из них часть адреса, общую для редактируемой страницы и ссылки).

В связи с этим, во фрейм будем подгружать отдельный документ с нужным адресом (к примеру, пусть движок сайта выдает пустой документ при запросе страницы с ключом ?wysiwyg=yes). Вернее, не совсем пустой, для MSIE нужно, чтобы в документе был тег <body>, иначе нечему будет присваивать innerHTML. Выдачу движком не пустого HTML, а редактируемого текста считаем нецелесообразным, т. к. это излишне усложняет движок без какой бы то ни было пользы. Текст мы будем получать из textarea, все равно необходимой для интерфейса редактора.

Заодно в этот подгружаемый документ можно вписать подгрузку стилей:

<link rel=stylesheet href=css-файл type=text/css>
<
body></body>,

Также таблицу стилей можно привязать к документу, загруженному в iframe, путем создания методами DOM в его headе элемента типа <link>, указывающего на файл с таблицей стилей.

var style = document.createElement(link)
style.rel = stylesheet
style.type = text/css
style.href = myStyleSheet.css
document.getElementsByTagName(head)[0].appendChild(style)

Здесь document — это документ фрейма-редактора.

С присваиванием контента могут быть некоторые проблемы, связанные с тем, что присваивание надо делать после всевозможных onloadов и через некоторый таймаут после установки designMode (в MSIE). Можно предложить такое решение:
Через try-catch() пытаемся присвоить innerHTML, если не получается, делаем небольшой setTimeout и пробуем снова. Практика показывает, что даже при таймауте в 0 миллисекунд зацикливания не происходит. Можно и изначально делать присваивание по таймауту.

Примечание 1: Сначала мы устанавливаем designMode, потом присваиваем контент.

Примечание 2: В Gecko нельзя устанавливать designMode у скрытого элемента (display:none). Это надо будет учесть, так как делается редактор с переключающимися панелями WYSIWYG / HTML-исходник.

Начнем писать код.

HTML
<textarea style=width:100%;height:350px id=wysiwyg_textarea></textarea>
<
iframe id=wysiwyg_iframe style=display:none;width:100%;height:350px src=canvas.html></iframe>

Здесь мы имеем textarea для работы с HTML-source и iframe для WYSIWYG. Редактор находится в режиме HTML-source (iframe спрятан). Чтобы изменить умолчание, достаточно перенести display:none; в стили textarea.

Можно добавить кнопку для переключения режимов:

<button onclick=wysiwyg_switch_mode(wysiwyg_textarea, wysiwyg_iframe)>Переключить режим отображения</button>

Сейчас мы не задумываемся над особой функциональностью. Можно сделать checkbox, можно сделать переключающиеся вкладки "Normal – HTML" и т. д.

Javascript
// Инициализация редактора
onload = function(){
   
wysiwyg_init(wysiwyg_textarea, wysiwyg_iframe)
}

// Функции инициализации на вход мы даем id составляющих редактор textarea и iframe
function wysiwyg_init(textarea_id, iframe_id){
   var
textarea = document.getElementById(textarea_id)
   var
iframe = document.getElementById(iframe_id)
   
// Проверим на существование iframe и textarea
   // Через offsetWidth проверим видимость iframe – то есть редактор находится в визуальном режиме
   
if(iframe && textarea && iframe.offsetWidth){
      
iframe.contentWindow.document.designMode = On
      
// Для Gecko устанавливаем такой режим, чтобы форматирование ставилось тегами, а не стилями
      // Чтобы MSIE не выдавал ошибку, прячем это в конструкцию try-catch
      
try{
         
iframe.contentWindow.document.execCommand(useCSS, false, true)
      }
catch(e){}

      
// Копируем текст из textarea в iframe
      
wysiwyg_textarea2iframe(textarea_id, iframe_id)
   }
}

// Копирование текста из textarea в iframe
function wysiwyg_textarea2iframe(textarea_id, iframe_id){
   
try{
      
document.getElementById(iframe_id).contentWindow.document.body.innerHTML = document.getElementById(textarea_id).value
   
}catch(e){
      
setTimeout(wysiwyg_textarea2iframe( + textarea_id + , + iframe_id + ), 0)
   }
}

// Переключение редактора из визуального режима в HTML-режим и обратно
function wysiwyg_switch_mode(textarea_id, iframe_id){
   var
textarea = document.getElementById(textarea_id)
   var
iframe = document.getElementById(iframe_id)
   if(
iframe && textarea){
      
// редактор в режиме редактирования HTML-source
      
if(textarea.offsetWidth){
         
// Сначала показываем iframe, потом прячем textarea.
         // Такой порядок для того, чтобы прокрутка не перескакивала
         // из-за укоротившейся на миг страницы.
         
iframe.style.display =
         
textarea.style.display = none
         
wysiwyg_init(textarea_id, iframe_id)
         
iframe.focus()
      }else{
// Редактор в визуальном режиме
         
textarea.style.display =
         
iframe.style.display = none
         
textarea.value = iframe.contentWindow.document.body.innerHTML
         textarea
.focus()
      }
   }
}

Выделение / Selection
"Выделение" (selection) является ключевым понятием в работе редактора. Это область, на которую будет распространяться команда форматирования. Она может быть текстовой и "объектной". Попробуйте в каком-нибудь редакторе (например, Word) сделать документ с картинкой, потом ткнуть мышкой в картинку и нажать Ctrl+A (выделить все) — вы увидите, что выделение картинки будет выглядеть по-разному — в первом случае она выделена как картинка (объект), во втором — как часть текста.

Если мы захотим у изображения <img> указать класс, мы должны будем выделить его объектно. Если мы хотим поставить с него ссылку — текстово.

Наш редактор должен уметь получать список выделенных узлов документа, при необходимости создавая новые (если выделена часть узла, к которому надо применить inline-форматирование).

Базовой функцией работы с выделением является получение начального и конечного узлов выделения. Из этой пары мы сможем получить весь набор входящих в выделение узлов нужных нам типов.

Получаем начальный и конечный узлы выделения (а так же их ближайшего общего родителя)
Примечание: Здесь есть нетривиальность, связанная со "странной" реализацией выделения в MSIE.

// Взятие крайних узлов выделения (корня — root и самых крайних слева и справа — start и end)
// на вход даем окно (т.е. iframe.contentWindow)
function get_selection_bounds(editor_window){
   var
range, root, start, end

   
if(editor_window.getSelection){ // Gecko, Opera
      
var selection = editor_window.getSelection()
      
// Выделение, вообще говоря, может состоять из нескольких областей.
      // Но при написании редактора нас это не должно заботить, берем 0-ую:
      
range = selection.getRangeAt(0)

      
start = range.startContainer
      end
= range.endContainer
      root
= range.commonAncestorContainer
      
if(start == end) root = start

      
if(start.nodeName.toLowerCase() == body) return null
      
// если узлы текстовые, берем их родителей
      
if(start.nodeName == #text) start = start.parentNode
      
if(end.nodeName == #text) end = end.parentNode

      
return {
         
root: root,
         
start: start,
         
end: end
      
}

   }else if(
editor_window.document.selection){ // MSIE
      
range = editor_window.document.selection.createRange()
      if(!
range.duplicate) return null

      
var r1 = range.duplicate()
      var
r2 = range.duplicate()
      
r1.collapse(true)
      
r2.moveToElementText(r1.parentElement())
      
r2.setEndPoint(EndToStart, r1)
      
start = r1.parentElement()

      
r1 = range.duplicate()
      
r2 = range.duplicate()
      
r2.collapse(false)
      
r1.moveToElementText(r2.parentElement())
      
r1.setEndPoint(StartToEnd, r2)
      
end = r2.parentElement()

      
root = range.parentElement()
      if(
start == end) root = start

      
return {
         
root: root,
         
start: start,
         
end: end
      
}
   }
   return
null // браузер, не поддерживающий работу с выделением
}



Похожие статьи:
- XMLHttpRequest (AJAX) - отправка и обработка ответов http-запросов с помощью JavaScript.
- Стиль кодирования на PHP
- Введение в MySQL (используя Perl DBI)
- Целевая аудитория на вашем сайте
- Создание приложения, работающего с XML-данными - 2
- Модуль Apache mod_rewrite
- Пишем правильный online WYSIWYG-редактор
- Скрытие части контента вашей страницы
- Текстовое наполнение сайта
- Как написать свой счетчик посещаемости на Perl
- Oracle и PHP - это очень просто
- AJAX. Тонкости Web Setup Project
- Ajax on Rails


Оглавление | Обсудить на форуме | Главная страница сайта | Карта сайта |

Контакты
Редакция:
[0.002]