How to ajax-refresh dynamic include content by navigation menu? (JSF SPA)
Asked Answered
L

3

36

I'm just learning JSF 2 thanks to this site I had learned a lot in such a short time.

My question is regarding how to implement a common layout to all my JSF 2 pages and have only the content part of the page refresh not the whole page whenever I click a link/menu from a different panel. I am using the Facelets approach it does what I want except that each time I click a link from a panel (e.g. menu items from left panel) the whole page is refreshed. What I am looking for is a way to refresh only the content part of my page. To illustrate this below is my target pagelayout.

enter image description here Did not post my code because I'm not sure if Facelets can do this . Are there other approach more suited for my requirement other than Facelets?

Laellaertes answered 18/8, 2011 at 14:3 Comment(0)
P
66

A straightforward approach would be the following view:

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

With this bean:

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

     private String page;

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

     // +getter+setter.
 }

In this example, the actual include templates are include1.xhtml, include2.xhtml and include3.xhtml in /WEB-INF/includes folder (folder and location is fully free to your choice; the files are just placed in /WEB-INF in order to prevent direct access by guessing the URL in browser's address bar).

This approach works in all MyFaces 2.x versions, but requires in case of Mojarra a minimum of 2.3.x. In case you're using a Mojarra version older than 2.3.0, then this all fails when the <ui:include> page in turn contains a <h:form>. Any postback will fail because it is totally missing the view state. You can solve this by upgrading to minimally Mojarra 2.3.0 or with a script found in this answer h:commandButton/h:commandLink does not work on first click, works only on second click. Or, if you're already using PrimeFaces and exclusively use <p:xxx> ajax, then it's already transparently taken into account.

Also, make sure that you're using minimally Mojarra 2.1.18 as older versions will fail in keeping the view scoped bean alive, causing the wrong include being used during postback. If you can't upgrade, then you'd need to fall back to the below (relatively clumsy) approach of conditionally rendering the view instead of conditionally building the view:

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

The disadvantage is that the view would become relatively large and that all associated managed beans may be unnecessarily initialized even though when they would not be used as per the rendered condition. See also JSTL in JSF2 Facelets... makes sense? for an in depth explanation on <ui:include src="#{...}"> vs <x:someComponent rendered="#{...}">.

As to positioning of the elements, that's just a matter of applying the right CSS. That's beyond the scope of JSF :) At least, <h:panelGroup layout="block"> renders a <div>, so that should be good enough.

Last but not least, this SPA (Single Page Application) approach is not SEO friendly. All the pages are not indexable by searchbots nor bookmarkable by endusers, you may need to fiddle around with HTML5 history in client and provide a server side fallback. Moreover, in case of pages with forms, the very same view scoped bean instance would be reused across all pages, resulting in unintuitive scoping behavior when you navigate back to a previously visited page. I'd suggest to go with templating approach instead as outlined in 2nd part of this answer: How to include another XHTML in XHTML using JSF 2.0 Facelets? See also How to navigate in JSF? How to make URL reflect current page (and not previous one).

Parade answered 18/8, 2011 at 20:47 Comment(12)
EXACTLY WHAT I WAS LOOKING FOR!!! Plus the implementation clean and easy to understand. Lucky for me the application I plan to use this is more of an Enterprise LAN application also my users does not require it to be SEO friendly, searchbotable (if searchbotable is ever a word) :) and bookmarkable thats why its like tailored fit for my requirement. Thank you very much.Laellaertes
Hi,thank you for fantastic answer BalusC. I have a submit form in this template like include1 and I want to go to include2 by a command button, but it needs to clicked twice.Is there a way to correct that?Haviland
@BulusC: How can I use fixviewstate.js for primeface4(<p:commandButton)? Thank you.Haviland
@Sima: It automatically recognizes PrimeFaces (specifically: jQuery).Parade
@BalusC: if I have a Primefaces <p:panelMenu> instead of a simple <ul> for the menu, what is the property I need to set in the <p:menuitem>to render the content?Nerine
@Parade you said " I'd suggest to go with templating approach instead as outlined in 2nd part of this answer ", i don't understand which one are you referring to? Is it the one you used fragment to wrap up the ui:includeEleanor
@ChristianIbanibo The <ui:define>/<ui:insert> section.Parade
@Parade You are talking about using <ui:composition template>, which is not possible to do SPAs. I have an application which i want to convert to SPA with your professional advise should i use AngularFaces for it?Eleanor
@ChristianIbanibo Did you understand the entire last paragraph? There I'm explaining why SPA is a bad idea and that you'd better use templating approach instead. If you want to continue using SPA nonetheless (e.g. because you're developing something like Gmail inbox, which is never supposed to be SEO friendly, then that's OK), then just ignore the templating approach :)Parade
@Parade I think i understand what you said concerning the bad idea of SPA which i intend using HTML5 history for bookmarking, is a social media which i want it to display like facebook or twitter single page. Which means i won't be using templating approach. But this is what you said which i don't understand "the very same view scoped bean instance would be reused across all pages, resulting in unintuitive scoping behavior when you navigate back to a previously visited page"? memory consumption? is it still possible to use JSF for SPA for what i am doing.Eleanor
@ChristianIbanibo: when you navigate in a SPA, you don't actually request a new view, but you merely changes content of the current view. So the very same view scoped bean will be reused for next page. This will work just fine, but it's unintuitive.Parade
Let us continue this discussion in chat.Eleanor
U
2

If you only want to refresh part of the page, there are only 2 ways to go (for the web in general, not just JSF). You have to use frames or Ajax. JSF 2 supports ajax natively, check out the f:ajax tag to update just 1 component without reloading the entire page.

Unparliamentary answered 18/8, 2011 at 14:14 Comment(6)
As I have experienced as far, this doesn't work out very well with include content containing a form.Parade
true...might have to play around also with either wrapping the whole section in a form vs including the form in the ajaxified sectionUnparliamentary
I was more referring as to using <ui:include src="#{bean.dynamicinclude}" /> syntax. For some reason the src is resolved differently during restore view of a postback than it was during render response which in turn causes that the form is not found and thus nothing is been processed. But that might also be just a Mojarra bug. I'm not sure. Yet.Parade
Interestingly, I tried MyFaces 2.1.1 and it just don't display the desired dynamic include during render response already.Parade
To make work ui:include src="..." as with facelets 1.1.x you have to set web config param org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS to true. See MYFACES-3271 for details.Base
I tested it (in fact this is a common question) and you have to set both org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS and org.apache.myfaces.REFRESH_TRANSIENT_BUILD_ON_PSS_PRESERVE_STATE to true, so the content will be saved and restored fully and validation will work as expected.Base
B
0

Netbeans provides a wizard that create the proposed layout with minimal effort using JSF. So, the best way to start is take a look at Facelets Template Wizard and look at the generated source.

Base answered 18/8, 2011 at 19:1 Comment(2)
How about the ajax load/refresh part? Does Netbeans also generate that part? I don't think so.Parade
Yes,you're right.Netbeans does not generate that part. But it has a nice wizard to define the layout. The ajax part obviously has to be done manually.There are many ways to do it, from using "rendered" property to use ui:include src="...". If you have multiple forms on a page and you need to keep "synchronized" the state you can use this hack on myfaces: <script type="text/javascript"> window.myfaces = window.myfaces || {}; myfaces.config = myfaces.config || {}; myfaces.config.no_portlet_env = true; </script> SeeMYFACES-3159Base

© 2022 - 2024 — McMap. All rights reserved.