Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Как вставить текст в место расположения курсора в поле ввода.


краткое содержание

Как вставить текст в место расположения курсора в поле ввода.

(1) Скрипт для форумов, где по клику на картинке нужно вставить код смайлика, обрамить выделенный текст псевдотегами (BB-коды), поместить быструю цитату. (2) Некоторые выводы о практичности использования Wiki-движка.
Существует дней: 123
Автор: 12345c
Другие выпуски:
Рассылка 'Упражнения по яванскому письму. Javascript.'
 
Статья.
8.08.06-20.08.06

Текст под курсором.

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

Такого рода скрипты писали или искали все, в ком пробуждался интерес к автоматизации вставки тегов или кодов смайликов при написании сообщения в форум. Её непрекращающаяся актуальность основана на том, что действия для разных браузеров требуются разные, и не всем разработчикам форумов хватало духу решить её до конца. При том, что задача эта вполне решаема и довольно давно цитируются варианты кроссбраузерных решений. (В Опере возможность её решения появилась, начиная с 8-й версии.)

Недавно, в связи с текущими задачами и появлением 9-й версии Оперы, я тоже написал кроссбраузерный код для всей группы задач вставки псевдотегов и цитат в поле ввода и хочу поделиться методом решения, поскольку он есть хороший пример не только полезного результата, но и способов его достижения при сильно разнящемся коде для браузеров и с учётом встречающихся побочных эффектов.

Как в случае решения другой интерфейсной задачи в выпуске рассылки #8, вначале требуется осознать правила, которые собираемся реализовать в коде. Они различаются для конкретной постановки. Например, одним разработчикам надо сохранять выделение в обрамляющих тегах ([b]text[/b]), другие предпочтут ставить курсор после тега. В одних случаях нужно иметь функцию внесения имени автора в поле ввода (для чатов, форумов), в других нет. Попробуем сделать набор функций, часть которых или все понадобятся в каком-либо проекте. Обрамление тегами - задача общая, а парные будут теги или нет - частная, поэтому устроим алгоритм так, чтобы вызовы частных функций сводились к вызову общей (insPic()), в которой вся обработка положения курсора будет происходить в соответствии с указанными параметрами. Если один параметр, то это - просто вставка кода смайлика, если 2 - обрамление тегами, вид которых подготовлен в вызывающей функции insTag(), имеющей 1 параметр. Так получим набор интуитивно понятных функций с 1 параметром без необходимости помнить их синтаксис - писать 1, 2 или больше.

Список пожеланий к алгоритму.

  1. Если нет курсора в поле или имеем старый браузер, теги ставим в конец текста; если есть (или был для не-IE (потому что IE не запоминает позиции курсора в поле)), теги ставим в точку курсора, а курсор - или между тегами (функция insTag(тег)), или в конец одиночного тега (insPic(тег)), или на место аргумента тега (insTagArg()). Последняя функция делает записи типа [URL=...].
  2. Если текст выделен в поле ввода, он обрамляется тегами. Одиночный тег (код смайлика) insPic(тег) помещаем в конец выделения. Курсор - после тега, но для функции insTagSel() выделение сохраняется (для тех, кому будет нужно). Если обратились к автору (insTag('B','Петя')), то логично вставить наоборот, его имя перед выделенным текстом.
  3. Вызовы функций вставки тегов - по клику на кнопке, ссылке, объекте, как будет продемонстрировано в примере. Ничто не мешает привязать их к комбинациям клавиш (Ctrl-B, ...) при желании.
  4. Кнопки привязаны к целевому полю ввода в переменной insField. Если хотим работать с другим полем ввода - переопределим эту переменную. (Конечно, можно передать её как параметр, но нечасто встречаются задачи с более чем одним полем ввода на странице.)
  5. функция цитаты insCapt(тег) копирует любое выделение в документе в позицию курсора и обрамляет тегами. Если выделение в поле ввода, оно просто обрамляется тегом цитаты. Функция выглядит как сторонняя, но часто используется вместе с другими кнопками и имеет общую процедуру обрамления тегами ([quote]...[/quote]).

Метод решения тоже выбираем заранее. По общим соображениям, встраиваемый модуль должен иметь минимум внешних (глобальных) имён. В идеале, одно - объект модуля. Но, если на дело посмотреть проще, не будем усложнять задачу, так как предполагается использование в не слишком насыщенных скриптами страницах. перечислим позже все внешние имена, чтобы не забывать о конфликтах имён. Код будет несколько компактнее, чем при использовании объектов, а главное, читаемее. Легче будет выбрасывать неиспользуемые части тем пользователям, которые не программируют на уровне объектов. Поэтому модуль у нас будет описанным выше набором функций.

Лучше всего познакомиться с работой модуля и протестировать в разных браузерах, посмотрев работу на демонстрационном примере. В нём сделаем возможность работы как с полем ввода textarea, так и input.

О методике построения алгоритма порассуждаем ниже, потому что он получился очень насыщенным оособенностями, связанными с версиями браузеров. Отдельного замечания будет заслуживать совместимость по событиям на уровне документа (onload, onmouseup).

Ещё раз, конспективно опишем, какие функции включает в себя представленная ниже программа.

  1. Вставить парный тег в место курсора в поле ввода или обрамить им выделенный текст.
  2. Вставить код смайлика в место положения курсора.
  3. Парный тег вокруг выделенного текста с сохранением выделения.
  4. Вставить тег с параметром внутри себя (URL рисунка, ссылки).
  5. Вставить тег-цитату, обрамляющий выделенный текст документа.
  6. Обратиться к автору кликом по специальной кнопке, поставив его имя в месте курсора.

Код.

<script>/*Externals: d,brkL,brkR,insPic(),insTag(),
insTagArg(), insBack(),insCapt(),insField,insTagSel(),
insBeg, defa,selted */ brkL="[";brkR="]";selted='';
d=document;
function insPic(s1,s2,s3){
//"форматирование параметров" - расстановка для работы
if(!d.all&&selted==''&&s3==3)
  {s1=s2;s2=brkL+'/'+s2+brkR;s3='';}
if(s3==2||s3==3){s1+=s2;s2='';}
s1=brkL+s1+(s2==brkR?'=':brkR); //'[b]' or '[b='
var isPic=s2==null;  //insert picture: cursor to end
var s=insField.value;var insPosL,insPosR;
//находим начало и конец выделения или положение курсора
if(insField.selectionEnd==null){var ch=0; //IE
    if(d.selection&&d.selection.createRange){
      var tR=d.selection.createRange();var ch='character'
      var tR1=d.body.createTextRange();}
  if(!ch||tR.parentElement&&tR.parentElement()!=insField)
    {insPosL=insPosR=s.length;}
  else{ //найти выделенную область
    insPosL=tR.text.length;
    if(insField.type=='textarea'){
      tR1.moveToElementText(insField);
      tR.setEndPoint('StartToStart',tR1);
      insPosR=tR.text.length;
    }else{tR.moveStart('textedit',-1);
    insPosR=tR.text.length;}
    insPosL=insPosR-insPosL;  //-эмул. selectionEnd (IE)
}}else{ //FF,Opera
  insPosL=insField.selectionStart;
  insPosR=insField.selectionEnd;
  if(insBeg&&self.opera&&!insPosL&&!insPosR)
    {insPosL=insPosR=s.length;insBeg=0;}
 //найден выделенный текст
}var insText=s.substring(insPosL,insPosR);
//для функции цитаты
if((isInSel=selted==insText)&&s3==3)
  {isInSel=insText.length;insText='';}
//запоминание последнего шага
if(d.all)insField.defaultValue=s;else defa=s;
if(isPic&&!(s3==2&&insText!='')){s2=s1;s1='';} //picture
//вывод готового текста
insField.value=s.substring(0,insPosL)+s1+insText+s2
  +s.substring(insPosR,s.length);
if(isInSel&&s3==3)insPosR-=isInSel;
var insCursor=insPosR+s1.length
  +(isPic||insPosL!=insPosR?s2.length:0);
//если insTagSel() не будет, это можно убрать
/*for selectional cursor*/var insCursorL=insCursor;
if(s3==1){insCursorL=insPosL+s1.length;
  insCursor=s1.length+insPosR;}//end "for"
//выставление курсора или выделения
if(d.body.createTextRange){setTimeout( //IE
"var t=insField.createTextRange();t.collapse();t.moveEnd('"
  +ch+"',"+insCursor+");t.moveStart('"+ch+"',"+insCursorL
  +");t.select();",1);  //задержка нужна
}else{  if(d.all)insField.focus(); //FF,Opera
  if(insField.selectionEnd!=null){
    insField.selectionStart=insCursorL;
    insField.selectionEnd=insCursor
      +(d.all?1:0) /*обход багов*/;
    setTimeout("insField.focus();",111);
  if(d.all){var tR=d.selection.createRange();
    if(insCursorL==insCursor)tR.collapse();
    else tR.moveEnd('character',-1);tR.select();}
}}}   //Функции-оболочки
function insTag(s,c){insPic(s,(c?c:'')+brkL+'/'+s+brkR
  +(c?', ':''),c?2:null);}   //'b','[/b]' | 'c[/b], '
function insTagSel(s){insPic(s,brkL+'/'+s+brkR,1);}
   //'b','[/b]',1
function insTagArg(s){insPic(s,brkR);}  //'b',']'
function insBack(){with(insField){var s=d.all?value:defa;
  value=d.all?defaultValue:defa;
  if(d.all)defaultValue=s;else defa=s;}}
/*для цитаты*/d.onmouseup=function(){
  if(self.opera)str=d.getSelection();}
  //--for selection capture in old Opera (>7,<8) only
function insCapt(s){insPic(s+brkR
  +(selted=(d.getSelection?(self.str?str
    :(d.all?(d.getSelection()
    ?d.getSelection():d.selection.createRange().text)
    :getSelection()))
    :(d.selection?d.selection.createRange().text:'')))
  +brkL+'/',s,3);}  //'b]selection[/',b,3
/*конец функций для цитаты*/
onload=function(){insField=d.getElementById('t1');insBeg=1}
</script>

Пример использования.

<textarea id=t1 cols=100 rows=35>12345</textarea><br>
<input type=checkbox onclick=
  "insField=this.checked?d.getElementById('t2')
  :d.getElementById('t1');" (в одну строку)
  title="переключение полей ввода">
<input id=t2 value=12345 size=110><br>
<style>span{border:1px solid gray;padding:0 4px}</style>
<span onmousedown=insTag('testTag')>TestTag</span>
<span onmousedown=insTagArg('URL')>URL</span>
<span onmousedown=insPic('smile')>Pic-smile</span>
<span onmousedown=insCapt('quote')>Fast quote</span>
<span onclick=insBack()>Back</span>  
<span onmousedown=insTagSel('B')
  title="с сохранением выделения">B+</span>
<span onmousedown=insTagSel('I')><i
  title="с сохранением выделения">I+</i></span>
<span onmousedown=insTag('B','Автор')>Автор!</span><br>
(Some text for quote)<br><br>

Демонстрация примера - http://javascript.aho.ru/example/JsPrimeryCode-90.htm.

Описание особенностей. Их очень много, так как программа совмещает разные методы работы с курсором в разных браузерах и разные способы чтения выделенного текста (часть программы, работающая в функции insCapt() и document.onmouseup).

С кнопок вызываются разные функции, первая задача которых - дать параметры основной функции insPic() для правильной работы. Расстановка параметров продолжается и в начале основной функции.

Следующая задача - определить положение курсора в поле ввода. Это просто в FF/Opera, но сложновато в IE (через получение разницы в положениях 2 объектов TextRange). Опера тоже начинает поддерживать selection.createRange, но не полностью, поэтому для определения начала и конца выделения этот метод используется только в IE. По обнаруженным границам выделения вставили теги. Далее задача - правильно поставить курсор для дальнейшей работы. Здесь тоже ряд сложностей для достижения эффекта в браузерах разных версий. В частности, браузеры по разным причинам (кроме Gecko) потребовали сделать паузу, чтобы корректно показать мигающий курсор. (Такой метод часто позволяет обойти недоделки движков, если чувствуется, что они не успевают всё корректно обработать.) Показательно, что время задержки достаточно поставить символически малое, лишь бы в процедуре произошла пауза.

В программе есть 2 функции перехвата событий, которые понадобится совместить с другими обработчиками событий документа, если такие есть (onload, document.onmouseup). Но их применения можно избегнуть. Первую функцию, содержащую только определение целевого объекта insField, исключаем, определяя его каждый раз при вызове insPic(). Вторую можно исключить, но за счёт прекращения поддержки версий Opera 7.24-до 8.00. В 7-й версии существовал баг, позволяющий получать выделенный текст только во время выделения, а не чуть позже. (В статьях это называли багом, хотя задумано вроде бы логично, но иначе, чем принято в других браузерах.)

Наиболее полезным для решения было знание правил нашей задачи. Из них скомпоновали распределение параметров в функции, чтобы она была достаточно компактной и очевидной. Не исключено, что пересмотр даст более компактный алгоритм, но не намного - сущности, отражённые в коде борьбы с версиями, имеют место быть.

Интересно, что работа с цитатами дала довольно много прибавки кода, из-за того, что пришлось рассматривать несколько случаев выполнения (выделение внутри поля ввода и вне) и различие версий браузеров. Те примеры в одно действие getSelection(), которые встречаются при демонстрациях цитирования, оказываются недостаточными при написания полноценного скрипта.

Решение показывает, как более-менее ясный алгоритм вуалируется особенностями реализации. В целом же процесс исполнения сохраняет свою структуру. Читая другие алгоритмы работы с объектами браузера, мы тоже часто видим, что существенная часть кода (50-60%) занята анализом версий браузера.

Как эту задачу решали другие. Попыток решения было и есть очень много, и все они, в основном. успешные. Иногда в реализациях форумов встречаются коды, охватывающие не все браузеры или не учитывающие их новые версии. Тогда подстановка приведённого кода, пока он не устареет, поможет достичь кроссбаузерность.

Ознакомиться со ссылками на способы решения (не менее 6) можно из темы форума, в которой описана заодно хронология отладки рассматриваемого нами скрипта.

Следите за демонстрационной страницей или описаниями скриптов в форуме, чтобы получить самую последнюю на момент прочтения статьи версию.

Уровень: для программистов

Новости.
10.07.06

Некоторые выводы о wiki-движках.

Рассмотрение и инсталляция wiki-движков (описаны в 2 предыдущих выпусках рассылки, на рабочие копии имеются ссылки на сайте) привело к выводам, которые кратко описаны ниже.

Рассмотрение движков Wiki и их практичности в последние 2-3 недели привело к выводам, что для рядовых посетителей типичные Wiki-системы не подходят. Дело в непривычности работы с такими движками. Конечно, догадливый читатель может нажать на кнопку "Править" и вписать несколько слов. Но психологически он будет себя чувствовать некомфортно, видя, что другие создают таблицы, разметку, а он не знает, как. Взглянув на страницу синтаксиса, он увидит довольно обширное знание, которое вряд ли захочет для одной заметки изучать. И чем обширнее правила, тем хуже.

С другой стороны, классическая система Wiki будет жить там, где формируется тесный круг авторов, каждый из которых знает, что будет пользоваться ей неоднократно, а результат будет представлять ценность. Так происходит в википедиях и в закрытых корпоративных системах поддержки и совместного редактирования знаний. Часто меняющуюся информацию, телефоны контактов, сотрудников и партнёров весьма удобно коллективно править и развивать в такой системе.

Если представить пути развития движков навстречу пользователям, то видится полезность полного устранения специфической и разнящейся от движка к движку разметке. Сейчас это стало возможно сделать, встроив визуальный редактор в Wiki-систему, пока таких русскоязычных проектов не существует, да и об англоязычных проектах такой информации не известно. Думается, будущее за такими системами, в которых не надо будет ничего учить, всё интуитивно чтобы было описано на странице. Ведь популярны блоги и форумы - потому что заглядывать в памятку синтаксиса не надо, чтобы быть на уровне умения писать, как другие - лишь общее умение писать и наличие мыслей и слов.

Ссылки на описания и движки.

Результаты испытаний wiki-движков. DokuWiki и wackoWiki. Почему выбор остановился на них и какой из них самый удачный? Имели значение простота пользования и наличие подсветки синтаксиса в кодах программ.
Выбор движка для обсуждений по программированию. Рассмотрены несколько русскоязычных движков и выбран один для экспериментов - dokuWiki. (Впоследствии, он оказался медленным и был сменён на Wacko Wiki.)
Тестирование движка Wacko Wiki. Оказалась более практичной по скорости работы, чем ранее рассмотренный движок dokuWiki.
Начальная страница системы dokuWiki. Приветствия, пояснения, как пользоваться, тестовая страница, страница описания синтаксиса. Можно пользоваться, чтобы оценить возможность установки движка в свои проекты.

Уровень: для пользователей

 javascript.aho.ru , © I.Svetlov, 2005-2006 
Текущая очерёдность плана статей (подписчики могут корректировать через голосование).
9. Многуровневое меню с навигацией по наведению мыши.
8. Ключевые слова новых технологий, которые нужно знать разработчику веб-страниц.
3. Как писать тексты с доступом через JS без экранирования специальных символов (&lt; и другие).
4. select и list - в них есть много общего. Как и с меню навигации. Эмулятор селекта.
5. Древовидное меню, подход к данным, отделение данных от представления.
6. Многонедельный календарь со ссылками. (По списку строится календарь.)

Форум сайта рассылки, почта автора рассылки.

 


В избранное