Ответ 1
Прямым подходом будет следующий вид:
<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>
. Любой postback не будет работать, потому что он полностью не видит состояние представления. Вы можете решить эту проблему путем обновления до минимального значения Mojarra 2.3.0 или с помощью script, найденного в этом ответе h: commandButton/h: commandLink не работает при первом нажатии, работает только на втором нажмите, или если вы уже используете библиотеку служебных программ JSF OmniFaces, используйте FixViewState script. Или, если вы уже используете 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>
Недостатком является то, что представление станет относительно большим и что все связанные управляемые beans могут быть излишне инициализированы, даже если они не будут использоваться в соответствии с предоставленным условием.
Что касается позиционирования элементов, это просто вопрос применения правого CSS. Это выходит за рамки JSF:) По крайней мере, <h:panelGroup layout="block">
отображает <div>
, поэтому это должно быть достаточно хорошим.
И последнее, но не менее важное: этот подход SPA (Single Page Application) не оптимизирован для SEO. Все страницы не индексируются поисковыми роботами и не могут быть заклассифицированы конечными пользователями. Возможно, вам придется столкнуться с историей HTML5 на клиенте и предоставить резервную копию на стороне сервера. Более того, в случае страниц с формами тот же экземпляр с ограниченным охватом bean будет использоваться повторно во всех страницах, что приведет к неинтуитивному поведению при переходе на предыдущую страницу. Я бы предложил вместо этого использовать шаблонный подход, как указано во второй части этого ответа: Как включить другой XHTML в XHTML с помощью JSF 2.0 Facelets? См. Также Как перемещаться в JSF? Как заставить URL-адрес отражать текущую страницу (а не предыдущую).