2

I would like to know if there is a possibility in JSF (or any of its frameworks, such as Primefaces) to have a variable order of the components, or more precisely panels, that will be displayed in the page.

For example, a user is displayed 3 different content sections (these being panels from 1 to 3). However, in settings, he is supposed to set the order in which these content sections are displayed (say, panel3 should be at the top, panel1 in the middle, and panel2 at the bottom).

Only idea coming to my mind is to use component binding or some kind of an invisible sorted datalist, but those are approaches that I would rather avoid as it involves too many workarounds.

Vanja Lee
  • 263
  • 4
  • 17
  • Why not have the panel order defined for each role(stored in backend) and then display the panel as per the user role and proceed to populate them? – Balwinder Singh Jan 14 '19 at 22:00
  • problem is that I have 10 panels. That gives us huge number of possible combinations... also roles are not really effecting anything. Each user (having the same role as others) sets the order himself. – Vanja Lee Jan 14 '19 at 22:01
  • In that case, create a user preferences table and have it store a default order of panels per user. In the UI, provide user an option to change the order and that will update this table. Every time the user loads the page, this table would be used to get the panels and their ordering for the respective user – Balwinder Singh Jan 14 '19 at 22:15
  • I'm not sure I understand what is that you are suggesting. I already have an ordered list in preferences that user is setting himself. This order is ofc saved in the database. However, when I write the .xhtml page, I am to provide different implementations for each of these sections (meaning, different panels and different components). At this point I am hardcoding the rendering order in the .xhtml. How is the list the user has set in his settings going to effect the order in wich these panels are displayed? – Vanja Lee Jan 14 '19 at 22:22
  • 1
    So, you have the first part done and the second part i.e. showing the exact panels in UI needs to be taken care of. You can create all the different panels along with their components as separate composite components in JSF. Once you have your composite components defined, you can have a conditional logic in your main xhtml file which would look for the given type of panel user wishes to have displayed at a given position and based on that you will enable/show the appropriate composite component - which have the required panel and components defined within them. Read about composite components – Balwinder Singh Jan 14 '19 at 22:32
  • I know the above can get a bit confusing but once you read about the composite components; you would have a clearer picture of the way you wish to implement it – Balwinder Singh Jan 14 '19 at 22:36
  • Ah... I get an idea now, but I forgot to mention that cc are also no go, as they don't get rendered correctly within primefaces extension masterDetail component (but that's a whole different problem). Truth is, even if I were able to use them at this point, I would have to have A LOT OF condition logic due to large number of combinations... I was hoping for some DataModel-like component, in which the panels could be declared as children with certain dynamic order... – Vanja Lee Jan 14 '19 at 22:55

1 Answers1

4

This is actually quite easy in JSF. The framework offers you the ability to plug yourself into the different rendering and build events of the life cycle. So you can, for example, easily use this functionality to shuffle components around before the view is rendered and sent to the browser.

The code

Here is an example of a XHTML page with five p:panel components defined. Each time you reload the page, the components will be shuffled and shown in a different order. You can easily adapt this to show them in an order you prefer or according to some configuration settings;

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Shuffle test</title>
    </h:head>
    <h:body>
        <h:panelGroup>
            <p:panel header="First"/>
            <p:panel header="Second"/>
            <p:panel header="Third"/>
            <p:panel header="Fourth"/>
            <p:panel header="Fifth"/>
            <f:event listener="#{shuffleBackingBean.onShuffle}" type="preRenderComponent" />
        </h:panelGroup>
    </h:body>
</html>

As you can see from the placement of the f:event tag, we plug ourselves into the preRenderComponentphase of the parent h:panelGroup. This allows us to receive an event before it's time for the rendering phase of this component.

@Named
@ViewScoped
public class ShuffleBackingBean implements Serializable {
    public void onShuffle(ComponentSystemEvent event) {
        final List<UIComponent> components = new ArrayList<>(event.getComponent().getChildren());
        Collections.shuffle(components);

        event.getComponent().getChildren().clear();
        event.getComponent().getChildren().addAll(components);  
    }
}

The backing bean above defines the onShufflemethod and just shuffles the components around when called. If you reload the page, the components get re-shuffled.

Side note

The reason for doing a shuffle() on a copy of the component list is that JSF uses a custom ChildrenList class that is based on ArrayList. The implementation is botched and causes Collections.shuffle() to crash with an IndexOutOfBoundsException. This just works around that issue.

An alternate solution

Another way of dealing with this would be to rely on some component that offers built-in sorting or use a binding declared on the h:panelGroup component. This would allow you to populate this component programmatically based on some setting. However, this moves much of the view definition out of the XHTML file and into a java class. It also complicates things slightly if you have a lot of sub-components in your panels and makes it even more complicated if they are very different from each other. Defining all that programmatically can be quite a hassle.

Conclusion

Personally, I prefer plugging into the event cycle and just moving or modifying components already defined in the XHTML page (as per the code above), as this moves more of the view definition into the XHTML file where it belongs.

Adam Waldenberg
  • 2,271
  • 9
  • 26
  • Ahhh! this is perfect, thank you Adam. Precisely what I was looking for... Problem is actually quite trivial, it would be shame if framework that exists for longer than 15 years didn't have such a feature. – Vanja Lee Jan 15 '19 at 07:57
  • @VanjaLee Glad I could help. JSF is extremely flexible - this is just one of the ways you can solve this particular issue. But as you said, this solution is trivial and easy to handle. – Adam Waldenberg Jan 15 '19 at 08:29
  • @AdamWaldenberg: Again, great answer. About the implementation being 'botched', is that the case for both Mojarra and MyFaces or did you just check with one of them? If so, which one (and maybe also add version info. Cheers!) – Kukeltje Jan 15 '19 at 10:18
  • @Kukeltje I only tested it on Mojara 2.3.3.99. However, I would imagine it's pretty much the same everywhere. I would guess `ChildrenList` probably omits overloading a method or two, causing the index/size being out of sync and thus triggering the `shuffle()` call to crash. If MyFaces behaves differently (and somebody is willing to test), we could expand the answer though. – Adam Waldenberg Jan 16 '19 at 02:01