Статьи

Лучшие леса с jQuery — Часть II

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

Первая область, о которой нам нужно поговорить, это сторона сервера. Помните, что прежде чем все, что нам нужно было изменить, было, когда было успешно сохранено напоминание, мы просто хотели вернуть экземпляр напоминания обратно клиенту как JSON. Теперь, когда нам нужно отобразить ошибки, если проверка не удалась, нам нужно немного больше, чем экземпляр домена. Несмотря на то, что экземпляр домена должен содержать наши ошибки, выделение их в JavaScript не является веселой задачей, и мы также хотим более простой способ определить, существуют ли ошибки. Помните, что с помощью JSON мы получаем свойства объекта, но не методы объекта. Поэтому мы не можем вызвать hasErrors () в JavaScript. Мы хотим создать объект-оболочку, который может очень просто хранить необходимую нам информацию, которая затем может быть возвращена клиенту в виде JSON. Я называю этот объект AjaxPostResponse.groovy.

class AjaxPostResponse {
boolean success
String message
def domainInstance
def errors = [:]
}

Сложно, верно? Я думаю, что большинство свойств говорят сами за себя. Но только для ясности:

  • успех — удачно ли сохранить
  • сообщение — общее необязательное сообщение
  • domainInstance — нам понадобятся свойства экземпляра домена, если бы все было хорошо для использования в JavaScript
  • ошибки — карта ошибок, если проверка не пройдена

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

class ReminderService {
boolean transactional = true
def grailsApplication

def prepareResponse(domainInstance) {
def g = grailsApplication.mainContext.getBean('org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib')
def postResponse = new AjaxPostResponse()
if (domainInstance.hasErrors()) {
g.eachError(bean: domainInstance) {
postResponse.errors."${it.field}" = g.message(error: it)
}
postResponse.success = false
postResponse.message = "There was an error"
} else {
postResponse.success = true
postResponse.message = "Success"
}
postResponse.domainInstance = domainInstance
return postResponse
}
}

Этот код выглядит немного грубовато, но на самом деле он довольно прост. Нам нужно определить переменную для хранения нашего grails ApplicationTagLib, потому что мы не находимся внутри контроллера, поэтому это еще не введено для нас. Затем нам нужен экземпляр нашего AjaxPostResponse. Затем мы проверяем domainInstance на наличие ошибок и, если мы найдем некоторые из них, мы соединяем карту ошибок в AjaxPostResponse с ошибками, использующими некоторые ярлыки grails по пути. Добавьте значение успеха и значение сообщения в зависимости от состояния ошибки, нашего domainInstance, и просто верните postResponse. Эта услуга вызывается из нашего ReminderController. Метод сохранения намного проще, чем был раньше:

def save = {
def reminderInstance = new Reminder(params)
reminderInstance.save(flush: true)
render reminderService.prepareResponse(reminderInstance) as JSON
}

Так как мы помещаем всю логику проверки в службу и AjaxPostResponse, контроллеру все равно, провалилась ли проверка или нет. Мы делегируем ответственность обратно клиенту, так как это то, что нужно знать об этом. Есть еще одно дополнение, которое нам нужно на стороне сервера, и это делает наши сообщения об ошибках менее общими, чем значения по умолчанию для grails. Добавьте следующее в файл messages.properties в каталоге i18n:

com.Reminder.duration.nullable=Please enter a Duration
typeMismatch.com.Reminder.duration=Duration must be a number

На клиенте нам нужно внести несколько изменений. Сначала нам нужно место, чтобы показать любые сообщения об ошибках, которые могут вернуться. Добавьте div с классом ‘errors’ в диалоговую форму. Если вы помните это в событии / edit.gsp:

<div id="dialog-form" title="Create new Reminder">
<div class="errors"></div>
<g:form action="save" method="post">
<% -- more code below here --%>
</div>

Затем нам нужно изменить то, что происходит, когда наш ответ возвращается из нашего запроса ajax. Помните, что раньше мы просто возвращали Reminder как JSON, но теперь мы возвращаем AjaxPostResponse как JSON. Вот как выглядит этот фрагмент кода:

if (data.success) {
var item = $("<li>");
var link = $("<a>").attr("href", contextPath + "/reminder/show/" + data.domainInstance.id).html(data.domainInstance.reminderType.name + " : " + data.domainInstance.duration);
item.append(link);
$('#reminder_list').append(item);
cleanup();
$('#dialog-form').dialog('close');
} else {
showErrors("#dialog-form .errors", data.errors);
}

Это не сильно отличается, хотя вместо data.id мы должны вызвать data.domainInstance.id (потому что domainInstance является свойством AjaxPostResponse). Мы также проверяем, верны ли data.errors в первую очередь. Если это так, мы добавляем напоминание в список, как и прежде, мы делаем некоторую очистку (немного объяснено) и закрываем диалог. Если были ошибки, нам нужно их показать. Я создал функцию под названием showErrors, которая принимает цель и карту ошибок:

function showErrors(target, errors) {
var errorList = $("<ul>");
for (field in errors) {
errorList.append("<li>" + errors[field] + "</li>")
}
$(target).html("").append(errorList).show(500);
}

Мы создаем UL, перебираем каждый ключ на карте ошибок и создаем элемент списка, содержащий сообщение об ошибке, которое было отправлено с сервера. Затем мы удаляем все существующие сообщения, добавляем наш список и показываем его. Кстати, об отображении списка ошибок; Это должно быть скрыто, когда диалоговое окно отображается в первый раз. Вы можете сделать это, просто вызвав $ («# dialog-form .errors»). Hide () вручную в верхней части вашего кода JavaScript или установив стиль. Имейте в виду, что Grails также использует класс ‘.error’, что хорошо для нас, потому что он уже стилизован, но это также означает, что вы должны позаботиться и использовать лучший селектор, чтобы добраться до них, как я это сделал. В противном случае вы можете изменить существующую разметку, не ожидая этого.

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

function cleanup() {
$('#dialog-form .errors').html("");
$('#dialog-form .errors').hide();
clearForm('#dialog-form form');
}

function clearForm(target) {
$(':input', target)
.not(':button, :submit, :reset, :hidden')
.val('')
.removeAttr('checked')
.removeAttr('selected');
}

cleanup () очищает ошибки, скрывает div и вызывает clearForm, который просто фильтрует все наши элементы формы и устанавливает их значения в пустые строки и снимает флажки / отменяет выбор любых элементов. Когда все сказано и сделано, вы получите диалоговое окно, которое выглядит примерно так, если у вас есть ошибки проверки:

 

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