Статьи

Spring MVC, Ajax и JSON. Часть 3. Код на стороне клиента

Если вы читали эту небольшую серию блогов на Spring, Ajax и JSON, вы вспомните, что я дошел до создания веб-приложения Spring MVC, которое отображает форму, позволяющую пользователю выбирать группу элементов. и отправить запрос на сервер для их покупки. Затем сервер отвечает некоторым JSON, позволяя пользователю подтвердить свои покупки. Если вы уже знаете все это, теперь вы можете перейти к ЗДЕСЬ . Если вам интересно, о чем я говорю, взгляните на первые два блога из этой серии:

ВОТ

После завершения кода на стороне сервера, далее нужно перейти к коду на стороне клиента, что предполагает написание некоторого JavaScript. Теперь, между вами и мной, я не эксперт в JavaScript, хотя я, как и большинство разработчиков на стороне сервера, похоже, запутался. Хорошая новость для таких людей, как я, в том, что за последние несколько лет Javascript стал стремительно развиваться благодаря множеству инструментов и библиотек, облегчающих задачу разработки, и, среди всего этого, jQuery, по-видимому, является стандартом по умолчанию опора на цепочку и философия «пиши меньше и делай больше».

Заявив, что я использую jQuery, следующим шагом является настройка JSP, чтобы я мог начать писать код на стороне клиента. Если вы посмотрите на HTML-элемент shopping.jsp <head>, то увидите, что он содержит следующие ссылки:

1
2
3
4
5
6
7
<link rel="stylesheet" href="<c:url value='/resources/blueprint/screen.css'/>" type="text/css" media="screen, projection"/>
<link rel="stylesheet" href="<c:url value='/resources/blueprint/print.css'/>" type="text/css" media="print" />
<!--[if lt IE 8]>
<link rel="stylesheet" href="<c:url value='/resources/blueprint/ie.css' />" type="text/css" media="screen, projection" />
<![endif]-->  
 
<link rel="stylesheet" href="<c:url value='/resources/style.css'/>" type="text/css" media="screen, projection"/>

Первые три ссылки включают в себя Blueprint , который, как я уже говорил в моем первом блоге, должен облегчить мою жизнь, когда дело доходит до форматирования экрана. Последняя ссылка на style.css — интересная. Он содержит два значения CSS, которые я беззастенчиво позаимствовал из исходного примера кода Spring MVC AJAX Кейта Дональда. Этими значениями css являются #mask и #popup , которые применяются к следующим скрытым #popup div , которые я добавил в мой JSP:

1
2
3
4
5
6
7
8
     <div id="mask" style="display: none;"></div>
     <div id="popup" style="display: none;">
          <div class="container" id="insertHere">
               <div class="span-1 append-23 last">
                    <p><a href="#" onclick="closePopup();">Close</a></p>
               </div>
          </div>
     </div>

Маска div используется для выделения серого содержимого браузера, в то время как popup div используется для отображения всплывающего окна, в которое я собираюсь записать результаты JSON моего вызова AJAX на сервер. Обратите внимание на идентификатор insertHere , это важно позже…

Последние две строки HTML-тега JSP включают файлы JavaScript для этой страницы:

1
2
     <script type="text/javascript" src="<c:url value='/resources/jQuery-1.9.1.js' /> "></script>
     <script type="text/javascript" src="<c:url value='/resources/shopping.js' /> "></script>

Первым из этих импортов является версия 1.9.1 jQuery. Версия важна здесь. Если вы используете версию 1.7.x или ниже, приведенный ниже javascript не будет работать, поскольку парни jQuery изменили способ вызова AJAX в версии jQuery 1.8; однако код JavaScript может быть легко изменен при необходимости.

Второй импорт JavaScript — это shopping.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
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
$(document).ready(
    function() {
      $('form').submit(
          function() {
 
            $('.tmp').remove();    // Remove any divs added by the last request
 
            if (request) {
              request.abort();  // abort any pending request
            }
 
            var $form = $(this);
            // let's select and cache all the fields
            var $inputs = $form.find("input");
            // serialize the data in the form
            var serializedData = $form.serialize();
 
            // let's disable the inputs for the duration of the ajax request
            $inputs.prop("disabled", true);
 
            // fire off the request to OrderController
            var request = $.ajax({
              url : "http://localhost:8080/store/confirm",
              type : "post",
              data : serializedData
            });
 
            // This is jQuery 1.8+
            // callback handler that will be called on success
            request.done(function(data) {
 
              console.log("Resulting UUID: " + data.uuid);
              $("<div class='span-16 append-8 tmp'><p>You have confirmed the following purchases:</p></div>")
              .appendTo('#insertHere');
 
              var items = data.items;
              // Add the data from the request as <div>s to the pop up <div>
              for ( var i = 0; i < items.length; i++) {
                var item = items[i];
 
                var newDiv = "<div class='span-4  tmp'><p>" + item.name + "</p></div>";
                $(newDiv).appendTo('#insertHere');
 
                newDiv = "<div class='span-6 tmp'><p>" + item.description + "</p></div>";
                $(newDiv).appendTo('#insertHere');
 
                newDiv = "<div class='span-4 append-10 last tmp'><p>£" + item.price + "</p></div>";
                $(newDiv).appendTo('#insertHere');
 
                console.log("Item: " + item.name + "  Description: " + item.description + " Price: "
                    + item.price);
              }
 
            });
 
            // callback handler that will be called on failure
            request.fail(function(jqXHR, textStatus, errorThrown) {
              // log the error to the console
              alert("The following error occured: " + textStatus, errorThrown);
            });
 
            // callback handler that will be called regardless if the request failed or succeeded
            request.always(function() {
              $inputs.prop("disabled", false);  // re-enable the inputs
            });
 
            event.preventDefault();   // prevent default posting of form
 
            showPopup();
          });
    });

Все действие происходит внутри функций, которые были переданы в функцию jQuery ready() и, как обычно в JavaScript, есть функции, переданные функциям, переданным функциям — цепочка, о которой я говорил ранее. Помните, что функция ready() будет вызываться, когда документ «готов» для взаимодействия.

Первая внутренняя функция — это $('form').submit(…) . Если вы не знаете jQuery, то $ является основной точкой входа в библиотеку jQuery и просто является сокращением для написания jQuery . В этом вызове я выбираю все формы в документе (и есть только одна) и передаю аргумент функции в метод submit(…) .

Особенность jQuery в том, что вы используете его для выбора объектов в вашей модели документа, а затем что-то с ними делаете. У jQuery есть своя собственная техника выбора, которая использует строки, переданные методу jQuery (…). Строки имеют следующий базовый формат: элементы HTML, такие как «form», «a», «div» и т. Д., Пишутся на простом английском языке и при индивидуальной передаче в jQuery выберет
все экземпляры этого типа HTML в документе. Слова с «.» добавлены значения CSS, а слова с символом «#» — это атрибуты html id. Так, например, если бы я написал: $('form#bill').submit(...) то я бы выбрал все формы с идентификатором bill , или если бы я написал $('.fred').submit(…) тогда я бы выбрал все объекты документа с атрибутом класса fred . Как только вы разберетесь с этим языком запросов, все остальное просто.

Когда дело доходит до jQuery, я считаю, что поваренная книга O’Reilly jQuery действительно полезна.

Функция, переданная методу $('form').submit(…) — это место, где выполняется вся работа. Перед выполнением Ajax-запроса необходимо выполнить несколько задач по ведению домашнего хозяйства. Они включают в себя удаление любых объектов документа с классом tmp (при первом вызове это ничего не даст, но я здесь со мной); прерывание любых невыполненных запросов к серверу (это ничего не будет делать большую часть времени); отключение любых форм ввода и сериализации данных, которые запрос Ajax отправит на сервер. Ключевым фрагментом кода JavaScript является
JQuery Ajax запрос :

1
2
3
4
5
6
            // fire off the request to OrderController
            var request = $.ajax({
              url : "http://localhost:8080/store/confirm",
              type : "post",
              data : serializedData
            });

Формат этой функции обычно: ajax, url, settings . Я использую URL-адрес http://localhost:8080/store/confirm что соответствует Spring RequestMapping я описал на прошлой неделе. Параметры, которые вы можете использовать, являются необязательными парами ключ-значение и полностью описаны в документации jQuery Ajax . В этом случае я отправляю сериализованные данные формы с помощью почтового запроса.

Сделав запрос, нужно выполнить несколько заключительных домашних дел. Они предназначены для предотвращения отправки HTML-формы на сервер и для «всплывающего» div, в который записываются результаты Ajax-запроса. При этом используются два ранее упомянутых div с идентификаторами popup и mask .

1
2
3
4
5
function showPopup() {
  $('body').css('overflow', 'hidden');
  $('#popup').fadeIn('fast');
  $('#mask').fadeIn('fast');
}

Возвращаясь к запросу Ajax… $.ajax(...) функции $.ajax(...) возвращает объект с именем request . Это тип jqXHR , где jqXHR с толку аббревиатура XHR обозначает XML HTTP Request . Объект jqXHR представляет собой ряд методов обратного вызова, предназначенных для того, чтобы ваш код мог обрабатывать определенные события. В этом случае я реализовал: fail(…) , always(…) и done(…) . В случае сбоя запроса браузер вызовет fail(…) для отображения простого alert(…) . always(…) является метко названным методом, который всегда вызывается независимо от успеха или неудачи запроса. В этом случае он повторно включает все типы ввода формы. Наконец, есть метод done(…) который вызывается при успешном выполнении Ajax-запроса.

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
            request.done(function(data) {
 
              console.log("Resulting UUID: " + data.uuid);
              $("<div class='span-16 append-8 tmp'><p>You have confirmed the following purchases:</p></div>")
              .appendTo('#insertHere');
 
              var items = data.items;
              // Add the data from the request as <div>s to the pop up <div>
              for ( var i = 0; i < items.length; i++) {
                var item = items[i];
 
                var newDiv = "<div class='span-4  tmp'><p>" + item.name + "</p></div>";
                $(newDiv).appendTo('#insertHere');
 
                newDiv = "<div class='span-6 tmp'><p>" + item.description + "</p></div>";
                $(newDiv).appendTo('#insertHere');
 
                newDiv = "<div class='span-4 append-10 last tmp'><p>£" + item.price + "</p></div>";
                $(newDiv).appendTo('#insertHere');
 
                console.log("Item: " + item.name + "  Description: " + item.description + " Price: "
                    + item.price);
              }
 
            });

Метод done(…) является наиболее важным здесь. В качестве аргумента ему передается функция, а аргументом этой функции являются данные JSON, которые нас интересуют. Это не какая-либо старая необработанная строка JSON, jQuery преобразовал JSON в объект, имеющий атрибуты uuid и items ; двойник объекта OrderForm стороне OrderForm из моего последнего блога . Используя этот объект data , мне остается только отобразить результаты на экране. Это означает циклический просмотр данных и создание переменной newDiv для каждого из OrderForm и преобразование их в HTML. Это простое форматирование строки, например:

1
"<div class='span-4  tmp'><p>" + item.name + "</p></div> "

будет выглядеть так:

1
<div class='span-4  tmp'><p>Socks</p></div>

Этот div содержит некоторые полезные атрибуты класса. Эти атрибуты являются атрибутами отображения Blueprint и атрибутом tmp . Атрибут класса tmp связан с ранее упомянутым $('.tmp').remove(); вызов. Это используется для удаления предыдущих выборов пользователя из всплывающего div, когда пользователь делает несколько заявок.

Создав переменную newDiv , последним шагом является добавление ее во всплывающий appendTo(…) div с помощью функции appendTo(…) jQuery с аргументом '#insertHere' :

1
                $(newDiv).appendTo('#insertHere');

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

После нажатия кнопки «Подтвердить покупку» запросите JSON с сервера, отформатируйте его и отобразите следующее всплывающее окно «div»

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

Есть пара заключительных моментов. Во-первых, я не являюсь Javascript или экспертом на стороне клиента, поэтому, если есть какие-либо эксперты, которые обнаружат какие-либо ошибки, я с нетерпением жду вашего ответа… Во-вторых, я забыл упомянуть, что JSON-часть этого проекта — RESTFul Итак, спасибо Джошу Лонгу и его упоминанию на этой неделе весной за напоминание. Я предполагаю, что мне не пришло в голову упомянуть это, потому что, как правило, само собой разумеется, что каждое приложение должно быть настолько RESTFul, насколько это возможно.

Полный исходный код этого блога см. На GitHub — https://github.com/roghughe/captaindebug/tree/master/ajax-json.