Что-то столь же важное, как контактная форма, вы хотите, чтобы оно работало должным образом для всех посетителей — даже с вызовом JavaScript. Как вы справляетесь с этим, если хотите использовать модальную (всплывающую) форму? Ответ — прогрессивное улучшение; начать с базовой, полезной функциональности; тогда увеличьте пользовательский опыт для тех, у кого есть браузеры, чтобы поддержать это.
Шаг 1: Определитесь с целями проекта
Перед началом любого путешествия, это помогает (в большинстве случаев) иметь пункт назначения. Цель этого проекта — взять стандартную ссылку на страницу, содержащую форму контакта, и разрешить этой форме всплывать на текущей странице в модальном диалоговом окне.
Есть несколько причин для такого подхода:
- Если у пользователя отключен JavaScript, они отправляются на страницу формы контакта как обычно.
- Должна быть сохранена только одна версия формы.
- Дополнительный контент (форма) может быть загружен асинхронно.
Шаг 2: список инструментов
Чтобы написать это с нуля в сыром JavaScript, было бы много кода. К счастью для нас, существуют инструменты, которые мы можем использовать, чтобы упростить задачу. Этот учебник опирается на:
Чтобы сделать этот код максимально пригодным для повторного использования, мы напишем плагин. Если вы не знакомы с разработкой плагина, вы можете получить представление от Jeffrey Way’s статья здесь на Nettuts +. Модальные функциональные возможности будут получены из $ .dialog jQuery-UI.
Шаг 3. Разработка интерфейса подключаемого модуля
Мы собираемся следовать обычному шаблону для плагина jQuery: вызывать плагин в селекторе и устанавливать параметры через массив. Какие варианты нужны? Будут варианты как для модального окна, так и для самого плагина. Мы собираемся ожидать, что плагин будет вызываться на привязке, и применять это в коде.
01
02
03
04
05
06
07
08
09
10
|
$(‘a.form_link’).popUpForm({
container : »,
modal : true,
resizeable : false,
width : 440,
title : ‘Website Form’,
beforeOpen : function(container) {},
onSuccess : function(container) {},
onError : function(container) {}
});
|
Изучение вариантов
Контейнер: так пользователь плагина будет указывать идентификатор формы на удаленной странице. Сама ссылка указывает на страницу, но опция контейнера позволит нам получить соответствующую часть. Это будет единственная обязательная опция при вызове плагина.
Modal, Resizeable, Width, Title: все эти опции будут переданы в $ .dialog пользовательского интерфейса jQuery. Приведенные выше значения являются значениями по умолчанию, и плагин будет работать нормально, и ни один из них не будет установлен при вызове $ .popUpForm.
beforeOpen, onSuccess, onError: Все это обратные вызовы, ожидающие функции. Функция будет передана объекту для ссылки, которая была нажата как «this», и контейнеру, на который нацелена эта ссылка. Обратные вызовы предназначены для предоставления пользовательских функций для пользователей плагина. По умолчанию для этих обратных вызовов будет пустая функция.
Минимальный код, необходимый для использования плагина, будет выглядеть так:
1
|
$(‘a.form_link’).popUpForm({ container : ‘#form_id’ });
|
Это кажется простым, не так ли? Когда вы вызываете такой плагин, код этого плагина вызывается с помощью коллекции jQuery всех элементов DOM, соответствующих селектору, которые будут доступны в специальной переменной ‘this’.
Шаг 4: Скелет плагина
Большинство плагинов jQuery следуют очень похожему шаблону. Они перебирают группу селекторов и делают все, что они делают. У меня есть базовый плагин «схема», из которого я обычно работаю, и он будет хорошо вписываться здесь. Это будет началом вашего файла плагина, popUpForm.jquery.js.
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
|
(function($) {
$.fn.popUpForm = function(options) {
// Defaults and options
var defaults = {
container : »,
modal : true,
resizeable : false,
width : 440,
title : ‘Website Form’,
beforeOpen : function(container) {},
onSuccess : function(container) {},
onError : function(container) {}
};
var opts = $.extend({}, defaults, options);
self.each(function() {
// The REAL WORK happens here.
// Within the scope of this function ‘this’ refers to a single
// DOM element within the jQuery collection (not a jQuery obj)
});
}
})(jQuery);
|
Код обернут в самовыполняющуюся функцию и добавляет себя в jQuery, используя пространство имен $ .fn. Идентификатор, следующий за $ .fn, — это имя метода, которое вы будете использовать для его вызова.
Мы также следуем хорошей практике кодирования, явно передав переменную jQuery. Это избавит нас от неприятностей, если плагин используется на странице с другими средами JavaScript, некоторые из которых используют $ в качестве переменной.
Затем создается массив значений по умолчанию, и эти значения по умолчанию будут использоваться, если они не были определены при вызове плагина. Строка, следующая сразу за массивом по умолчанию, объединяет переданные параметры со значениями по умолчанию и сохраняет их все в массиве opts.
Наконец, создается цикл для перебора коллекции jQuery, идентифицируемой селектором при вызове плагина. Хотя в большинстве ситуаций есть вероятность, что это будет один элемент (якорь), он все равно будет обрабатывать несколько ссылок с один вызов — при условии, что они все загружают одну и ту же форму.
Важно понимать, что значение специальной переменной ‘this’ изменяется, когда мы входим в цикл self.each; это специальный метод jQuery, предназначенный для упрощения создания циклов DOM-коллекций. Функция обратного вызова использует контекст текущего элемента DOM, поэтому переменная this указывает на этот элемент в цикле.
В очень простом примере вы можете увидеть, как «this» ссылается на коллекцию jQuery объектов jQuery в области действия подключаемого модуля, но внутри каждого цикла «this» относится к одному элементу DOM, отличному от jQuery.
Шаг 5: Запуск кишок
Код для следующих нескольких разделов содержится в блоке self.each нашего скелета. Что же нам теперь делать? Для каждого переданного элемента jQuery необходимо выполнить несколько шагов:
- Убедитесь, что это ссылка, и она идет куда-то
- Извлечь указанную часть удаленной страницы
- Прикрепите удаленную форму к странице и создайте для нее скрытый диалог
- Украдите ссылку, чтобы она создала наше всплывающее окно
- Обработка формы представления в стиле AJAX
Однако, прежде чем делать что-либо из этого, мы добавим одну строку кода в обратный вызов, в самом верху.
1
|
var $this = $(this);
|
Это больше, чем просто удобство; переменная ‘this’ выйдет из области видимости в любых замыканиях в каждом цикле, и позже нам понадобится доступ к текущему объекту. Поскольку мы почти всегда хотим, чтобы он был объектом jQuery, мы храним его как единое целое.
Шаг 6: убедитесь, что элемент действителен
$ .popUpForm будет работать только с тегами привязки, а тег привязки должен иметь значение href, чтобы мы знали, откуда взять форму. Если ни одно из этих условий не будет выполнено, мы оставим элемент в покое. Вторая строка наших «кишок» будет:
1
|
if (!$this.is(‘a’) || $this.attr(‘href’) == ») { return ;
|
Некоторые люди ненавидят множественные точки возврата в функции, но я всегда обнаруживал, что наличие одной точки в начале может сделать функцию более читабельной, в отличие от использования условия if (условие) для переноса остальной части функции. Производительность мудрая, они идентичны.
Шаг 7: Получить с удаленной страницы
Метод $ .load имеет приятную функциональность, позволяющую указать вызов и идентификатор, чтобы прикрепить только часть извлеченного документа. Скрипт не будет прикреплять возвращенный HTML-код непосредственно к DOM, потому что $ .load только перезаписывает, но не добавляет.
1
2
|
var SRC = $this.attr(‘href’) + ‘ ‘ + opts.container;
var formDOM = $(«<div />»).load(SRC, function() {
|
Переменная opts.container имеет идентификатор элемента формы на удаленной странице. Вторая строка загружает эту удаленную страницу и присоединяет форму и ее содержимое к элементу div, все содержимое которого хранится в переменной formDOM. Обратите внимание, что $ .load включает в себя обратный вызов (функция) — мы будем использовать formDOM внутри этого обратного вызова.
Шаг 8: Прикрепите HTML и создайте диалог
Внутри обратного вызова $ .load код собирается присоединить форму, переопределить событие click привязки и переопределить событие отправки формы.
На этом этапе HTML-код формы хранится в переменной formDOM, и его легко присоединить к существующей странице.
1
|
$(‘#popUpHide’).append(formDOM);
|
Идентификатор #popUpHide относится к скрытому элементу div, который будет подключен к странице подключаемым модулем. Чтобы обеспечить этот div, следующая строка будет добавлена в верхней части плагина . Если он уже существует, мы не воссоздаем его.
1
|
$(«#popUpHide»).length ||
|
Теперь, когда форма надежно спрятана на нашей странице, пришло время использовать метод $ .dialog для ее создания. Большинство параметров настройки взяты из нашего плагина. Опция ‘autoopen’ жестко запрограммирована, так как мы хотим, чтобы диалог открывался при нажатии на ссылку, а не при его создании.
1
2
3
4
5
6
7
8
|
// Create and store the dialog
$(opts.container).dialog({
autoOpen : false,
width : opts.width,
modal : opts.modal,
resizable : opts.resizeable,
title : opts.title
});
|
Шаг 9: переопределить обработку событий по умолчанию
Если мы остановимся здесь, плагин не будет делать много. Ссылка все равно приведет нас к следующей странице. Мы хотим, чтобы поведение открывало диалог по ссылке.
1
2
3
4
5
|
$this.bind(‘click’, function(e) {
e.preventDefault();
opts.beforeOpen.call($this[0], opts.container);
$(opts.container).dialog(‘open’);
});
|
Первая строка этого обработчика кликов очень важна. Он останавливает загрузку новой страницы при нажатии на нее.
Вторая строка — наш обратный вызов beforeOpen. Переменная opts.beforeOpen содержит ссылку на функцию — это очевидно. Метод .call используется для вызова функции таким образом, чтобы мы могли предоставить контекст — переменную this для этой функции. Первый передаваемый аргумент становится ‘this’ для вызываемой функции.
Когда функция имеет доступ к переменной this, существуют некоторые контракты JavaScript с программистом, которые мы должны поддерживать.
- Переменная this должна быть объектом, на который действует функция
- Переменная this это один объект DOM
Чтобы сохранить этот контракт, мы передаем $ this [0] вместо $ this. $ this [0] представляет один объект DOM, отличный от jQuery.
Чтобы лучше понять это, представьте следующую функцию обратного вызова:
1
2
3
4
5
6
7
8
|
opts.beforeOpen = function(container) {
// Gives the value of the link you just clicked
alert(‘The remote page is ‘ + this.href);
// Gives the id container assigned to this link
alert(‘And the container is ‘ + container);
}
|
Щелчок по ссылке — не единственное поведение по умолчанию, которое можно переопределить Мы также хотим, чтобы форма отправлялась через AJAX, поэтому необходимо предотвратить обычное событие onsumbit формы и закодировать новое поведение.
1
2
3
4
|
$(opts.container).bind(‘submit’, function(e) {
e.preventDefault();
ajaxSubmit();
});
|
Опять же, мы используем warnDefault (), чтобы остановить событие, и в этом случае добавляем новую функцию для обработки отправки формы. Код ajaxSubmit () может идти непосредственно в обратном вызове, но он был перемещен в новую функцию для удобства чтения.
Шаг 10: обрабатывать отправку форм, стиль AJAX
Эта функция будет добавлена сразу после окончания цикла self.each (не волнуйтесь, весь код плагина вы увидите за один раз всего за несколько секунд). Он принимает форму, передает ее удаленному сценарию и запускает соответствующие обратные вызовы.
Первый шаг — получить форму в виде объекта jQuery и определить метод формы, GET или POST.
1
2
3
|
function ajaxSubmit() {
var form = $(opts.container);
var method = form.attr(‘method’) ||
|
Если вы помните, мы сохранили идентификатор формы в opts.container. Следующая строка проверяет форму для метода и назначает «GET», если метод отсутствует. Это согласуется с HTML, который использует GET по умолчанию в формах, если не указан метод.
Используйте метод $ .ajax для отправки формы:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
$.ajax({
type : method,
url : form.attr(‘action’),
data : form.serialize(),
success : function() {
$(opts.container).dialog(‘close’);
opts.onSuccess.call($this[0], opts.container);
},
error : function() {
$(opts.container).dialog(‘close’);
opts.onError.call($this[0], opts.container);
}
});
|
Параметр URL определяется из атрибута действия тега формы. Данные создаются с использованием метода serialize для объекта jQuery, содержащего форму.
Варианты успеха и ошибок — это обратные вызовы $ .ajax, которые мы в свою очередь используем для вызова наших обратных вызовов, точно так же, как был вызван обратный вызов beforeOpen.
Мы также закрываем диалог для обработчиков как успеха, так и ошибок.
Шаг 11: весь плагин
В качестве обзора давайте рассмотрим код, который мы написали в целом, включая некоторые полезные комментарии кода:
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
(function($) {
var alog = window.console ?
$.fn.popUpForm = function(options) {
// REQUIRE a container
if(!options.container) { alert(‘Container Option Required’);
// Give us someplace to attach forms
$(«#popUpHide»).length ||
// Defaults and options
var defaults = {
container : »,
modal : true,
resizeable : false,
width : 440,
title : ‘Website Form’,
beforeOpen : function(container) {},
onSuccess : function(container) {},
onError : function(container) {}
};
var opts = $.extend({}, defaults, options);
// The «this» within the each loop refers to the single DOM item
// of the jQuery collection we are currently operating on
this.each(function() {
/* We want to keep the value ‘this’ available to the $.load
* callback */
var $this = $(this);
/* we only want to process an item if it’s a link and
* has an href value
*/
if (!$this.is(‘a’) || $this.attr(‘href’) == ») { return ;
/* For a $.load() function, the param is the url followed by
* the ID selector for the section of the page to grab
*/
var SRC = $this.attr(‘href’) + ‘ ‘ + opts.container;
/* the event binding is done in the call back in case the
* form fails to load, or the user clicks the link before
* the modal is ready
*/
var formDOM = $(«<div />»).load(SRC, function() {
// Append to the page
$(‘#popUpHide’).append(formDOM);
// Create and store the dialog
$(opts.container).dialog({
autoOpen : false,
width : opts.width,
modal : opts.modal,
resizable : opts.resizeable,
title : opts.title
});
/* stops the normal form submission;
* creating the dialog otherwise the form doesn’t exist
* yet to put an event handler to
*/
$(opts.container).bind(«submit», function(e) {
e.preventDefault();
ajaxSubmit($this[0]);
});
// create a binding for the link passed to the plug-in
$this.bind(«click», function(e) {
e.preventDefault();
opts.beforeOpen.call($this[0], opts.container);
$(opts.container).dialog(‘open’);
});
});
});
function ajaxSubmit(anchorObj) {
console.log(anchorObj);
var form = $(opts.container);
var method = form.attr(‘method’) ||
$.ajax({
type : method,
url : form.attr(‘action’),
data : form.serialize(),
success : function() {
$(opts.container).dialog(‘close’);
opts.onSuccess.call(anchorObj, opts.container);
},
error : function() {
opts.onError.call(anchorObj, opts.container);
}
});
}
}
})(jQuery);
|
Этот код должен быть сохранен в файле с именем popUpForm.jquery.js
Шаг 12: Настройка плагина
Первым шагом в использовании плагина будет включение всех необходимых зависимостей на вашу HTML-страницу. Лично я предпочитаю использовать Google CDN. Файлы, находящиеся в отдельном домене, могут помочь увеличить скорость загрузки страницы, а серверы работают быстро. Кроме того, это увеличивает вероятность того, что посетитель уже будет кэшировать эти файлы.
В заголовок документа HTML добавьте следующее:
1
2
3
4
5
|
<link rel=»stylesheet» href=»http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/black-tie/jquery-ui.css» type=»text/css» />
<link rel=»stylesheet» href=»css/main.css» type=»text/css» />
<script src=’http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js’></script>
<script src=’http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js’></script>
|
Файл main.css предназначен для стилей, характерных для нашего сайта, а все остальное — из CDN Google. Обратите внимание, что вы даже можете использовать темы jQuery-UI из CDN таким образом.
Шаг 13: вызов плагина
Помните, что мы хотим вызывать плагин только для ссылок, которые ведут на страницу формы. В онлайн-демонстрации формы содержатся в form.html, и только две ссылки ведут на эту страницу.
01
02
03
04
05
06
07
08
09
10
11
|
<script>
$(document).ready(function() {
$(‘.contact a’).popUpForm({
container : ‘#modalform’,
onSuccess : function() { alert(‘Thanks for your submission!’);
onError : function() { alert(‘Sorry there was an error submitting your form.’);
});
$(‘.survey a’).popUpForm({ ‘container’ : ‘#othercontainer’ });
});
</script>
|
Вызовы упакованы в блок document.ready, поэтому мы можем быть уверены, что якорные элементы существуют, прежде чем пытаться воздействовать на них. Второй вызов $ (‘survey a’) — это пример минимальной суммы, необходимой для использования нашего нового плагина. Первый пример устанавливает обратный вызов как для onSuccess, так и для onError.
Шаг 14: стилизация модальной
Если вы зашли так далеко, и вы создали примеры форм и страницу для их вызова, вы заметите, что форма в модале, вероятно, ужасно. Сам модал неплох, потому что мы используем тему jQuery-UI. Но форма внутри модального стиля в основном не имеет стиля, поэтому мы должны приложить некоторые усилия, чтобы сделать его красивым.
При создании стилей для использования в моде jQuery-UI необходимо учитывать некоторые моменты:
- Сам модал является только дочерним элементом элемента BODY страницы
- Все содержимое модала является дочерним элементом div класса ‘ui-dialog’
Используя эти небольшие кусочки информации, мы можем начать применять стили к форме в модале. Сначала мы даем модалу цвет фона, которым мы довольны, а также модифицируем шрифт для строки заголовка.
1
2
3
4
5
6
7
|
.ui-dialog {
background: rgb(237,237,237);
font: 11px verdana, arial, sans-serif;
}
.ui-dialog .ui-dialog-titlebar {
font: small-caps bold 24px Georgia, Times, serif;
}
|
Далее мы хотим отделить каждый элемент в форме с помощью линий. Поскольку структура формы чередует h3s с элементами div, содержащими элементы формы, мы добавляем следующие правила:
1
2
3
4
5
6
|
.ui-dialog h3,
.ui-dialog div {
border-top:1px solid rgb(247,247,247);
border-bottom:1px solid rgb(212,212,212);
padding:8px 0 12px 10px;
}
|
И нам нужны только линии между разделами, а не сверху или снизу.
1
2
3
4
5
6
|
.ui-dialog .puForm div:last-child {
border-bottom:none;
}
.ui-dialog .puForm h3:first-child {
border-top:none;
}
|
Не будем забывать стилизовать h3s и элементы формы. Переключатели должны отображаться в строке, чтобы они все были в ряд.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
.ui-dialog h3 {
font: 18px Georgia, Times, serif;
margin: 0;
}
.ui-dialog select,
.ui-dialog textarea,
.ui-dialog input {
width:76%;
display: block;
}
.ui-dialog #rating input,
.ui-dialog #rating label {
display: inline;
width:auto;
}
|
Помните, что эти стили специфичны для этого проекта, вам придется стилизовать свои собственные формы в зависимости от используемой структуры. Чтобы нацелить элементы формы конкретно, вы можете настроить таргетинг на потомков .ui-dialog или индивидуально стилизовать каждую форму, включив стили, начиная с идентификатора формы, который вы включили.
Стилизованная форма:
Шаг 15: Заключение
Итак, что мы действительно сделали? Мы взяли обычную ссылку, ведущую к контактной форме (или формам), и заставили эту форму загружаться в модальном диалоге и отправлять через ajax. Для пользователей без javascript ничего не происходит, и ссылки ведут себя нормально, поэтому мы никому не мешали заполнять ваши формы.
Если вы нажмете на ссылку опроса в демоверсии, обязательно отправьте что-нибудь. Результаты будут опубликованы в комментариях для развлечения через неделю или около того!