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

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

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

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


person royjavelosa    schedule 18.08.2011    source источник


Ответы (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>

С этим bean-компонентом:

@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> ajax, то это уже прозрачно учтено.

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

...
<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>

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

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

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

person BalusC    schedule 18.08.2011
comment
ТОЧНО ТО, ЧТО Я ИСКАЛ !!! Плюс реализация чистая и понятная. К счастью для меня, приложение, которое я планирую использовать, больше похоже на приложение для корпоративной локальной сети, и мои пользователи не требуют, чтобы оно было оптимизированным для SEO, поддерживающим поисковый робот (если поисковый робот - это когда-либо слово) :) требование. Большое Вам спасибо. - person royjavelosa; 19.08.2011
comment
Привет, спасибо за фантастический ответ BalusC. У меня есть форма отправки в этом шаблоне, например include1, и я хочу перейти к include2 с помощью командной кнопки, но ее нужно щелкнуть дважды. Есть ли способ исправить это? - person Sima; 11.03.2014
comment
@BulusC: Как я могу использовать fixviewstate.js для primeface4 (‹p: commandButton)? Спасибо. - person Sima; 11.03.2014
comment
@Sima: он автоматически распознает PrimeFaces (в частности: jQuery). - person BalusC; 30.01.2015
comment
@BalusC: если у меня есть Primefaces <p:panelMenu> вместо простого <ul> для меню, какое свойство мне нужно установить в <p:menuitem>, чтобы отображать контент? - person Alvaro Pedraza; 06.09.2016
comment
@BalusC, вы сказали, что я предлагаю использовать шаблонный подход, как указано во 2-й части этого ответа, я не понимаю, о чем вы говорите? Это тот фрагмент, который вы использовали для завершения ui: include - person Christian Ibanibo; 15.03.2019
comment
@ChristianIbanibo Раздел <ui:define>/<ui:insert>. - person BalusC; 15.03.2019
comment
@BalusC Вы говорите об использовании ‹ui: шаблона композиции›, что невозможно для создания SPA. У меня есть приложение, которое я хочу преобразовать в SPA с вашим профессиональным советом, следует ли мне использовать для него AngularFaces? - person Christian Ibanibo; 15.03.2019
comment
@ChristianIbanibo Вы поняли весь последний абзац? Здесь я объясняю, почему SPA - плохая идея и что вам лучше использовать шаблонный подход. Если вы все же хотите продолжать использовать SPA (например, потому что вы разрабатываете что-то вроде почтового ящика Gmail, который никогда не должен быть оптимизирован для SEO, тогда это нормально), тогда просто игнорируйте подход с использованием шаблонов :) - person BalusC; 15.03.2019
comment
@BalusC Я думаю, я понимаю, что вы сказали о плохой идее SPA, которую я собираюсь использовать для создания закладок в истории HTML5, - это социальная сеть, которую я хочу, чтобы она отображалась как отдельная страница facebook или twitter. Это означает, что я не буду использовать шаблонный подход. Но это то, что вы сказали, и я не понимаю, что один и тот же экземпляр bean-компонента с областью просмотра будет повторно использоваться на всех страницах, что приведет к неинтуитивному поведению при переходе на ранее посещенную страницу? потребление памяти? Можно ли по-прежнему использовать JSF для SPA для того, что я делаю. - person Christian Ibanibo; 15.03.2019
comment
@ChristianIbanibo: когда вы перемещаетесь в SPA, вы фактически не запрашиваете новое представление, а просто изменяете содержимое текущего представления. Таким образом, тот же самый bean-компонент с областью просмотра будет повторно использован для следующей страницы. Это будет работать нормально, но это не интуитивно. - person BalusC; 15.03.2019
comment
Позвольте нам продолжить это обсуждение в чате. - person Christian Ibanibo; 15.03.2019

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

person GBa    schedule 18.08.2011
comment
Насколько я знаю, это не очень хорошо работает с включением содержимого, содержащего форму. - person BalusC; 18.08.2011
comment
true ... возможно, придется также поиграть с переносом всего раздела в форму или с включением формы в раздел ajaxified - person GBa; 18.08.2011
comment
Я больше имел в виду использование синтаксиса <ui:include src="#{bean.dynamicinclude}" />. По какой-то причине src разрешается иначе во время восстановления представления обратной передачи, чем во время ответа рендеринга, что, в свою очередь, приводит к тому, что форма не найдена и, следовательно, ничего не обрабатывается. Но это также может быть просто ошибка Мохарры. Я не уверен. Пока что. - person BalusC; 18.08.2011
comment
Интересно, что я попробовал MyFaces 2.1.1, и он просто не отображает желаемое динамическое включение во время ответа на рендеринг. - person BalusC; 19.08.2011
comment
Чтобы заставить работать ui: include src = ... как и в случае с facelets 1.1.x, вам необходимо установить для параметра web config org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS значение true. Подробнее см. MYFACES-3271. - person lu4242; 19.08.2011
comment
Я тестировал его (на самом деле это частый вопрос), и вы должны установить для org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS и org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS_PRESERVE_STATE значение true, поэтому содержимое будет сохранено и проверка будет полностью восстановлена. ожидал. - person lu4242; 19.08.2011

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

person lu4242    schedule 18.08.2011
comment
Как насчет части загрузки / обновления ajax? Создает ли Netbeans и эту часть? Я так не думаю. - person BalusC; 18.08.2011
comment
Да, вы правы, Netbeans не генерирует эту часть. Но у него есть хороший мастер для определения макета. Часть ajax, очевидно, должна выполняться вручную. Есть много способов сделать это, от использования свойства rendered до использования ui: include src = .... Если у вас есть несколько форм на странице, и вам нужно синхронизировать состояние, которое вы можно использовать этот прием на myfaces: ‹тип сценария = текст / javascript› window.myfaces = window.myfaces || {}; myfaces.config = myfaces.config || {}; myfaces.config.no_portlet_env = true; ‹/Script› См. MYFACES-3159. - person lu4242; 19.08.2011