Пишемо правильний online wysiwyg-редактор
Введення і розуміння суті проблеми
Навіщо це потрібно
Примітка 1: Mozilla і Firefox далі будемо об'єднувати ім'ям Gecko (ця назва їх движка).
Примітка 2: Поки Opera 9 не є релізом, тому про неї потім напишемо. Взагалі, реалізація designMode в Opera схожа на реалізацію такого в Gecko.
Але є у нього і серйозний недолік - назвемо його "синдромом Ворда" - переважання візуальності розмітки над логікою, коли, умовно кажучи, заголовок в документі робиться шляхом виставлення великого і жирного шрифту. У нашому проекті ми спробуємо уникнути цього недоліку (або принаймні мінімізувати його).
Яким це повинно бути (Правильний WYSIWYG)
втілення
Так що наша робота буде полягати в реалізації цих додаткових можливостей.
Примітка: Оскільки війна браузерів поки не скінчилася, то ми маємо необхідність для різних браузерів писати різний код. Розрізняти браузери будемо шляхом перевірки, який код вони підтримують, а який - ні. Це дозволить нам не морочитися перевіркою userAgent'ов і версій.
Отже, сформулюємо, що нам потрібно:
- Оформлення виділення потрібним блоковим тегом (на щастя, є команда formatBlock) з необхідним атрибутом class (а тут вже нічого готового немає) або без оного.
- Оформлення виділення потрібним рядковим (inline) тегом з класом або без.
- Присвоєння атрибутів (в основному класів) нетекстової об'єктів - картинкам (їм ще корисно привласнювати src і alt), таблиць, ліній
. - Очищення форматування, що не підходить під задану таблицю стилів (корисно при копіюванні тексту з документів Microsoft Office, інших web-сторінок і т. Д.).
панель редагування
Кросбраузерності панель редагування являє собою document. якому властивість designMode встановлено в "On". Оскільки зазвичай нам не потрібно, щоб редагуванню піддавалося все вміст вікна браузера, зручно укладати цей document у фрейм (звичайний - frame або плаваючий - iframe).
Вирішувати цю проблему будемо так:
Заодно в цей підвантажуємий документ можна вписати подгрузку стилів:
Також таблицю стилів можна прив'язати до документа, завантаженому в iframe, шляхом створення методами DOM в його head'е елемента типу . вказує на файл з таблицею стилів.
З привласненням контенту можуть бути деякі проблеми, пов'язані з тим, що присвоювання треба робити після всіляких onload'ов і через деякий таймаут після установки designMode (в MSIE). Можна запропонувати таке рішення:
Через try-catch () намагаємося привласнити innerHTML. якщо не виходить, робимо невеликий setTimeout і пробуємо знову. Практика показує, що навіть при таймауті в 0 мілісекунд зациклення не відбувається. Можна і спочатку робити присвоювання з таймаут.
Примітка 1: Спочатку ми встановлюємо designMode, потім присвоюємо контент.
Почнемо писати код.
Можна додати кнопку для перемикання режимів:
Зараз ми не замислюємося над особливою функціональністю. Можна зробити checkbox, можна зробити перемикаються вкладки "Normal - HTML" і т. Д.
Виділення / Selection
Якщо ми захочемо у зображення вказати клас, ми повинні будемо виділити його об'єктно. Якщо ми хочемо поставити з нього посилання - текстово.
Базовою функцією роботи з виділенням є отримання початкового і кінцевого вузлів виділення. З цієї пари ми зможемо отримати весь набір входять в виділення вузлів потрібних нам типів.
Отримуємо початковий і кінцевий вузли виділення (а так само їх найближчого загального батька)
Примітка: Тут є нетривіальність, пов'язана зі "дивною" реалізацією виділення в MSIE.
Отримуємо список всіх вузлів з певним ім'ям тега, що лежать між початком і кінцем виділення
Коли ми застосовуємо до блокам або інлайн команду форматування певним тегом з певним класом, у нас може вийти багато вузлів, тому нам цікаві не тільки початковий і кінцевий. Банальний прохід по nextSibling'ам не підходить, тому що нам пожет знадобиться в процесі обходу підніматися і опускатися по дереву вузлів. Тому алгоритм такий - отримавши найближчого загального батька (root), з поддерева його нащадків, обмеженого початковим і кінцевим вузлами, вибираємо всі потрібні нам вузли.
функція, швидше за все, потребує оптимізації, а то вона через глобальну змінну написана.
Найближчий батько з потрібним тегом
На вхід даємо вузол і ім'я тега. Якщо вузол уже є необхідною тегом, якщо у нього немає відповідних батьків або якщо ім'я тега порожньо, повертаємо цей вузол. Інакше повертаємо найближчого батька, у якого потрібне ім'я тега.
Масив всіх вузлів з потрібним тегом, що потрапили в виділення
форматування блоків
В API є команда formatBlock. їй на вхід дається ім'я блочного тега і вона оформляє поточне виділення цим тегом.
наприклад:
document.execCommand ( "formatBlock", false, "
").
Далі все просто - ми застосуємо цю команду і потім виберемо з виділення все теги потрібного імені (tagName), яким і дамо потрібний className:
Форматування слів (inline)
Собі: закроссбраузеріть clean_nodes
BUGS:
1: MSIE: пропадають граничні прогалини (потрапили в виділення і крайні в ньому. Мабуть, через перепривласнення innerHTML)
2: іноді в MSIE при застосуванні інлайн-форматування на кілька абзаців відразу частина тексту залишається зеленою (magic_unusual_color). У Мозіль, до речі, теж іноді, але при інших обставинах. Може, вибирати все fontи з усього документа, а не тільки з виділення? // Круглов
Робота зі списками
Мається на увазі робота з нумерований і маркованими списками. На наше щастя в API вже майже є (і навіть більше ніж потрібно, але не будемо забігати вперед).
Ми хочемо перетворювати абзаци в обидва види списків і назад, а також керувати вкладеністю списків (міняти відступи).
Ось список наявних в designMode API команд:
- InsertOrderedList - вставити
- InsertUnorderedList - вставити
- Indent - збільшити відступ (зробити подсписок)
- Outdent - зменшити відступ (вийти з подсписка)
Зі зміною відступів є невелика проблемка - якщо ми збільшуємо відступ не у списку, вставляється тег
. Він нам тут зовсім не потрібен. Однак ми, озброївшись недавно описаної функцією clean_nodes. його негайно видалимо.Чистка коду (-)
література