Последние несколько месяцев я потратил на доработку клиентской поддержки JavaScript в Tapestry 5, пытаясь отойти от жесткой привязки к Prototype. После всего этого рефакторинга, перекодирования, репозиции и простого хакерства я смог выполнить большую часть работы по внедрению поддержки jQuery всего за несколько часов, вчера и сегодня.
Очень скоро я буду выпускать еще одну предварительную версию, или вы можете получить последнюю из мастер-ветки Tapestry.
В настоящее время код для переключения с использования Prototype на jQuery выглядит следующим образом (скоро это станет проще):
public class EnableJQueryModule { @Contribute(SymbolProvider.class) @ApplicationDefaults public static void switchProviderToJQuery(MappedConfiguration<String, Object> configuration) { configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery"); } @Contribute(Compatibility.class) public static void disableScriptaculous(MappedConfiguration<Trait, Boolean> configuration) { configuration.add(Trait.SCRIPTACULOUS, false); configuration.add(Trait.INITIALIZERS, false); } }
Первая часть переопределяет провайдера как «jquery» (провайдер по умолчанию — «прототип»). В терминах Tapestry «инфраструктура infastructure» предоставляет API-интерфейсы для запросов DOM, манипулирования DOM и обработки запросов Ajax. Если вам не нравится jQuery, вы можете легко создать своего собственного провайдера для своей любимой платформы.
Часть 5.4 пытается управлять всеми этими различными проблемами совместимости на несколько более высоком уровне, чем символы конфигурации; это сервис совместимости с его чертами. Эти две черты отключают поддержку Scriptaculous (которая не нужна jQuery и не будет работать без Prototype) и отключают поддержку инициализаторов в стиле Tapestry 5.3.
Под покровом переключение между Prototype и jQuery работает довольно просто:
@Contribute(ModuleManager.class) public static void setupFoundationFramework(MappedConfiguration<String, Object> configuration, @Inject @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER) String provider, @Inject @Path("classpath:org/apache/tapestry5/t5-core-dom-prototype.js") Resource domPrototype, @Inject @Path("classpath:org/apache/tapestry5/t5-core-dom-jquery.js") Resource domJQuery) { if (provider.equals("prototype")) { configuration.add("t5/core/dom", new JavaScriptModuleConfiguration(domPrototype)); } if (provider.equals("jquery")) { configuration.add("t5/core/dom", new JavaScriptModuleConfiguration(domJQuery)); } // If someone wants to support a different infastructure, they should set the provider symbol to some other value // and contribute their own version of the t5/core/dom module. }
Сервис ModuleManager — это код, который обрабатывает запросы на модули от клиента. Он имеет конфигурацию, которая используется для обработки крайних случаев, таких как обработка традиционных библиотек JavaScript, как если бы они были модулями AMD. Приведенный выше код устанавливает переопределение на стороне сервера для модуля t5/core/dom
. Все клиентские модули Tapestry используют dom
; Ни один из них не использует Prototype или jQuery напрямую (за исключением пары, которая имеет доступ к функциональности Twitter Bootstrap).
Эти вклады гарантируют, что когда клиентская сторона запрашивает t5/core/dom
модуль, то, что будет отправлено обратно, будет либо реализацией, специфичной для прототипа, либо реализацией, специфичной для jQuery. Без этого вклада мы бы увидели ошибку 404 при dom
запросе модуля. Вместо этого на стороне клиента нет какой-либо особенной идеи dom
.
Основная задача dom
модуля состоит в том, чтобы обернуть элементы DOM внутри нового объекта, тем самым предоставляя новый API, который позволяет выполнять различные виды манипуляций, а также прослушивать события или запускать их. API представляет собой нечто вроде гибридной схемы между Prototype и jQuery, но довольно сильно склоняется к операциям в стиле jQuery и присвоению имен. dom
Второстепенная задача заключается в поддержке Ajax-запросов, а также добавлении обработчиков событий в объект документа.
На практике это может быть очень кратким и читабельным (частично, благодаря CoffeeScript):
dom.onDocument events.zone.update, (event) -> this.trigger events.zone.willUpdate content = event.memo.content unless content is undefined this.update content this.trigger events.zone.didUpdate
В этом листинге (который поддерживает некоторые свойства компонента Tapestry Zone) dom
хранится экспорт из модуля t5/core/dom
; он ожидает запуска events.zone.update
события; обратный вызов вызывается с помощью this
set для оболочки вокруг элемента, где было инициировано событие.
Событие здесь также является оберткой; это минимальное сочетание Prototype и jQuery: мне нравится memo
свойство из Prototype, так что оно присутствует. Обработчик событий запускает пару событий до и после и обновляет содержимое зоны. Почему до и после событий? По умолчанию они ничего не делают, но было бы просто добавить обработчики для выполнения некоторой анимации при добавлении контента.
В любом случае, поскольку все клиентские модули Tapestry кодируют этот API, они не знают и не заботятся, загружен ли на странице Prototype, jQuery или что-то еще. Если вы пишете компоненты Tapestry для повторного использования, кодирование по dom
API поможет обеспечить правильную работу вашего компонента во всех видах приложений Tapestry. Однако при кодировании приложения. вы оставляете за собой право выбирать, какой должна быть инфраструктура инфраструктуры: вы можете свободно использовать инфраструктуру инфраструктуры напрямую … если не считаете, что dom
API проще.