Посмотрим правде в глаза: людям действительно не нравится заполнять формы, особенно когда нужно вводить значения. Вот почему такие приложения, как Microsoft Outlook, включают в себя текстовые поля автозаполнения — текстовые поля, которые проверяют первые несколько символов, введенных пользователем, и предлагают слово из данный список. Веб-браузеры также работают таким образом, когда они автоматически представляют список URL-адресов, когда вы начинаете вводить веб-адрес в адресную строку.
В этом уроке с небольшой хитростью JavaScript мы создадим один и тот же тип поведения как в Internet Explorer (v5.5 и выше), так и в Mozilla (v1.0 и выше).
Простое обнаружение браузера
Во-первых, нам нужно сделать небольшое обнаружение браузера, поэтому вот несколько простых кодов для этого (хотя, конечно, вы можете использовать свой собственный):
var isOpera = navigator.userAgent.indexOf("Opera") > -1; var isIE = navigator.userAgent.indexOf("MSIE") > 1 && !isOpera; var isMoz = navigator.userAgent.indexOf("Mozilla/5.") == 0 && !isOpera;
Этот код, очевидно, не очень надежен, но достаточно хорош для наших целей; давайте доберемся до сути этого проекта.
Выбор текстового поля
Первым шагом в этом процессе является создание метода, который будет выделять определенное количество текста в текстовом поле. Мы назовем этот метод textboxSelect()
, и он примет три параметра. Первый параметр — это текстовое поле, с которым мы хотим, чтобы метод действовал; второй параметр, который является необязательным, представляет собой позицию, в которой должен начинаться выбор (если этот параметр опущен, то выбирается все текстовое поле); третий параметр, также необязательный, — это позиция, в которой выбор должен заканчиваться. Если второй параметр указан, но третьего параметра нет, текстовое поле выбирается от начальной позиции до конца текста в текстовом поле.
Сначала мы рассмотрим самый простой случай: если указан только один параметр, то мы должны использовать метод select()
текстового поля для выделения всего текста в текстовом поле:
function textboxSelect(oTextbox, iStart, iEnd) { switch(arguments.length) { case 1: oTextbox.select(); break; ... } }
Обратите внимание, что мы используем оператор switch, чтобы определить, сколько аргументов было заполнено. Если есть только 1, это означает, что был предоставлен только oTextbox
. Далее мы перейдем к случаю, когда есть три аргумента (с iStart
и iEnd
). Здесь нам нужно использовать функцию обнаружения браузера, чтобы определить, что делать. Для Internet Explorer мы будем использовать текстовый диапазон.
function textboxSelect (oTextbox, iStart, iEnd) { switch(arguments.length) { case 1: oTextbox.select(); break; case 3: if (isIE) { var oRange = oTextbox.createTextRange(); oRange.moveStart("character", iStart); oRange.moveEnd("character", -oTextbox.value.length + iEnd); oRange.select(); } else if (isMoz) { ... } } }
В жирном коде мы начинаем с создания текстового диапазона для объекта текстового поля. Затем диапазон должен быть установлен в начальные и конечные координаты. Чтобы переместить начало диапазона, мы используем метод moveStart()
. Этот метод принимает два параметра: тип перемещаемого пространства («символ») и количество перемещаемых пространств. Метод moveEnd()
в следующей строке имеет те же параметры. Разница здесь в том, что moveEnd()
требует, чтобы второй параметр был отрицательным числом (под этим moveEnd()
перемещение конца выделения на один пробел назад, на два пробела назад и т. Д.).
Чтобы получить этот параметр, мы берем отрицательное значение длины текста в текстовом поле и добавляем к нему значение iEnd. Таким образом, если iEnd
равен 8, а текстовое поле содержит 10 символов, второй параметр становится равным 2, и конец диапазона перемещается назад на два символа. Наконец, мы вызываем метод select()
чтобы выделить диапазон в текстовом поле.
Выполнить то же самое для Mozilla на самом деле очень легко. В текстовых полях есть метод setSelectionRange()
который принимает два параметра: начало и конец выделения. Мы можем перейти в iStart
и iEnd
напрямую:
function textboxSelect (oTextbox, iStart, iEnd) { switch(arguments.length) { case 1: oTextbox.select(); break; case 3: if (isIE) { var oRange = oTextbox.createTextRange(); oRange.moveStart("character", iStart); oRange.moveEnd("character", -oTextbox.value.length + iEnd); oRange.select(); } else if (isMoz) { oTextbox.setSelectionRange(iStart, iEnd); } } }
Теперь мы сделаем шаг назад и рассмотрим случай, когда заданы два параметра ( iEnd
не указан). По сути, мы хотим сделать то, что сделали бы, если бы было три аргумента, с той лишь разницей, что iEnd
должно быть равно количеству символов в текстовом поле. Мы можем сделать это следующим образом:
function textboxSelect (oTextbox, iStart, iEnd) { switch(arguments.length) { case 1: oTextbox.select(); break; /3b#/case 2: iEnd = oTextbox.value.length; /* falls through */ case 3: if (isIE) { var oRange = oTextbox.createTextRange(); oRange.moveStart("character", iStart); oRange.moveEnd("character", -oTextbox.value.length + iEnd); oRange.select(); } else if (isMoz){ oTextbox.setSelectionRange(iStart, iEnd); } } }
Обратите внимание, что в этом случае мы не используем оператор break. Мы хотим, чтобы выполнение было выполнено в следующем случае, поскольку мы установили для iEnd соответствующее значение. Теперь это будет работать правильно во всех трех случаях.
Самая последняя часть этой функции — установить фокус на текстовое поле, чтобы при вводе пользователь заменял выделенный текст:
function textboxSelect (oTextbox, iStart, iEnd) { switch(arguments.length) { case 1: oTextbox.select(); break; case 2: iEnd = oTextbox.value.length; /* falls through */ case 3: if (isIE) { var oRange = oTextbox.createTextRange(); oRange.moveStart("character", iStart); oRange.moveEnd("character", -oTextbox.value.length + iEnd); oRange.select(); } else if (isMoz){ oTextbox.setSelectionRange(iStart, iEnd); } } oTextbox.focus(); }
Замена выделения текстового поля
Другая задача, которую нам нужно будет выполнить, — заменить выбранный текст другим текстом. Для этого мы создадим метод textboxReplaceSelect()
, который будет принимать два параметра: текстовое поле для действия и текст для вставки. Поскольку мы будем использовать диапазоны, нам нужно будет создать другой код для IE и Mozilla. Во-первых, давайте посмотрим на код IE:
function textboxReplaceSelect (oTextbox, sText) { if (isIE) { var oRange = document.selection.createRange(); oRange.text = sText; oRange.collapse(true); oRange.select(); } else if (isMoz) { ... } oTextbox.focus(); }
В строке 4 мы создаем диапазон из текущего выделения документа (которое мы можем с уверенностью предположить, находится внутри текстового поля, так как это будет вызываться при событии нажатия клавиши текстового поля). В следующей строке мы заменяем текст диапазона заданной строкой. В строке 6 мы вызываем метод collapse()
диапазона, который устанавливает длину диапазона в 0; параметр, логическое значение, сворачивается до конца диапазона, если значение равно true, и сворачивается до начала диапазона, если значение равно false. Наконец, мы выбираем свернутый диапазон, который помещает курсор после текста, который мы только что вставили.
В Mozilla мы можем добиться того же эффекта с помощью простых манипуляций со строками. Мы можем определить начальную и конечную точки выделения, используя атрибуты Mozilla selectionStart и selectionEnd текстового поля:
function textboxReplaceSelect (oTextbox, sText) { if (isIE) { var oRange = document.selection.createRange(); oRange.text = sText; oRange.collapse(true); oRange.select(); } else if (isMoz) { var iStart = oTextbox.selectionStart; oTextbox.value = oTextbox.value.substring(0, iStart) + sText + oTextbox.value.substring(oTextbox.selectionEnd, oTextbox.value.length); oTextbox.setSelectionRange(iStart + sText.length, iStart + sText.length); } oTextbox.focus(); }
В строке 9 мы просто сохраняем начальную точку выделения текстового поля; это понадобится нам позже. Строка 10 выглядит сложной, но в действительности она просто берет строку перед выделением и строку после выделения и добавляет sText между ними. После этого нам просто нужно повторить то, что мы делали в IE: установить курсор, чтобы он появлялся после только что вставленного текста. Итак, в строке 11 мы устанавливаем диапазон выбора как старую начальную точку плюс длина вставленного текста. Это эффективно копирует то, что мы сделали для IE.
согласование
Следующим шагом в этом процессе является написание метода, который будет искать массив строк и возвращать первое значение, начинающееся с данной строки (например, нам нужно передать «а» и заставить его вернуть первое значение в массив, начинающийся с буквы «а»). Этот метод будет называться autocompleteMatch () и будет принимать два параметра: текст для сопоставления и массив значений для сопоставления.
Поиск прост — мы просто перебираем массив и проверяем каждое значение. Если метод возвращает true, тогда мы возвращаем значение. Если совпадений не найдено, возвращается нулевое значение. Вот полный код:
function autocompleteMatch (sText, arrValues) { for (var i=0; i < arrValues.length; i++) { if (arrValues[i].indexOf(sText) == 0) { return arrValues[i]; } } return null; }
Обратите внимание, что для правильной работы строки в arrValues
должны быть в алфавитном порядке.
Кишки
С нашими другими встроенными методами мы можем перейти к внутренностям текстового поля автозаполнения: метод autocomplete()
. Этот метод будет принимать три параметра: текстовое поле для действия, объект события и массив возможных значений. Предполагая, что у нас есть массив с именем arrValues
, вызов будет выглядеть так:
<input type="text" onkeypress="return autocomplete(this, event, arrValues)" />
Сначала мы рассмотрим код клавиши нажатой клавиши:
function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { ... } }
Обратите внимание, что мы все еще форматируем объект события, используя наш EventUtil.formatEvent()
.
Есть несколько клавиш, которым мы не хотим мешать, например, клавиши курсора; мы просто вернем true в этих особых случаях:
function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { case 38: //up arrow case 40: //down arrow case 37: //left arrow case 39: //right arrow case 33: //page up case 34: //page down case 36: //home case 35: //end case 13: //enter case 9: //tab case 27: //esc case 16: //shift case 17: //ctrl case 18: //alt case 20: //caps lock case 8: //backspace case 46: //delete return true; break; ... } }
Далее мы возьмем случай по умолчанию: когда пользователь вводит символ. В этом случае нам нужно выполнить ряд шагов:
- Мы должны заменить выделенный текст символом, набранным пользователем
- Мы должны попытаться найти соответствие тексту, введенному пользователем
- Если найдено, мы должны установить текстовое поле для предлагаемого текста и установить выбор, чтобы охватить только буквы, которые пользователь не набрал
Это может показаться сложным, но вспомогательные функции, которые мы уже написали, сделают это намного проще.
Важным первым шагом является определение символа, String.fromCharCode()
пользователем (используя String.fromCharCode()
в keyCode
объекта keyCode
(в IE) и charCode
(в Mozilla)). Мы используем этот символ для замены выбранного текста. Затем нам нужно получить длину текста в текстовом поле.
function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { case 38: //up arrow case 40: //down arrow case 37: //left arrow case 39: //right arrow case 33: //page up case 34: //page down case 36: //home case 35: //end case 13: //enter case 9: //tab case 27: //esc case 16: //shift case 17: //ctrl case 18: //alt case 20: //caps lock case 8: //backspace case 46: //delete return true; break; default: textboxReplaceSelect(oTextbox, String.fromCharCode(isIE ? oEvent.keyCode : oEvent.charCode); var iLen = oTextbox.value.length; ... } }
Затем нам нужно найти подходящее значение в массиве значений, используя наш метод autocompleteMatch()
.
function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { case 38: //up arrow case 40: //down arrow case 37: //left arrow case 39: //right arrow case 33: //page up case 34: //page down case 36: //home case 35: //end case 13: //enter case 9: //tab case 27: //esc case 16: //shift case 17: //ctrl case 18: //alt case 20: //caps lock case 8: //backspace case 46: //delete return true; break; default: textboxReplaceSelect(oTextbox, String.fromCharCode(isIE ? oEvent.keyCode : oEvent.charCode); var iLen = oTextbox.value.length; var sMatch = autocompleteMatch(oTextbox.value, arrValues); ... } }
После того, как мы запросим соответствующее значение, нам нужно определить, действительно ли найдено совпадение. Чтобы сделать это, мы тестируем sMatch
чтобы увидеть, является ли он нулевым. Если он не нулевой, нам нужно заменить текст в текстовом поле на sMatch. Затем мы будем использовать iLen
(длина текста, фактически набранного пользователем), чтобы выбрать только текст, который пользователь не набрал с помощью нашего textboxSelect()
.
function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { case 38: //up arrow case 40: //down arrow case 37: //left arrow case 39: //right arrow case 33: //page up case 34: //page down case 36: //home case 35: //end case 13: //enter case 9: //tab case 27: //esc case 16: //shift case 17: //ctrl case 18: //alt case 20: //caps lock case 8: //backspace case 46: //delete return true; break; default: textboxReplaceSelect(oTextbox, String.fromCharCode(isIE ? oEvent.keyCode : oEvent.charCode); var iLen = oTextbox.value.length; var sMatch = autocompleteMatch(oTextbox.value, arrValues); if (sMatch != null) { oTextbox.value = sMatch; textboxSelect(oTextbox, iLen, oTextbox.value.length); } ... } }
После всего этого, последнее, что нам нужно сделать, это вернуть значение false в обработчик событий. Если мы этого не сделаем, напечатанный символ появится дважды.
function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { case 38: //up arrow case 40: //down arrow case 37: //left arrow case 39: //right arrow case 33: //page up case 34: //page down case 36: //home case 35: //end case 13: //enter case 9: //tab case 27: //esc case 16: //shift case 17: //ctrl case 18: //alt case 20: //caps lock case 8: //backspace case 46: //delete return true; break; default: textboxReplaceSelect(oTextbox, String.fromCharCode(isIE ? oEvent.keyCode : oEvent.charCode); var iLen = oTextbox.value.length; var sMatch = autocompleteMatch(oTextbox.value, arrValues); if (sMatch != null) { oTextbox.value = sMatch; textboxSelect(oTextbox, iLen, oTextbox.value.length); } return false; } }
пример
Последним шагом, конечно же, является создание примера, который тестирует код. Без лишних слов, вот оно:
<html> <head> <title>Autocomplete Textbox Example</title> <script type="text/javascript"> var isOpera = navigator.userAgent.indexOf("Opera") > -1; var isIE = navigator.userAgent.indexOf("MSIE") > 1 && !isOpera; var isMoz = navigator.userAgent.indexOf("Mozilla/5.") == 0 && !isOpera; function textboxSelect (oTextbox, iStart, iEnd) { switch(arguments.length) { case 1: oTextbox.select(); break; case 2: iEnd = oTextbox.value.length; /* falls through */ case 3: if (isIE) { var oRange = oTextbox.createTextRange(); oRange.moveStart("character", iStart); oRange.moveEnd("character", -oTextbox.value.length + iEnd); oRange.select(); } else if (isMoz){ oTextbox.setSelectionRange(iStart, iEnd); } } oTextbox.focus(); } function textboxReplaceSelect (oTextbox, sText) { if (isIE) { var oRange = document.selection.createRange(); oRange.text = sText; oRange.collapse(true); oRange.select(); } else if (isMoz) { var iStart = oTextbox.selectionStart; oTextbox.value = oTextbox.value.substring(0, iStart) + sText + oTextbox.value.substring(oTextbox.selectionEnd, oTextbox.value.length); oTextbox.setSelectionRange(iStart + sText.length, iStart + sText.length); } oTextbox.focus(); } function autocompleteMatch (sText, arrValues) { for (var i=0; i < arrValues.length; i++) { if (arrValues[i].indexOf(sText) == 0) { return arrValues[i]; } } return null; } function autocomplete(oTextbox, oEvent, arrValues) { switch (oEvent.keyCode) { case 38: //up arrow case 40: //down arrow case 37: //left arrow case 39: //right arrow case 33: //page up case 34: //page down case 36: //home case 35: //end case 13: //enter case 9: //tab case 27: //esc case 16: //shift case 17: //ctrl case 18: //alt case 20: //caps lock case 8: //backspace case 46: //delete return true; break; default: textboxReplaceSelect(oTextbox, String.fromCharCode(isIE ? oEvent.keyCode : oEvent.charCode); var iLen = oTextbox.value.length; var sMatch = autocompleteMatch(oTextbox.value, arrValues); if (sMatch != null) { oTextbox.value = sMatch; textboxSelect(oTextbox, iLen, oTextbox.value.length); } return false; } } </script> <script> var arrValues = ["red", "orange", "yellow", "green", "blue", "indigo", "violet", "brown"]; </script> </head> <body> <h2>Autocomplete Textbox Example</h2> <p>Type in a color in lowercase:<br /> <input type="text" value="" id="txt1" onkeypress="return autocomplete(this, event, arrValues)" /></p> </body> </html>
Заворачивать
Пользователям нравится иметь возможность вводить значения вместо использования мыши, и текстовое поле автозаполнения сделает их очень счастливыми.
Единственным ограничением этого кода является то, что он чувствителен к регистру, хотя функцию autocompleteMatch()
можно легко изменить, чтобы сделать ее нечувствительной к регистру (я оставлю вас, чтобы выяснить, как именно!). Этот код не будет работать в семействе Netscape Navigator 4.x или в Opera. Его функциональность неизвестна (но предполагается, что он не работает) в других браузерах.