Быстроредактируемые поля

24 Апрель 2008 года, 17:34
К данной статье привязаны следующие примеры:

Посадив Кэт в машину, Штирлиц с облегчением сказал:
— Ну вот и всё, теперь можно трогать!
— Ого! — потрогав, сказала Кэт.

Иногда хочется сделать что-то очень быстро и прямо в том месте, где сейчас находишься. На английском языке я бы назвал это просто just-in-place, а на JavaScript описал бы не очень сложным, но очень полезным, по моему мнению, кодом.

Для начала нужно определится с тем, что мы хотим получить в конечном счёте. Рисуем себе в мыслях этакий незаметный заголовок, при двойном клике на котором происходит превращение во вполне редактируемый себе элемент, содержимое которого можно изменить. Здесь можно фантазировать и фантазировать (главное, чтобы фантазии укладывались в рамки стандартов CSS), но я в итоге пришёл к минималистичному исполнению: по умолчанию элемент ничем не выделяется, но если кликнуть по нему дважды, он становится текстовым элементов ввода, стиль которого соответствует стилю текста, который должен быть отредактирован. Стоит заметить, что происходит автоматический выбор редактируемого элемента. Выбор этот зависит от вполне конкретного значения, а именно — от длины содержимого элемента. Весь элемент у нас теперь как на ладони, приступим к конкретике.

Наводим красоту

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

/* Небольшие элементы редактирования */ .editable { color: #444; font-size: 2.5em; } input.editable { font-family: Tahoma; font-size: 1.0em; border: none; background: #FFFFA7; } /* Элементы с размерами поболее предыдущих */ .longtext { display: block; width: 400px; } textarea.longtext { background: #FFFFD2; }

Стиль, конечно же, всегда можно переиначить под собственный лад (а иначе какой смысл в CSS и трёхкитовом разделении, согласитесь?)

Действие порождает действие

Я сразу оговорюсь об использовании специальных функций, который были описаны уже довольно давно. Эти функции называются bind и listen.

Помимо этого, всё чаще ловлю себя на использовании гибрида данных двух функций:

function listenex(object, hevent, lobject, lfunc) { listen(object, hevent, bind(lobject, lfunc)); }

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

//Наш класс для редактируемых элементов function EditableField(editableid) { //Устанавливаем переменные this.editable = $(editableid); //Флаг редактирования this.isediting = false; //Переменные изменения состояния this.callback_begin_edit = false; this.callback_finish_edit = false; }

Класс имеет один входящий параметр — идентификатор поля, которое должно стать редактируемым just-in-place. Также вы заметили две странные переменные: callback_begin_edit и callback_finish_edit, которые намерены вызываться в тот момент, когда начинается (или оканчивается же) редактирование нашего поля.

Ну что ж, приступим к кульминации? Присоединим событие двойного клика по полю к нашей функции-обработчику.

//Подключаем обработчики событий listenex(this.editable, "dblclick", this, "handler_clicked"); //Создаём сами обработчики this.handler_clicked = function() { }

Вот тепеть приступаем к реализации «начинки» нашей функции управления кликом по элементу.

//Если поле не редактируется if (this.isediting == false) { //Если задан обработчик, вызываем его if (this.callback_begin_edit != false) this.callback_begin_edit(this.editable); //Создаём поле редактирования if (this.editable.innerHTML.length < 100) { //Если поле небольшое var intbox = document.createElement("input"); } else { //Если поле большое var intbox = document.createElement("textarea"); intbox.style.height = this.editable.clientHeight + "px"; } intbox.id = this.editable.id + "_edit"; intbox.className = this.editable.className; intbox.value = this.editable.innerHTML; //Очищаем весь HTML this.editable.innerHTML = ""; //И присоединяем наше поле-редактор this.editable.appendChild(intbox); //Устанавливаем значение индикатора редактирования this.isediting = true; } else { //В противном случае, применяем изменения this.editable.innerHTML = $(this.editable.id + "_edit").value; //Меняем значение индикатора this.isediting = false; //Если задан обработчик, вызываем его if (this.callback_finish_edit != false) this.callback_finish_edit(this.editable); }

На первый взгляд... всё! Но на самом деле, есть одна проблема.

Балет

В браузере Opera обработчик, назначенный на событие двойного клика мышкой вызван быть не может (уж не знаю, чем это может быть обусловлено, может быть баг, может быть и нет). Поэтому наш код стоит модифицировать для его рабоспособности.

Добавим обходящий обработчик событий и проверку типа браузера.

//Подключаем обработчики событий if (!window.opera) listenex(this.editable, "dblclick", this, "handler_clicked"); else listenex(this.editable, "click", this, "handler_clicked_opera");
Создаём сами обработчики.
//Обработчик для Opera this.handler_clicked_opera = function() { //Проверяем номер клика (первый или же второй) if (this.clicked_last == false) { //Первый клик совершён, ждём второго... this.clicked_last = true; //...но не позже, чем через секунду tmp_this = this; setTimeout("tmp_this.clicked_last = false", 1000); } else { //В противном случае вызываем нужный обработчик this.clicked_last = false; this.handler_clicked(); } }

Для полной работоспособности данного кода нужно добавить нам ещё одну переменную.

//Флаг номера клика this.clicked_last = false;

Всё готово, применять всё это — проще простого.

//Инициализируем первый элемент var efield = new EditableField("mytest1"); //Добавляем второй var efield2 = new EditableField("mytest2"); //Добавляем обработчик окончания редактирования efield2.callback_finish_edit = function(self) { //Выводим новый текст alert(self.value); }

Заключение экспертизы

Вот, собственно, и всё на сегодняшний день. Можно к этому добавить live-пример на сайте, что я и сделаю. Хочу лишь добавить, что вкупе с другими средствами, такие поля можно использовать достаточно активно (для быстрого редактирования чего-либо). Я нахожу их очень удобными и, разумеется, найду то место, где применю их (а точнее, я уже нашёл).

Мнения (15)

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

  • Mischka

    25 Апрель 200808:27 А почему не сделать сам элемент редактируемым? Обязательно вешать новый input/teaxtarea?
    Про неработоспособность двойного клика в опере первый раз слышу.
    *Ушел тестить.
  • Дин

    25 Апрель 200812:16 Сам элемент редактируемым как ты сделаешь?
  • Mischka

    25 Апрель 200812:28 $('place').contentEditable = true
    Тока, т-с-с-с! Никому не говори.
  • Дин

    25 Апрель 200812:32 Можно конечно так сделать, но пользователи предков текущих версий браузеров ничего не получат в итоге, а здесь — чистый DOM, который и лишней нагрузки-то не вызывает на странцу/производительность.
  • Mischka

    25 Апрель 200808:55 можно повесить обработку выхода из режима редактирования еще и на потерю фокуса input'ом.
  • Дин

    25 Апрель 200812:15 Можно, но тогда может быть учащение случайных сбросов редактирования и лишних движений со стороны пользователя.
  • Mischka

    25 Апрель 200812:22 ну как же?
    Я дблкликнул элемент, изменил что надо, и пошел дблкликать следующий. А этот потерял фокус и, соответственно, применил все изменения.
  • Дин

    25 Апрель 200812:26 Можно рефлексивно после выбора элемента для редактирования кликнуть по пустой области экрана, можно просто случайно кликнуть и тогда будет причинено некоторое неудобство.
  • Mischka

    25 Апрель 200814:52 Ну да, тут вопрос на любителя.
    А вообще, отличный материал и пример тоже замечательный. Спасибо!
  • Sannis

    25 Апрель 200822:28 Как всегда подробно ;)

    Пользуясь случаем, хочу сказать, что стили в форме ответа, что в конце списка комментариев, в Опере погано выглядят. Текст не умещается в кнопки, а размер шрифта для имени, почты и сайта я бы сделал чуток меньше.
  • Дин

    25 Апрель 200823:31 Скоро будет кое-что необычное и новое, поэтому пока не буду исправлять старую версию. ;-)
  • Sannis

    26 Апрель 200813:24 Ну, прям как СуперХабр ;D
    Сделай, plz, хоть кнопку предпросмотра шире.
  • Александр

    26 Апрель 200814:36 Спасибо.
    Интересную тему поднял. Применение быстроредактируемых полей очень практично и не загромождает пользовательский интерфейс. Надо будет где-нибудь тоже применить.
  • sunnybear

    14 Октябрь 200802:57

    стоит заметить, что если на странице по счастливой случайности объявлена переменная opera, то window.opera окажется true, и код будет работать несколько иначе. Опытные разработчики рекомендуют проверять по возможностям браузера, а не по внешним показателям

  • Дин

    14 Октябрь 200807:02

    @sunnybear, да, разумеется, так оно и есть.

Я тоже знаю!

Вы можете тоже написать собственный комментарий. Если хотите к кому-то обратиться, используйте символ @, после которого не забудьте написать имя того, к кому обращаетесь. Не забывайте про существование XHTML-элементов, с помощью которых вы можете оформить ваш комментарий как вам угодно. И, да: ведите себя достойно, вы же не роботы, правда? Если вам интересно, можете подписаться на комментарии по RSS.