Использование библиотеки jQuery UI на практике
105Веб-программирование --- jQuery UI --- Использование библиотеки jQuery UI на практике
Библиотека jQuery UI состоит из виджетов и взаимодействий, которые позволяют создавать насыщенные веб-приложения со стилевым оформлением пользовательского интерфейса, подчиняющимся определенной теме, и обеспечивают чрезвычайно гибкие возможности настройки в соответствии с конкретными задачами. Ранее мы рассмотрели все эти виджеты и взаимодействия и в этой статье некоторые из указанных средств будут добавлены в наш базовый пример для демонстрации того, каким образом можно организовать их совместную работу.
В качестве отправной точки для данной статьи мы используем вариант документа, приведенный в примере ниже:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jQuery UI</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js"></script>
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/sunny/jquery-ui.css">
<!-- Локальные файлы -->
<link rel="stylesheet" href="styles.css">
<script src="jquery.tmpl.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
$.getJSON("mydata.json", function(data) {
$('#flowerTmpl').tmpl(data).appendTo("#products");
});
});
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
<div class="dcell">
<img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
<label for="${product}">${name}:</label>
<input name="${product}" value="0" />
</div>
</script>
</head>
<body>
<h1>Цветочный магазин</h1>
<form>
<div id="products"></div>
<div id="buttonDiv"><button type="submit">Заказать</button></div>
</form>
</body>
</html>
В этом документе необходимые элементы генерируются с помощью шаблона данных (для сокращения разметки документа) на основании информации о продуктах, извлекаемой из файла JSON с помощью метода getJSON(). Вся совокупность элементов, соответствующих отдельным видам продукции, собирается в единственном элементе с идентификатором products. Файл mydata.json находиться в одном каталоге с документом и содержит следующие данные:
[{"name":"Астра","product":"astor","stocklevel":"10","price":"2.99"},
{"name":"Нарцисс","product":"daffodil","stocklevel":"12","price":"1.99"},
{"name":"Роза","product":"rose","stocklevel":"2","price":"4.99"},
{"name":"Пион","product":"peony","stocklevel":"0","price":"1.50"},
{"name":"Примула","product":"primula","stocklevel":"1","price":"3.12"},
{"name":"Подснежник","product":"snowdrop","stocklevel":"15","price":"0.99"}]
Также не забудьте добавить файл таблицы стилей styles.css:
h1 {
min-width: 70px; border: thick double black; margin-left: auto;
margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
color: darkgreen; margin-top: 0;
}
.dtable {display: table;}
.drow {display: table-row;}
.dcell {display: table-cell; padding: 10px;}
.dcell > * {vertical-align: middle}
input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
label {width: 5em; padding-left: .5em; display: inline-block;}
#buttonDiv {text-align: center;}
#oblock {display: block; margin-left: auto; margin-right: auto; min-width: 700px; }
Вид исходного документа в окне браузера представлен на рисунке:
Отображение данных
В качестве средства отображения продуктов для пользователя мы воспользуемся виджетом Accordion. Несмотря на то что имеется всего лишь шесть продуктов, мы разобьем их на группы, в каждую из которых войдет по два продукта, а для создания структуры элементов, которая требуется для виджета Accordion, используем jQuery. Соответствующие изменения представлены в примере ниже:
...
$(function() {
$.getJSON("mydata.json", function(data) {
var flowers = $('#flowerTmpl').tmpl(data);
var rowCount = 1;
for (var i = 0; i < flowers.length; i += 2) {
$("<h2><a href=#>" + data[i].name + " и " + data[i + 1].name.toLowerCase()
+ "</a></h2>").appendTo("#products");
$("<div id='row" + (rowCount++) + "'></div>")
.appendTo("#products")
.append(flowers.slice(i, i + 2))
}
$('#products').accordion();
});
});
...
Здесь в функцию, передаваемую методу getJSON(), добавлен код, предназначенный для создания виджета Accordion, включая построение необходимой структуры элементов и вызов метода accordion(). В новой реализации названия цветов извлекаются из соответствующего источника с помощью объекта данных JSON, но для генерации HTML-элементов (которые затем разбиваются на группы и помещаются в оболочки, образуемые элементами div, в соответствии с требованиями виджета Accordion) по-прежнему используется подключаемый модуль шаблонов данных.
Вид документа в окне браузера после добавления вызова метода accordion() представлен на рисунке:
Добавление корзины покупателя
Нашим следующим шагом будет добавление простейшей корзины покупателя, представляющей отобранные покупателем продукты. Соответствующие изменения, которые для этого требуется внести в образец документа, представлены в примере ниже:
Запустить пример
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jQuery UI</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js"></script>
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/sunny/jquery-ui.css">
<!-- Локальные файлы -->
<link rel="stylesheet" href="styles.css">
<script src="jquery.tmpl.min.js" type="text/javascript"></script>
<style type="text/css">
.dcell img {height: 60px}
#basketTable {border: thin solid black; border-collapse: collapse}
th, td {padding: 4px; width: 50px}
td:first-child, th:first-child {width: 150px}
#placeholder {text-align: center}
#productWrapper {float: left; width: 65%}
#basket {width: 30%; text-align: left; float: left; margin-left: 10px}
#buttonDiv {clear: both}
</style>
<script type="text/javascript">
$(function() {
$.getJSON("mydata.json", function(data) {
var flowers = $('#flowerTmpl').tmpl(data);
var rowCount = 1;
for (var i = 0; i < flowers.length; i += 2) {
$("<h2><a href=#>" + data[i].name + " и " + data[i + 1].name.toLowerCase()
+ "</a></h2>").appendTo("#products");
$("<div id='row" + (rowCount++) + "'></div>")
.appendTo("#products")
.append(flowers.slice(i, i + 2))
}
$('#products').accordion();
$('input').change(function(event) {
$('#placeholder').hide();
var fname = $(this).attr("name");
var row = $('tr[id=' + fname + ']');
if (row.length == 0) {
$('#rowTmpl').tmpl({
name: fname,
val: $(this).val(),
product: $(this).siblings("label").text()
}).appendTo("#basketTable").find("a").click(function() {
removeTableRow($(this).closest("tr"));
var iElem = $('#products').find("input[name=" + fname + "]")
$('#products').accordion("option", "active",
Number(iElem.closest("div[id^=row]").attr('id').substring(3)) - 1)
iElem.val(0).select();
})
} else if ($(this).val() != "0") {
row.children().eq(1).text($(this).val())
} else {
removeTableRow(row)
}
})
});
function removeTableRow(row) {
row.remove();
if ($('#basketTable tbody').children(':visible').length == 1) {
$('#placeholder').show();
}
}
});
</script>
<script id="rowTmpl" type="text/x-jquery-tmpl">
<tr id=${name}><td>${product}</td><td>${val}</td>
<td><a href=#>Удалить</a></td></tr>
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
<div class="dcell">
<img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
<label for="${product}">${name}:</label>
<input name="${product}" value="0" />
</div>
</script>
</head>
<body>
<h1>Цветочный магазин</h1>
<form>
<div id="productWrapper">
<div id="products"></div>
</div>
<div id="basket" class="ui-widget">
<table border=1 id="basketTable">
<tr><th>Продукт</th><th>Количество</th><th>Удалить</th></tr>
<tr id="placeholder"><td colspan=3>Ничего не выбрано</td></tr>
</table>
</div>
<div id="buttonDiv"><button type="submit">Заказать</button></div>
</form>
</body>
</html>
Давайте рассмотрим этот код более подробно.
Помещение виджета Accordion в оболочку
Мы хотим, чтобы корзина покупателя отображалась рядом с панелями виджета Accordion. Для этого мы помещаем элемент, для которого вызывается метод accordion(), внутрь другого элемента div:
<div id="productWrapper">
<div id="products"></div>
</div>
Работа виджета Accordion будет нарушена, если окажется, что он не занимает все пространство родительского элемента по ширине, поэтому мы добавляем оболочку и фиксируем ее ширину с помощью CSS-свойства width:
#productWrapper {float: left; width: 65%}
Таким образом, виджет Accordion, как и должно быть, благополучно располагается по всей ширине элемента-оболочки div, который занимает только 65% ширины своего родительского элемента.
Добавление таблицы
Для отображения корзины мы используем элемент table, который включаем в число статических элементов документа:
<div id="basket" class="ui-widget">
<table border=1 id="basketTable">
<tr><th>Продукт</th><th>Количество</th><th>Удалить</th></tr>
<tr id="placeholder"><td colspan=3>Ничего не выбрано</td></tr>
</table>
</div>
Как и в случае виджета Accordion, мы помещаем элемент table в оболочку, ширину которой устанавливаем с помощью CSS-свойства:
#basket {width: 30%; text-align: left; float: left; margin-left: 10px}
В таблице содержится строка, в которой располагаются заголовки столбцов, и ряд-заполнитель, занимающий всю таблицу по ширине.
Обработка изменений входных значений
Чтобы связать таблицу с виджетом Accordion, мы реагируем на события change, порождаемые элементами input, которые создаются в функции getJSON(). Это делается с помощью следующей функции-обработчика:
$('input').change(function(event) {
$('#placeholder').hide();
var fname = $(this).attr("name");
var row = $('tr[id=' + fname + ']');
if (row.length == 0) {
$('#rowTmpl').tmpl({
name: fname,
val: $(this).val(),
product: $(this).siblings("label").text()
}).appendTo("#basketTable").find("a").click(function() {
removeTableRow($(this).closest("tr"));
var iElem = $('#products').find("input[name=" + fname + "]")
$('#products').accordion("activate",
iElem.closest("div[id^=row]").prev())
iElem.val(0).select();
})
} else if ($(this).val() != "0") {
row.children().eq(1).text($(this).val())
} else {
removeTableRow(row)
}
})
Эта функция-обработчик решает множество задач. Прежде всего, когда пользователь изменяет какое-либо значение, осуществляется проверка того, имеется ли уже в таблице строка, соответствующая данному продукту. В случае отсутствия такой строки создается новая строка, для генерации которой используется шаблон:
<script id="rowTmpl" type="text/x-jquery-tmpl">
<tr id=${name}><td>${product}</td><td>${val}</td>
<td><a href=#>Удалить</a></td></tr>
</script>
Необходимые для данного шаблона значения получаются с помощью методов ядра jQuery, которые извлекают информацию из элемента input, породившего событие. Мы хотим отображать также названия продуктов, и с этой целью выполняем поиск в DOM-дереве для нахождения ближайшего элемента label и считывания его содержимого:
$(this).siblings("label").text()
Вновь созданная строка присоединяется к таблице. Ряд-заполнитель был скрыт еще раньше в самом начале выполнения функции-обработчика:
$('#placeholder').hide();
Процесс добавления новых строк таблицы представлен на рисунке. Пользователь вводит значение в текстовом поле, и как только это поле теряет фокус ввода, в корзине покупателя появляется новая позиция:
Удаление строк
Вы могли заметить, что в качестве части шаблона данных в документ добавляется элемент <a>. При создании строки по шаблону мы регистрируем функцию-обработчик для этого элемента:
... .appendTo("#basketTable").find("a").click(function() {
removeTableRow($(this).closest("tr"));
var iElem = $('#products').find("input[name=" + fname + "]")
$('#products').accordion("option", "active",
Number(iElem.closest("div[id^=row]").attr('id').substring(3)) - 1)
iElem.val(0).select();
})
...
Первое, что мы делаем, — вызываем функцию removeTableRow(), передавая ей в качестве аргумента элемент tr, являющийся ближайшим предком элемента <a>. Для удаления указанного элемента из документа функция removeTableRow() использует метод remove(). Она также восстанавливает в таблице рад-заполнитель в случае отсутствия строк, относящихся к продуктам:
function removeTableRow(row) {
row.remove();
if ($('#basketTable tbody').children(':visible').length == 1) {
$('#placeholder').show();
}
}
Удалив строку из таблицы корзины покупателя, мы находим среди продуктов элемент input, связанный с данной строкой. Затем мы используем навигацию по DOM-дереву для поиска элемента, являющегося ближайшим предшествующим сестринским элементом по отношению к элементу div, который содержит данный элемент input, и передаем его индекс свойству active виджета Accordion. Это приводит к раскрытию той части виджета Accordion, которая содержит элемент, только что удаленный пользователем из корзины.
Наконец, мы устанавливаем значение данного элемента input равным 0 и вызываем метод select(), в результате чего этот элемент получает фокус ввода, а содержащееся в нем значение выделяется.
Обновление существующих строк
Если в таблице корзины покупателя уже есть строка, соответствующая нужному продукту, то заказываемое количество данного продукта можно изменить. Вместо того чтобы удалять одну строку и создавать другую, мы находим эту строку в таблице и обновляем содержимое нужной ячейки:
row.children().eq(1).text($(this).val())
Переменная row — это объект jQuery, содержащий элемент tr для продукта в таблице. Доступ к элементу td осуществляется по номеру позиции (с помощью метода eq()), а его содержимое устанавливается с помощью метода text().
Применение темы оформления
Теперь наша корзина функционирует вполне удовлетворительно, но ее внешний вид оставляет желать лучшего. К счастью, jQuery UI предоставляет библиотеку CSS-стилей (CSS-фреймворк), которые можно применить к элементам, чтобы они выглядели так же, как и виджеты после применения к ним выбранной вами темы стилевого оформления. В примере ниже показано, насколько просто получить требуемый результат путем добавления классов в HTML-элементы документа:
...
<div id="basket" class="ui-widget ui-widget-content">
<table border=1 id="basketTable">
<tr class="ui-widget-header"><th>Продукт</th><th>Количество</th><th>Удалить</th></tr>
<tr id="placeholder"><td colspan=3>Ничего не выбрано</td></tr>
</table>
</div>
...
Для элемента table дополнительно к использованию указанных классов задано отсутствие границ (рамки):
#basketTable {border:none; border-collapse:collapse}
Полученный результат проиллюстрирован на рисунке:
Создание кнопки jQuery UI
Наш следующий шаг — перемещение кнопки в другое место и ее преобразование в виджет jQuery UI. Для этого внесем изменения в функцию-обработчик загрузки страницы:
$(function() {
...
$('#buttonDiv, #basket').wrapAll("<div />").parent().css({
float: "left",
marginLeft: "2px"
})
$('button').button()
});
а также добавим пару CSS-стилей:
#basket {text-align: left; /* Удалить остальные свойства */}
#buttonDiv {clear: both; margin: 5px}
Здесь мы поместили элементы buttonDiv и basket в новый элемент div и изменили некоторые CSS-стили для настройки позиций этих элементов. И наконец, мы вызываем метод button() для создания кнопки jQuery UI, как показано на рисунке:
Добавление диалогового окна для завершения заказа
Мы хотим, чтобы, прежде чем завершить оформление заказа, щелкнув на кнопке "Заказать", пользователь предоставил нам некоторую дополнительную информацию о себе. Чтобы внести некоторое разнообразие, воспользуемся виджетом Dialog. Также добавим функцию обработки введенных пользователем данных. В примере ниже показан конечный код примера, а затем мы более подробно рассмотрим нововведения:
Запустить пример
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jQuery UI</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js"></script>
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/sunny/jquery-ui.css">
<!-- Локальные файлы -->
<link rel="stylesheet" href="styles.css">
<script src="jquery.tmpl.min.js" type="text/javascript"></script>
<style type="text/css">
.dcell img {height: 60px}
#basketTable {border: thin solid black; border-collapse: collapse}
th, td {padding: 4px; width: 50px}
td:first-child, th:first-child {width: 150px}
#placeholder {text-align: center}
#productWrapper {float: left; width: 65%}
#buttonDiv {clear: both}
#basketTable {border:none; border-collapse:collapse}
#basket {text-align: left;}
#buttonDiv {clear: both; margin: 5px}
/* 2 новых стиля */
#completeDialog input {width: 150px; margin-left: 5px; text-align: left}
#completeDialog label {width: 60px; text-align: right}
</style>
<script type="text/javascript">
$(function() {
$.getJSON("mydata.json", function(data) {
var flowers = $('#flowerTmpl').tmpl(data);
var rowCount = 1;
for (var i = 0; i < flowers.length; i += 2) {
$("<h2><a href=#>" + data[i].name + " и " + data[i + 1].name.toLowerCase()
+ "</a></h2>").appendTo("#products");
$("<div id='row" + (rowCount++) + "'></div>")
.appendTo("#products")
.append(flowers.slice(i, i + 2))
}
$('#products').accordion();
// Нужно сузить выборку элементов input, чтобы они не пересекались
// с текстовыми полями формы в диалоговом окне
$('#products input').change(function(event) {
$('#placeholder').hide();
var fname = $(this).attr("name");
var row = $('tr[id=' + fname + ']');
if (row.length == 0) {
$('#rowTmpl').tmpl({
name: fname,
val: $(this).val(),
product: $(this).siblings("label").text()
}).appendTo("#basketTable").find("a").click(function() {
removeTableRow($(this).closest("tr"));
var iElem = $('#products').find("input[name=" + fname + "]")
var index = iElem.closest("div[id^=row]").attr('id').substring(2)
$('#products').accordion("option", "active",
Number(iElem.closest("div[id^=row]").attr('id').substring(3)) - 1)
iElem.val(0).select();
})
} else if ($(this).val() != "0") {
row.children().eq(1).text($(this).val())
} else {
removeTableRow(row)
}
})
});
function removeTableRow(row) {
row.remove();
if ($('#basketTable tbody').children(':visible').length == 1) {
$('#placeholder').show();
}
}
$('#buttonDiv, #basket').wrapAll("<div />").parent().css({
float: "left",
marginLeft: "2px"
})
// Обработчик щелчка по кнопке "Заказать"
$('#buttonDiv button').button().click(function(e) {
e.preventDefault();
if ($('#placeholder:visible').length) {
$('<div>Вы ничего не выбрали для заказа</div>').dialog({
modal: true,
buttons: [{text: "OK",
click: function() {$(this).dialog("close")}}]
})
} else {
$('#completeDialog').dialog("open");
}
})
// Создаем и настраиваем виджет Dialog
$('#completeDialog').dialog({
modal: true,
autoOpen: false,
buttons: [{text: "OK", click: sendOrder},
{text: "Отмена", click: function() {
$("#completeDialog").dialog("close");
}}]
});
// Обработка персональных данных в диалоговом окне
function sendOrder() {
var data = new Object();
$('input').each(function(index, elem) {
var jqElem = $(elem);
data[jqElem.attr("name")] = jqElem.val();
})
console.log(JSON.stringify(data));
$('#completeDialog').dialog("close");
$('#products input').val("0");
$('#products').accordion("option", "active", 0)
$('#basketTable tr').has(':not(th)').filter(':visible').remove();
$('#placeholder').show();
}
});
</script>
<script id="rowTmpl" type="text/x-jquery-tmpl">
<tr id=${name}><td>${product}</td><td>${val}</td>
<td><a href=#>Удалить</a></td></tr>
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
<div class="dcell">
<img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
<label for="${product}">${name}:</label>
<input name="${product}" value="0" />
</div>
</script>
</head>
<body>
<h1>Цветочный магазин</h1>
<form>
<div id="productWrapper">
<div id="products"></div>
</div>
<div id="basket" class="ui-widget ui-widget-content">
<table border=1 id="basketTable">
<tr class="ui-widget-header"><th>Продукт</th><th>Количество</th><th>Удалить</th></tr>
<tr id="placeholder"><td colspan=3>Ничего не выбрано</td></tr>
</table>
</div>
<div id="buttonDiv"><button type="submit">Заказать</button></div>
</form>
<div id="completeDialog" title="Завершите покупку">
<div><label for="name">Имя: </label><input name="first" /></div>
<div><label for="email">Email: </label><input name="email" /></div>
<div><label for="city">Город: </label><input name="city" /></div>
</div>
</body>
</html>
Здесь мы добавили элемент div, содержимое которого будет отображаться для пользователя в элементе body, а также некоторые CSS-стили, заменяющие стили из файла styles.css, которые импортируются в документ с помощью элемента link. Для создания диалогового окна используется следующий вызов метода dialog():
$('#completeDialog').dialog({
modal: true,
autoOpen: false,
buttons: [{text: "OK", click: sendOrder},
{text: "Отмена", click: function() {
$("#completeDialog").dialog("close");
}}]
});
Здесь мы создаем модальный вариант диалогового окна, в котором есть две кнопки. После щелчка на кнопке "Отмена" диалоговое окно закрывается. Щелчок на кнопке "OK" приводит к вызову функции sendOrder():
function sendOrder() {
var data = new Object();
$('input').each(function(index, elem) {
var jqElem = $(elem);
data[jqElem.attr("name")] = jqElem.val();
})
console.log(JSON.stringify(data));
$('#completeDialog').dialog("close");
$('#products input').val("0");
$('#products').accordion("option", "active", 0)
$('#basketTable tr').has(':not(th)').filter(':visible').remove();
$('#placeholder').show();
}
В этой функции мы получаем значения, содержащиеся в каждом из текстовых полей, и добавляем их в виде свойств в объект, который затем преобразуем в формат JSON и выводим на консоль.
Далее мы возвращаем документ в исходное состояние путем закрытия диалогового окна, сброса значений в текстовых полях, перехода на первую вкладку виджета Accordion и очистки корзины. Само диалоговое окно выглядит следующим образом:
Щелчок на кнопке "OK" приводит к генерации данных в формате JSON и восстановлению исходного состояния документа. Консольный вывод имеет следующий вид:
Когда пользователь щелкает на кнопке, мы проверяем, является ли элемент placeholder видимым. Это делается с помощью селектора jQuery, предоставляющего объект, который содержит элементы лишь в том случае, если указанный элемент виден на экране.
Здесь видимость элемента placeholder используется для индикации того, выбран ли пользователем хотя бы один продукт. Если в корзине покупателя есть хотя бы один продукт, этот элемент скрыт, и его появление говорит о том, что ни один продукт выбран не был:
$('#buttonDiv button').button().click(function(e) {
e.preventDefault();
if ($('#placeholder:visible').length) {
$('<div>Вы ничего не выбрали для заказа</div>').dialog({
modal: true,
buttons: [{text: "OK",
click: function() {$(this).dialog("close")}}]
})
} else {
$('#completeDialog').dialog("open");
}
})
Описанный прием служит хорошим примером реализации функциональности приложения на нескольких уровнях. Вместе с тем использованный подход к проверке того, выбрал ли пользователь продукты, зависит от способа реализации корзины покупателя, и если этот способ впоследствии будет изменен, то процедуру проверки также потребуется изменить.
Если пользователь выполняет щелчок на кнопке, не выбрав ни одного продукта, динамически создается и отображается диалоговое окно, представленное на рисунке ниже:
В этой статье мы переработали простой документ, включив в него интерактивные возможности, предлагаемые библиотекой jQuery UI. В него были добавлены виджеты Accordion, Dialog и Button, а также использовалась функциональность классов CSS-фреймворка jQuery UI для управления внешним видом других элементов.