Статьи

Создать систему редактирования на месте

Заставить пользователей кликать по нескольким страницам просто для редактирования поля — это 1999 год. В этом руководстве вы узнаете, как создать систему редактирования на месте, которую можно найти на популярных сайтах, таких как Flickr.


При всей шумихе вокруг Web 2.0, простота использования теперь намного важнее, чем когда-либо. Возможность редактировать какой-либо контент без перехода на другую страницу — это то, чего действительно жаждут многие пользователи. Многие громкие имена уже используют этот шаблон с большим эффектом. Если вы использовали Flickr, вы, вероятно, видели это в действии.

Я верю, что демонстрация стоит тысячи слов. Попробуйте демо и попробуйте сами.

Сегодня мы рассмотрим, как это реализовать, как вы уже догадались, с нашей любимой библиотекой JavaScript, jQuery. Заинтересованы? Давайте начнем прямо сейчас!

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

  • Мы должны позволить пользователю редактировать контент, не покидая страницу. Это дано.
  • Это должно либо функционировать в целом, либо потерпеть неудачу в целом. Когда JS отключен, мы не хотим сталкиваться со странными причудами.
  • Пользователь должен знать, что контент доступен для редактирования. Незаметное изменение синего фона должно привлечь внимание пользователя к этому.
  • Когда речь идет о том, как вызвать редактирование, есть несколько вариантов. Мы можем позволить пользователю редактировать обычным кликом или двойным кликом. Я выбрал двойной щелчок, поскольку случайные двойные щелчки происходят с меньшей частотой, чем случайные. Переключение это просто вопрос изменения параметра в событии связывания.
  • Способ для пользователя, чтобы сохранить или отменить изменения.
  • Сохранение или редактирование событий может быть вызвано двумя способами. События клавиатуры или события мыши. Я выбрал события мыши, так как событиям клавиатуры не хватает специфичности.
  • Что касается событий мыши, вы можете использовать либо традиционные кнопки, либо обычные ссылки. Я выбрал ссылки без особой причины.
  • Пользователь должен иметь возможность возобновить редактирование, даже если он щелкнет за пределами поля ввода или покинет страницу и вернется.
  • Кроме того, пользователь должен иметь возможность редактировать как можно больше полей одновременно.

Теперь, когда мы наметили наши потребности, мы можем теперь перейти к тому, как мы собираемся это сделать.

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

Шаг 1: Нам нужно будет добавить класс редактируемого для каждого элемента, который нуждается в этой функциональности.

Шаг 2: Затем нам нужно будет добавить наведения к каждому редактируемому элементу, чтобы обратить внимание на тот факт, что содержимое этого элемента является редактируемым. Мы добавим и удалим наведенные мышки, используя JavaScript вместо CSS. В основном это делается для устройств или браузеров с отключенным JavaScript. Мы не хотим посылать им неправильные визуальные подсказки.

Шаг 3: При двойном щелчке по редактируемому элементу мы должны поменять содержимое и заменить его текстовым полем со старым текстом в нем.

Шаг 4а. Когда пользователь хочет сохранить изменения, скопируйте значение ввода в родительский элемент и удалите поле ввода.

Шаг 4b: Или когда пользователь хочет отменить изменения, замените старый контент и удалите поле ввода.

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

HTML-разметка демонстрационной страницы выглядит так.

1
<!DOCTYPE html> <html lang=»en-GB»> <head> <title>In-place editing system — by Siddharth for NetTuts</title> <link type=»text/css» href=»css/style.css» rel=»stylesheet» /> <script type=»text/javascript» src=»js/jquery.js»></script> <script type=»text/javascript» src=»js/mojo.js»></script> </head> <body> <div id=»container»> <h1>In-place editing</h1> <div>by Siddharth for the lovely folks at Net Tuts</div> <p>Elements with a class of <em>editable</em> are, well, editable.

Как видите, без учета котельной пластины у нас есть два неупорядоченных списка. Каждый элемент li имеет класс editable, который обозначает, что его содержимое можно редактировать.

Мы также включили библиотеку jQuery и наш собственный файл скрипта.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
body{
    font-family: «Lucida Grande», «Verdana», sans-serif;
    font-size: 12px;
}
 
a{
    color: #000;
}
 
a:hover{
    text-decoration: none;
}
 
p{
    margin: 30px 0 10px 0;
}
 
h1{
    font-size: 30px;
    padding: 0;
    margin: 0;
}
 
h2{
    font-size: 20px;
}
 
#container{
    width: 820px;
    margin-left: auto;
    margin-right: auto;
    padding: 50px 0 0 0;
 
}
 
.editHover{
    background-color: #E8F3FF;
}
 
.editBox{
    width: 326px;
    min-height: 20px;
    padding: 10px 15px;
    background-color: #fff;
    border: 2px solid #E8F3FF;
}
 
ul{
    list-style: none;
}
 
li{
    width: 330px;
    min-height: 20px;
    padding: 10px 15px;
    margin: 5px;
}
 
li.noPad{
    padding: 0;
    width: 360px;
}
 
form{
    width: 100%;
}
 
.btnSave, .btnCancel{
    padding: 6px 30px 6px 75px;
}
 
.block{
    float: left;
    margin: 20px 0;
}

Здесь нет ничего особенного. Просто набор кода для макета и стилей.

Обратите особое внимание на классы editHover и noPad . Мы будем использовать их немного.

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

Наведения курсора

Как отмечалось ранее, нам нужно добавить тонкий синий фон к редактируемым объектам, чтобы показать, что они доступны для редактирования. Мы уже создали класс editHover, чтобы позаботиться об этом.

01
02
03
04
05
06
07
08
09
10
$(«.editable»).hover(
        function()
        {
            $(this).addClass(«editHover»);
        },
        function()
        {
            $(this).removeClass(«editHover»);
        }
    );

Этот крошечный фрагмент заботится об этом за нас. Мы используем метод hover в jQuery, чтобы добавить класс editHover, когда элемент наведен, и удалить его, когда его нет. Мы используем это для ссылки на конкретный элемент, который находится над. Если бы мы использовали .editable в качестве селектора, вместо этого каждый элемент получит добавленный класс. Таким образом, мы используем это для таргетинга только того элемента, который нам нужен

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

1
$(«.editable»).bind(«dblclick», replaceHTML);

Мы присоединяем функцию replaceHTML к событию двойного щелчка относительно редактируемого элемента с этим одним вкладышем. Теперь мы можем перейти к выключению элементов.

1
2
3
4
5
6
7
8
9
function replaceHTML()
    {
        oldText = $(this).html()
                         .replace(/»/g, «»»);
        $(this).html(«»)
               .html(«<form><input type=\»text\» class=\»editBox\»
                value=\»» + oldText + «\» /> </form><a href=\»#\» class=\»btnSave\»>Save changes</a>
                <a href=\»#\» class=\»btnDiscard\»>Discard changes</a>»);
    }

Давайте рассмотрим наш код постепенно.

Я определяю функциональность внутри отдельной именованной функции вместо анонимной по определенной причине: я буду использовать эту функцию более одного раза. Затем мы сохраняем содержимое элемента для будущего использования, используя html- метод jQuery и заменяя все кавычки, так как он портит наш вывод по строке.

Теперь, когда наш контент безопасно хранится для дальнейшего использования, мы можем отключить элементы. Сначала мы очищаем элемент li , отправляя пустую строку в метод html . Далее мы вставляем некоторый стандартный HTML для поля ввода. Мы добавляем некоторые классы для стилизации. Что еще более важно, мы устанавливаем его атрибут value в исходный текст, содержащийся в элементе, хранящемся в oldText . Мы также добавили несколько ссылок, чтобы сохранить и отменить изменения. Мы также добавили к ним классы, чтобы их можно было легко использовать для стилизации.

Как всегда, мы используем это для нацеливания на элемент, вызвавший событие.

Отредактированный текст
01
02
03
04
05
06
07
08
09
10
11
$(«.btnSave»).live(«click»,
    function()
    {
        newText = $(this).siblings(«form»)
                         .children(«.editBox»)
                         .val().replace(/»/g, «»»);
                          
        $(this).parent()
               .html(newText);
    }
);

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

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

Он привязывает обработчики к событиям независимо от того, когда элемент был создан. Подробнее об этом вы можете прочитать в официальных документах .

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

Затем мы получаем родительский элемент links, элемент li и заменяем его HTML-содержимое текстом, который мы скопировали на предыдущем шаге.

Этот блок мог быть легко создан как один слой, но я решил разбить его на 2 строки в целях удобства чтения.

1
2
3
4
5
6
7
$(«.btnDiscard»).live(«click»,
    function()
    {
        $(this).parent()
               .html(oldText);
    }
);

Это так же просто, как кажется. Поскольку пользователь не хочет сохранять какие-либо изменения. Мы просто заменим содержимое HTML родительского элемента на исходный текст, который мы скопировали ранее в переменную oldText .

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

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

Чтобы исправить это, нам нужно отменить привязку обработчика событий для этого конкретного элемента и перепривязать их, как только пользователь нажмет кнопку «Сохранить» или «Отменить». Давайте реализуем это сейчас.

Наши предыдущие блоки кода теперь нужно отредактировать так:

1
2
3
4
5
6
7
function replaceHTML()
    {
        //Code
        $(this).html(«»)
        // Earlier form insertion code
                .unbind(‘dblclick’, replaceHTML);
    }

Мы отцепили обработчик для элемента, который вызвал событие. Остальные элементы с редактируемым классом по-прежнему имеют свои обработчики без изменений и будут реагировать на события.

01
02
03
04
05
06
07
08
09
10
$(«.btnSave»).live(«click»,
    function()
    {
        // Earlier code
                          
        $(this).parent()
               .html(newText)
                           .bind(«dblclick», replaceHTML);
    }
);
1
2
3
4
5
6
7
8
$(«.btnDiscard»).live(«click»,
    function()
    {
        $(this).parent()
               .html(oldText)
                           .bind(«dblclick», replaceHTML);
    }
);

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

Этот последний кусочек кода предназначен только для улучшения внешнего вида нашего эффекта. Если вы заметили, у li есть немного отступов, чтобы текст внутри выглядел лучше. Но когда текст вычеркивается и заменяется текстовым полем, результат выглядит ужасно и нарушает эффект. Мы хотим, чтобы текстовое поле занимало точно такое же пространство, как и оригинальный текст. Имея это в виду, мы добавляем класс noPad к элементу, когда он дважды щелкается и снова удаляется, когда пользователь сохраняет или отменяет редактирование.

1
2
3
4
5
6
7
function replaceHTML()
    {
        //Code
        $(this).addClass(«noPad»)
                            .html(«»)
        // Earlier code
    }

Мы отцепили обработчик для элемента, который вызвал событие. Остальные элементы с редактируемым классом по-прежнему имеют свои обработчики без изменений и будут реагировать на события.

01
02
03
04
05
06
07
08
09
10
$(«.btnSave»).live(«click»,
    function()
    {
        // Earlier code
                          
        $(this).parent()
               .removeClass(«noPad»)
                // Earlier code
    }
);
1
2
3
4
5
6
7
8
$(«.btnDiscard»).live(«click»,
    function()
    {
        $(this).parent()
               .removeClass(«noPad»)
                           // Earlier code
    }
);

Вот как выглядит полный код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
$(document).ready(function()
{
    var oldText, newText;
 
    $(«.editable»).hover(
                    function()
                    {
                        $(this).addClass(«editHover»);
                    },
                    function()
                    {
                        $(this).removeClass(«editHover»);
                    }
                    );
   
    $(«.editable»).bind(«dblclick», replaceHTML);
      
      
    $(«.btnSave»).live(«click»,
                    function()
                    {
                        newText = $(this).siblings(«form»)
                                         .children(«.editBox»)
                                         .val().replace(/»/g, «»»);
                                          
                        $(this).parent()
                               .html(newText)
                               .removeClass(«noPad»)
                               .bind(«dblclick», replaceHTML);
                    }
                    );
     
    $(«.btnDiscard»).live(«click»,
                    function()
                    {
                        $(this).parent()
                               .html(oldText)
                               .removeClass(«noPad»)
                               .bind(«dblclick», replaceHTML);
                    }
                    );
     
    function replaceHTML()
                    {
                        oldText = $(this).html()
                                         .replace(/»/g, «»»);
 
                        $(this).addClass(«noPad»)
                               .html(«»)
                               .html(«<form><input type=\»text\» class=\»editBox\»
                                value=\»» + oldText + «\» /> </form><a href=\»#\» class=\»btnSave\»>Save changes</a>
                               .unbind(‘dblclick’, replaceHTML);
             
                    }
}
);

Неплохо. Пятьдесят нечетных строк, чтобы добавить немного новой функциональности.

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

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

Чтобы добавить запрос AJAX, наш обработчик сохранения должен быть обновлен до следующего:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
$(«.btnSave»).live(«click»,
    function()
    {
        newText = $(this).siblings(«form»)
             .children(«.editBox»)
             .val().replace(/»/g, «»»);
                                   
                 $.ajax({
            type: «POST»,
        url: «handler.php»,
            data: newText,
            success: function(msg){
             // Some code here to reflect a successful edit;
            }
            });
                          
        $(this).parent()
               .html(newText)
               .removeClass(«noPad»)
               .bind(«dblclick», replaceHTML);
    }
);

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

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

Вопросов? Хорошие вещи, чтобы сказать? Критицизмы? Нажмите на раздел комментариев и оставьте мне комментарий. Удачного кодирования!

  • Подпишитесь на нас в Твиттере или подпишитесь на ленту Nettuts + RSS для ежедневных новостей и статей о веб-разработке.