Гобелен 5.3.1 вышел в дикую природу … и, чтобы Гобелен оставался актуальным, Гобелен 5.4 должен быть чем-то весьма эволюционным.
Перед рассылкой этого блога возникла путаница в списке рассылки разработчиков Tapestry; Я намекал на то, что это произойдет, и некоторые возражали против того, чтобы такие заявления выходили полностью сформированными, без обсуждения. На самом деле, это просто дистилляция идей, отправная точка, а не законченное, окончательное решение. Если он будет более подробным, чем некоторые обсуждения эволюции Tapestry в прошлом, это просто означает, что обсуждение списка рассылки и возможная реализация будут намного лучше информированы.
В постах и других беседах я упоминал о своем видении гобелена 5.4. Как всегда, цель Tapestry состоит в том, чтобы позволить разработчикам меньше кодировать, доставлять больше , и это было в центре внимания Tapestry на стороне сервера: все движет этим моментом: краткость кода и шаблонов, перезагрузка живого класса и отличная обратная связь критические факторы там. Многое из того, что вошло в Tapestry 5.3, усилило эти моменты … улучшения возможностей метапрограммирования Tapestry, улучшения контейнера IoC и сокращение объема памяти Tapestry несколькими способами. У меня один клиент сообщил об уменьшении использования памяти на 30%, а другой сообщил о повышении скорости выполнения на 30 — 40%.
Интересно, что я думаю, что для того, чтобы Tapestry действительно оставался актуальным, необходимо сместить гораздо больше внимания на сторону клиента. В течение некоторого времени Tapestry шел по тонкой грани в отношении критического вопроса, где выполняется приложение?До Ajax, это был простой вопрос: приложение запускается на сервере, с минимальными трюками и проверками JavaScript на клиенте. По мере того, как использование Ajax стало более зрелым, и ожидания клиентов в отношении поведения приложений в браузере расширились, больше нельзя утверждать, что Tapestry основан на страницах с ограниченными улучшениями Ajax. Поток приложений и бизнес-логика все чаще должны выполняться в браузере, а роль на стороне сервера заключается в организации и поддержке клиентского приложения, а также в качестве источника и приемника данных, в конечном итоге хранящихся в базе данных.
Когда серверная часть Tapestry повзрослела, клиентская сторона не успевала. Tapestry включает в себя некоторые отличные функции, такие как то, как он позволяет на стороне сервера управлять клиентским JavaScript модульным и эффективным способом. Однако этого становится все меньше … и напряжение, вызванное взаимной уступкой между логикой на стороне клиента и на стороне сервера, росло с каждым выпуском.
Нигде это не проявляется так явно, как в том, как Tapestry обращается к формам HTML. Это всегда было сложной задачей в Tapestry, потому что динамический рендеринг, который может происходить, должен соответствовать динамической обработке отправки формы. В Tapestry подход заключается в сериализации в инструкции формы, которые будут использоваться при отправке формы (см. Метод store () в FormSupport).API). Эти инструкции используются во время обработки запроса на отправку формы, чтобы переконфигурировать необходимые компоненты и направить их на чтение параметров запроса, выполнение проверок и отправку обновленных значений обратно в свойства объектов на стороне сервера. Если вы когда-нибудь задумывались, что такое скрытое поле ввода t: formdata внутри каждой формы Tapestry … ну, теперь вы знаете: это сериализованный поток Java-объектов, закодированных в GZipped и MIME.
Однако, относительно многих других вещей в Гобелене, это немного неуклюже и ограничено. Вы начинаете замечать это, когда вы видите теплый ответ на вопросы в списке рассылки, такие как « как выполнять проверку по нескольким полям? ». Делать более сложные вещи, такие как высокодинамичные макеты форм или формы с равномерными отношениями между полями, может быть проблематичным (хотя все еще вообще возможно) … но это требует слишком большого внутреннего знания Гобелена, и результаты в браузере кажутся немного неуклюжими, немного неуклюжими. Гобелен начинает чувствовать, что он мешает, и это никогда не приемлемо.
Проще говоря, абстракции Гобелена на формах и полях являются неясными и недостаточными. Гобелен пытается сделать слишком много и просто не может идти в ногу с современными, разумными требованиями с точки зрения отзывчивости и удобства использования внутри клиента. Мы привыкли к перестраиванию и переформатированию страниц, даже когда мы печатаем. Чтобы Tapestry понимал, как обрабатывать отправку формы, ему нужна модель того, как форма выглядит на стороне клиента, и у нее просто ее нет. Не существует эффективного способа сделать это без существенного ограничения того, что возможно на стороне клиента, или не требующего передачи гораздо большего количества данных в запросах или сохранения на стороне сервера в сеансе.
Основной проблемой здесь является общий цикл отправки формы, особенно в сочетании с необходимостью Tapestry сериализовать команды в форму (как скрытое поле t: formdata). Как только вы добавите Ajax в этот микс, где новые поля и правила создаются динамически (на стороне сервера) и устанавливаются в DOM на стороне клиента … хорошо, становится все сложнее и сложнее управлять. Добавьте еще несколько сложностей (таких как сочетание временных и постоянных объектов Hibernate или динамическое создание дочерних объектов и отношений) в форму, это может быть мозгом, заставляющим Tapestry делать правильные вещи при отправке формы : вам нужно точно понять, как Tapestry обрабатывает эту информацию: и как добавлять свои собственные обратные вызовы в поток обратных вызовов, чтобы выполнить абсолютно правильную вещь в точно точное время. Еще раз,это не гобелен, где вещи должныпросто работа .
Кроме того, есть некоторые сомнения даже в желательности всей модели. Во многих случаях имеет смысл объединить ряд изменений в отдельные свойства … но во многих других случаях столь же желательно, чтобы отдельные изменения фильтровали обратно на сервер (и в базу данных), когда пользователь перемещается. Form-submit-and-re-render — это стиль зеленого экрана взаимодействия с пользователем. Сейчас ожидается прямое взаимодействие, и это то, что должен принять Гобелен.
В чем же решение? Ну, это все еще очень движущаяся цель. Цель состоит в том, чтобы упростить создание клиентских библиотек JavaScript, упростить интеграцию с такими библиотеками, как jQuery (и его обширную библиотеку расширений), упростить и повысить эффективность на стороне клиента и не пожертвовать функциями, которые сделать гобелен веселым и продуктивным в первую очередь.
Общее видение
Общее видение разбивается на несколько этапов:
- Уменьшить или удалить внешние зависимости
- Модульный JavaScript
- Изменить инициализации страницы для использования модулей
- Охватите логику контроллера на стороне клиента
Конечно, все эти шаги зависят от других, поэтому нет хорошего порядка их обсуждать.
Уменьшение и удаление внешних зависимостей
Преимуществом Tapestry на стороне клиента всегда было множество «готовых» функций: проверка на стороне клиента, зоны и другие Ajax-ориентированные поведения, а также хорошо интегрированная система для выполнения инициализации на уровне страницы.
Тем не менее, эта сила также является слабым местом, поскольку нестандартное поведение слишком тесно связано с библиотеками Prototype и Scriptaculous … разумный выбор в 2006 году, но не в ногу с сегодняшней отраслью. Не только с точки зрения динамики jQuery, но и с точки зрения совершенно разных подходов, таких как Sencha / ExtJS и другие.
В 2006 году было принято сознательное решение не пытаться создать слой абстракции, пока я не понял все абстракции. У меня было промежуточное время, чтобы принять эти абстракции. Теперь большая проблема — это импульс и обратная совместимость.
Удаляя ненужные поведения, такие как анимации, мы можем уменьшить потребности Tapestry на стороне клиента. Гобелен должен иметь возможность прикреплять обработчики событий к элементам. Он должен быть в состоянии легко найти элементы с помощью уникальных идентификаторов или с помощью селекторов CSS . Он должен уметь запускать запросы Ajax и обрабатывать ответы, включая динамические обновления элементов .
Все эти вещи целесообразно абстрагировать, и благодаря тому, что сделать выполнение JavaScript как часть рендеринга страницы или обновления страницы ( что уже присутствует в Tapestry 5.3 ) еще проще, делегированные в настоящее время встроенные функции (такие как анимация) могут быть делегированы приложение, которое, вероятно, лучший выбор в любом случае.
Модуляризация JavaScript
Гобелен всегда был осторожен, чтобы избежать загрязнения клиентского пространства имен. В версии 5.2 большая часть JavaScript Tapestry была заключена в объект Tapestry. В Tapestry 5.3, второй объект, T5, был представлен с намерением постепенно заменить оригинальный объект Tapestry (но этот пост представляет изменение направления).
Однако этого недостаточно. Слишком часто пользователи создают встроенные библиотеки JavaScript или библиотеки JavaScript, которые определяют «пустые» переменные и функции (которые в конечном итоге добавляются в объект окна браузера). Это вызывает проблемы, в том числе конфликты между компонентами (которые предоставляют конкурирующие определения объектов и функций), или поведение, которое меняется в зависимости от того, был ли JavaScript добавлен на страницу как часть рендеринга на всю страницу, или с помощью рендеринга частичной страницы Ajax.
Правильный подход состоит в том, чтобы поощрять и использовать некоторую форму архитектуры модулей JavaScript , где нет явных глобальных переменных или функций, и что весь JavaScript оценивается внутри функции, что позволяет использовать частные переменные и функции.
В настоящее время я рассматриваю RequireJS как способ организации JavaScript. Tapestry поможет организовать собственный код в модули, а также модули JavaScript для конкретных приложений (или даже для страниц). Это будет означать, что отмена ссылки на объект T5 больше не будет происходить (вне какого-либо временного режима совместимости).
Например, нажатие кнопки внутри некоторого элемента контейнера может, в разделе 5.3, опубликовать событие с помощью клиентской системы публикации / подписки Tapestry. В следующем примере события click всплывают от кнопок (с именем CSS-класса кнопки) к элементу контейнера, а затем публикуются под названием темы при нажатии кнопки.
(function() { $(element).on("click", ".button", function(event, element) { event.stop(); T5.pubsub.publish("button-clicked", this, element); }); })();
Рассмотрим этот сокращенный пример, так как он не объясняет, где переменная элемента определена или инициализирована; важная часть — взаимодействие с клиентской библиотекой Tapestry: ссылка на функцию T5.pubsub.publish.
В разделе 5.4, используя функцию RequireJS require, вместо этого она может быть закодирована как:
require(["t5/pubsub"], function(pubsub) { $(element).on("click", ".button", function(event, element) { event.stop(); pubsub.publish("button-clicked", this, element); }); });
Здесь модуль t5 / pubsub будет загружен RequireJS и передан в качестве параметра в функцию, которая выполняется автоматически. Таким образом, это поддерживает модуляризацию JavaScript и использует способность RequireJS загружать модули «на лету» по мере необходимости.
Обратите внимание на разницу между двумя примерами; в первом примере кодирование в качестве модуля было необязательным (но рекомендуется), поскольку необходимая функция publish () была доступна в любом случае. В примере 5.4, кодирование с использованием модулей JavaScript фактически требуется : анонимная функция, переданная для require (), фактически является модулем, но только с помощью require () (или defineire () RequireJS) функция publish () может быть доступным.
Это и кнут, и кнут; морковь — это то, как легко объявить зависимости и передать их в вашу функцию как модуль. Суть в том, что (в конечном итоге) единственный способ получить доступ к этим зависимостям — это предоставить модуль и объявить зависимости.
Изменить инициализации страницы для использования модулей
Tapestry имеет достаточно сложную систему, позволяющую компонентам описывать свои требования JavaScript по мере их отображения в форме среды JavaScriptSupport (среда представляет собой разновидность объекта службы для каждого потока / запроса). Методы в JavaScriptSupport позволяют компоненту запрашивать импорт библиотеки JavaScript на странице (хотя это чаще всего выполняется с помощью аннотации импорта ) и запрашивать выполнение функций инициализации .
Часть поддержки Ajax в Tapestry заключается в том, что в запросе Ajax методы JavaScriptSupport по-прежнему могут вызываться, но за интеграцию этих запросов в общий ответ отвечает совершенно другая реализация (которая в запросе Ajax является объектом JSON, а не простым поток HTML).
Вот пример компонента из библиотеки TapX:
/** * Component that renders a placeholder for content with controls to expand or collapsed * the content. The content is not rendered until the expando is first expanded. The content * is the body of the Expando component. * * @since 1.1 */ @Import(stack = "tapx-core") public class Expando implements ClientElement { @Property @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.LITERAL) private Block title; private String clientId; @Inject private ComponentResources resources; @Environmental private JavaScriptSupport jss; @InjectComponent private Zone content; void setupRender() { clientId = jss.allocateClientId(resources); } public String getClientId() { return clientId; } void afterRender() { Link link = resources.createEventLink("expand"); JSONObject spec = new JSONObject("clientId", clientId, "zoneId", content.getClientId(), "contentURL", link.toString()); jss.addInitializerCall("tapxExpando", spec); } Object onExpand() { return resources.getBody(); } }
Аннотация @Import указывает, что на страницу будет импортирован стек (набор связанных библиотек JavaScript, определенных в другом месте); альтернативно, компонент может импортировать любое количество определенных файлов JavaScript, расположенных либо в контекстной папке веб-приложения, либо в пути к классам.
Внутри метода afterRender () код создает JSONObject данных, необходимых на стороне клиента для выполнения операции. Вызов addInitializerCall ссылается на функцию по имени: эта функция должна быть добавлена в объект пространства имен T5.Initializer. Обратите внимание на префикс naming: tapxExpando: для идентификации библиотеки и предотвращения коллизий с любым другим приложением или библиотекой, которые также добавили свои собственные функции в объект T5.initializers.
Библиотека JavaScript включает в себя функцию, которая будет вызываться:
T5.initializers.tapxExpando = function(spec) { var loaded = false; function zone() { return Tapestry.findZoneManagerForZone(spec.zoneId); } function expand(event) { $(spec.clientId).removeClassName("tx-collapsed").addClassName( "tx-expanded"); $(spec.clientId).down("div.tx-content").show(); if (!loaded) { loaded = true; zone().updateFromURL(spec.contentURL); } } function collapse(event) { $(spec.clientId).removeClassName("tx-expanded").addClassName( "tx-collapsed"); $(spec.clientId).down("div.tx-content").hide(); } $(spec.clientId).down(".tx-expand").observe("click", expand); $(spec.clientId).down(".tx-collapse").observe("click", collapse); };
В соответствии с 5.4 это в значительной степени будет таким же, за исключением:
- Для каждой библиотеки (или приложения) будет определен пакет Java для хранения библиотечных модулей.
- В среде JavaScriptSupport появятся новые методы для ссылки на функцию внутри модуля для вызова.
- Стеки будут состоять не только из отдельных библиотек, но и из модулей в соответствии с соглашением об именовании и упаковке.
Охватите логику контроллера на стороне клиента
Обсуждаемые до сих пор изменения сглаживают лишь несколько грубых краев; они по-прежнему позиционируют код Гобелена, запущенный на сервере, как ведущий всего шоу.
Как упоминалось ранее; для любого сложного пользовательского интерфейса задача состоит в том, чтобы согласовать пользовательский интерфейс на стороне клиента (с точки зрения полей формы, элементов DOM и параметров запроса) с компонентами на стороне сервера; это кодируется в скрытое поле t: formdata. Тем не менее, я считаю, что для любой динамической формы Гобелен является или близок к концу пути для этого подхода.
Вместо этого пришло время использовать клиентскую логику, написанную на JavaScript, в браузере. В частности, оторвитесь от HTML-форм и используйте более динамичную структуру, в которой «отправка» формы всегда работает через обновление Ajax … и отправляется не простой набор параметров и значений запроса, а представление JSON что было обновлено, изменено или создано.
Мое особое видение состоит в том, чтобы интегрировать Backbone.js (или что-то очень похожее), чтобы полностью переместить эту логику на сторону клиента. Это фундаментальное изменение: то, где клиентская сторона может свободно изменять и перенастраивать пользовательский интерфейс любым удобным для него способом, и в конечном итоге несет ответственность за упаковку завершенных данных и их отправку на сервер.
Когда вы привыкли к компоненту BeanEditForm , это может показаться шагом назад, поскольку вы в конечном итоге отвечаете за написание чуть большего количества кода (в JavaScript) для реализации пользовательского интерфейса, проверки ввода и связей между полями. Однако, как бы ни был интересен BeanEditForm, декларативный подход к проверке на клиенте и сервере оказался ограниченным и ограничивающим, особенно в условиях межполевых отношений. Мы могли бы попытаться расширить декларативный характер, введя правила или даже языки сценариев для установления отношений … или мы могли бы двигаться в ситуации, которая ставит разработчика на место водителя.
Кроме того, некоторые будут обеспокоены тем, что это является нарушением принципа СУХОЙ ; однако я согласен с другой философией, что проверка на стороне клиента и на стороне сервера в любом случае принципиально различна; это обсуждается в отличном сообщении в блоге Иана Биклинга .
Конечно, будут компоненты и сервисы, которые помогут в этом процессе, с точки зрения извлечения данных в формат JSON и преобразования данных JSON в набор обновлений объектов на стороне сервера. Существует также ряд проблем безопасности, которые требуют тщательной проверки того, что приходит от клиента в запросе Ajax. Кроме того, будут добавлены новые библиотеки для упрощения создания этих динамических пользовательских интерфейсов.
Вывод
В этом видении будущего Tapestry инфраструктура на стороне сервера начинает смещаться с фокуса всего поведения на фасилитатора : он рисует широкие задачи на сервере, но ключевые взаимодействия в конечном итоге работают исключительно на клиенте.
Я уверен, что это мнение будет противоречивым: в конце концов, на самом деле сообщество действительно хочет просто «jQuery вместо Prototype». Тем не менее, все факторы, описанные в вышеприведенных разделах, я считаю крайне важными для сохранения актуальности Tapestry, так как она охватывает сторону клиента так, как этого требует сторона клиента.
Я думаю, что это изменение в фокусе имеет большое значение; Я думаю, что также необходимо, чтобы Гобелен оставался актуальным в среднесрочной и долгосрочной перспективе. Я слышал от многих отдельных разработчиков (не обязательно пользователей Tapestry), что они действительно хотят «просто jQuery и спокойный API»; Я думаю, что Tapestry может быть тем спокойным API, но, используя многие другие сильные стороны Tapestry, он может быть намного больше. Создавая что-то прямо на металле, вы чувствуете себя уверенно … пока вы не задействуете всю инфраструктуру, которую предоставляет Tapestry, включая лучшие в своем классе отчеты об исключениях, оперативную агрегацию и минимизацию JavaScript и (конечно) перезагрузку живых классов во время разработки , Ява
Я очень хочу вывести Tapestry на передний край разработки веб-приложений … и сделать это быстро! Следите за списком рассылки разработчиков Tapestry, чтобы увидеть, как все это закончится.
От http://tapestryjava.blogspot.com/2011/11/tapestry-54-focus-on-javascript.html