Как ajax-обновить динамическое включение контента по меню навигации? (JSF SPA)

Я только изучаю JSF 2, благодаря этому сайту я многому научился за такое короткое время.

Мой вопрос касается того, как реализовать общий макет для всех моих страниц JSF 2 и чтобы обновлялась только часть содержимого страницы, а не вся страница, когда я щелкаю ссылку / меню на другой панели. Я использую подход Facelets, он делает то, что я хочу, за исключением того, что каждый раз, когда я нажимаю ссылку на панели (например, элементы меню на левой панели), вся страница обновляется. То, что я ищу, это способ обновить только контентную часть моей страницы. Чтобы проиллюстрировать это ниже, моя цель pagelayout.

Не опубликовал мой код, потому что я не уверен, что Facelets может сделать это. Есть ли другой подход, более подходящий для моего требования, кроме Facelets?

3 ответа

Решение

Прямой подход будет следующим:

<h:panelGroup id="header" layout="block">
    <h1>Header</h1>
</h:panelGroup>
<h:panelGroup id="menu" layout="block">
    <h:form>
        <f:ajax render=":content">
            <ul>
                <li><h:commandLink value="include1" action="#{bean.setPage('include1')}" /></li>            
                <li><h:commandLink value="include2" action="#{bean.setPage('include2')}" /></li>            
                <li><h:commandLink value="include3" action="#{bean.setPage('include3')}" /></li>            
            </ul>
        </f:ajax>
    </h:form>
</h:panelGroup>
<h:panelGroup id="content" layout="block">
    <ui:include src="/WEB-INF/includes/#{bean.page}.xhtml" />
</h:panelGroup>

С этим бобом:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

     private String page;

     @PostConstruct
     public void init() {
         page = "include1"; //  Default include.
     }

     // +getter+setter.
 }

В этом примере фактические шаблоны включения include1.xhtml, include2.xhtml а также include3.xhtml в /WEB-INF/includes папка (папка и папка полностью свободны на ваш выбор; файлы просто помещаются в /WEB-INF для предотвращения прямого доступа, угадав URL в адресной строке браузера).

Этот подход работает во всех версиях MyFaces, но требует как минимум Mojarra 2.3.0. Если вы используете версию Mojarra более раннюю, чем 2.3.0, то все это не работает, когда <ui:include> страница в свою очередь содержит <h:form>, Любая обратная передача не удастся, потому что в ней полностью отсутствует состояние просмотра. Вы можете решить эту проблему путем обновления до минимально Mojarra 2.3.0 или с помощью скрипта, найденного в этом ответе h: commandButton / h: commandLink не работает при первом щелчке, работает только при втором щелчке или, если вы уже используете библиотеку утилит JSF OmniFaces, используйте его скрипт FixViewState. Или, если вы уже используете PrimeFaces и используете исключительно <p:xxx> Аякс, то это уже прозрачно учтено.

Кроме того, убедитесь, что вы используете минимально Mojarra 2.1.18, так как более старые версии не будут поддерживать боб области видимости живым, вызывая неправильное включение, используемое во время обратной передачи. Если вы не можете выполнить обновление, тогда вам придется вернуться к нижеприведенному (относительно неуклюжему) подходу условной визуализации представления вместо условного построения представления:

...
<h:panelGroup id="content" layout="block">
    <ui:fragment rendered="#{bean.page eq 'include1'}">
        <ui:include src="include1.xhtml" />
    </ui:fragment>
    <ui:fragment rendered="#{bean.page eq 'include2'}">
        <ui:include src="include2.xhtml" />
    </ui:fragment>
    <ui:fragment rendered="#{bean.page eq 'include3'}">
        <ui:include src="include3.xhtml" />
    </ui:fragment>
</h:panelGroup>

Недостатком является то, что представление станет относительно большим, и что все связанные управляемые bean-компоненты могут быть излишне инициализированы, даже если они не будут использоваться согласно представленному условию.

Что касается позиционирования элементов, это просто вопрос применения правильного CSS. Это выходит за рамки JSF:) По крайней мере, <h:panelGroup layout="block"> оказывает <div>, так что это должно быть достаточно хорошо.

Наконец, что не менее важно, этот подход SPA (одностраничное приложение) не является SEO дружественным. Все страницы не индексируются поисковыми роботами и не могут быть добавлены в закладки конечными пользователями. Возможно, вам придется поиграться с историей HTML5 в клиенте и предоставить запасной вариант на стороне сервера. Более того, в случае страниц с формами один и тот же экземпляр bean-объекта области видимости будет повторно использоваться на всех страницах, что приведет к неинтуитивному поведению области видимости при переходе к ранее посещенной странице. Вместо этого я бы предложил использовать шаблонный подход, как описано во 2-й части этого ответа: Как включить еще один XHTML в XHTML с использованием Facelet JSF 2.0? Смотрите также Как ориентироваться в JSF? Как сделать так, чтобы URL отражал текущую страницу (а не предыдущую).

Если вы хотите обновить только часть страницы, есть только 2 способа (для Интернета в целом, а не только для JSF). Вы должны использовать фреймы или Ajax. JSF 2 изначально поддерживает ajax. Проверьте тег f: ajax, чтобы обновить только 1 компонент без перезагрузки всей страницы.

Netbeans предоставляет мастер, который создает предложенный макет с минимальными усилиями, используя JSF. Итак, лучший способ начать - взглянуть на Facelet Template Wizard и посмотреть на сгенерированный источник.

Другие вопросы по тегам