Как часто вам приходится писать в текстовых полях на web-страницах текст, требующий табуляцию (например, HTML-код)? Если вы знакомы с этой проблемой, то помните, что рефлекс, выработанный в большинстве редакторов, приводит к потере фокуса у текущего элемента формы. Таково поведение браузера по-умолчанию, которое можно забороть, в случае необходимости, конечно.
Проблема уже решалась, в частности Владимиром Токмаковым. Чем не понравилось предлагаемое решение:
- Требуется библиотечный файл common.js на 13.5Кб.
- Сам скрипт обрабатывает все текстовые поля на странице по факту загрузки, что не всегда удобно.
- Хотелось бы иметь минимальный объем кода для такой простой фичи.
Немного пошаманив с кроссбраузерностью, получаем такой простой код:
if (object.addEventListener) {
object.addEventListener(event, handler, useCapture ? useCapture : false);
} else if (object.attachEvent) {
object.attachEvent('on' + event, handler);
}
}
function tabInsertInit(obj) {
var _obj = obj;
if (window.opera) addHandler(obj, "keypress", function(evt) {tabInsert(evt, _obj);});
else addHandler(obj, "keydown", function(evt) {tabInsert(evt, _obj);});
}
function tabInsert(evt, obj) {
evt = evt || window.event;
var key = evt.keyCode || evt.which;
if (key == 9 && !evt.ctrlKey && !evt.shiftKey) {
if (obj.nodeName) if (obj.nodeName.toLowerCase() == "textarea") obj.focus();
if(document.selection) {
var iesel = document.selection.createRange().duplicate();
iesel.text = "\t";
} else {
var start = obj.selectionStart;
var end = obj.selectionEnd;
var left = obj.value.substring(0, start);
var right = obj.value.substring(end);
var scroll = obj.scrollTop;
obj.value = left + "\t" + right;
obj.selectionStart = obj.selectionEnd = start + 1;
obj.scrollTop = scroll;
obj.focus();
}
if(evt.preventDefault) evt.preventDefault();
evt.returnValue = false;
return false;
}
}
Всё что требуется — это передать функции tabInsertInit() ссылку на объект, в котором планируется вставлять табы, вместо потери фокуса. Смотрим пример.
Совместимость
- WIN: IE7
- WIN: FF2
- WIN: Стабильная работа в Opera 9.5. В более низких версиях работает с косяками, характер которых зависит от положения звезд на небе :-)
- WIN: Safari 3
Обнаружили неработоспособность скрипта в своем браузере? Пишите об этом в комментах, будем бороть.

1. в чем смысл конструкции:
var scroll = obj.scrollTop;
obj.scrollTop = scroll;
?
2. зачем obj.focus(); два раза? Зачем он вообще нужен? Разве есть браузер, который снимает фокус с поля при добавлении в него текста?
3. вот в этой статье: http://fastcoder.org/articles/?aid=187
учитывается ситуация когда selectionStart > selectionEnd :
} else if (this.start >= 0 && this.end >= this.start) {
здесь это точно не нужно учитывать?
4. насколько я в курсе "return false;" в старых браузерах не только отменяет действие по умолчанию, но и останавливает всплытие. Останавливать всплытие совсем не нужно, вдруг там, на родительских элементах, еще какие-то обработчики.
2) Фокус нужен. Давно писал этот скрипт, но фокус там точно не просто так стоит. Можете убрать и поэкспериментировать в каком из браузеров перестанет работать.
3) Не нужно.
4) Это не так. За отмену всплытия отвечает свойство cancelBubble объекта Event.
2) Пока не удается найти. Протестировал в FF(2.0.0.2, 3.0.3, 3.0.4, 3.5.3), Opera(9.23, 9.25, 9.62, 10.alfa1, 10), Crome3.0.195.27, Safari4.0.3, IE(5.5, 6, 7, 8) .
3) Можно поподробней, почему там нужно, а здесь нет?
4)
или метод stopPropagation, от браузера зависит. А "return false;" это (preventDefault || returnValue) + (stopPropagation || cancelBubble) .
Простые примеры:
1) Мы кроссбраузерно увидим alert, т.к. return false; не отменяет всплывания события.
<div onmousedown="return false;">Click me</div>
</div>
2) Мы кроссбраузерно НЕ увидим алерт, т.к. св-во cancelBubble запрещает всплывание:
<div onmousedown="(function(e) {e = e || window.event; e.cancelBubble = true;})(event);">Click me2</div>
</div>
Это на примере события onmousedown и других событий мыши. С обработкой события click правила немного другие, НО cancelBubble запрещает всплывание ЛЮБОГО события (в том числе и клика) КРОССБРАУЗЕРНО, в отличие от return false.
А зачем? Нам здесь не важно положение фокуса в выделении...
this.value = this.value.slice(0, 7) + '\t' + this.value.slice(4);
this.value = '0123456' + '\t' + '456789';
и результат: '0123456\t45678'
кусок '456' два раза повторяется.
Выделение заменяется табом, оно не сохраняется.
Кусок объекта я брал из чужого кода (см. самый первый комментарий: "// >>> За основу взят объект, написанный автором Sardar <Sardar@vingrad.ru>"), похоже это условие осталась из того самого кода. Проверю его полезность для скрипта...
Лучше читайте реализации от производителей:
http://www.w3.org/TR/DOM-Level-2-Events/events.html
http://msdn.microsoft.com/en-us/library/microsoft.speechserver.dom.mshtml.event.aspx