0

I´m trying to create an updateable GUI using JSF 2.2 and PrimeFaces 4.0. My problem is that the model is updated but not the view.

Here is my example page. It has just a Button which should make the two Panels change their places:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:p="http://primefaces.org/ui">
    <h:head/>
    <h:body>
        <h:form>
            <p:panelGrid id="grid">
                <p:row>
                    <p:column id="slot1" style="width: 100px; height: 100px">
                        <p:panel id="panel1" binding="#{test.panel1}" style="width: 100px; height: 100px"/>
                    </p:column>
                    <p:column id="slot2" style="width: 100px; height: 100px">
                        <p:panel id="panel2" binding="#{test.panel2}" style="width: 100px; height: 100px"/>
                    </p:column>
                </p:row>
            </p:panelGrid>

            <p:commandButton id="button2" value="Switch Panels">
                <p:ajax listener="#{test.onSwitchPanels(event)}" update="grid"/>
            </p:commandButton>
        </h:form>
    </h:body>
</html>

And the Java class:

@ManagedBean
@SessionScoped
public class Test {

Panel panel1 = new Panel();
Panel panel2 = new Panel();

public Test() {
    panel1.setHeader("Panel 1");
    panel2.setHeader("Panel 2");
}

public Panel getPanel1() {
    return panel1;
}

public void setPanel1(Panel panel) {
    this.panel1 = panel;
}

public Panel getPanel2() {
    return panel2;
}

public void setPanel2(Panel panel) {
    this.panel2 = panel;
}

public void onSwitchPanels(ActionEvent event) {
    Panel tmp = panel1;
    panel1 = panel2;
    panel2 = tmp;
}
}

Debugging shows me that the getters and setters are called after onSwitchPanels() with the correct new values when the button is clicked. But as I wrote the view is not updated. This happens only after a manual page refresh or the next click (which of course sets the model back to it´s original state but shows the switched panels). I already tried update="@form" and update="slot1 slot2" without luck...

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
jpstrube
  • 775
  • 1
  • 11
  • 27

4 Answers4

2

This code is honestly a WTF. Binding UI component instances to a session scoped bean is recipe for trouble. UI component instances are namely request scoped.

Even if you used a request scoped bean, this wouldn't work without rebuilding the view. The UI component tree is created during building of the view. You're manipulating the component references after building the view and expecting that the changes are magically reflected in the UI component tree. This is not true. Only their attributes would be reflected (thus, when you do e.g. panel1.setHeader(panel2.getHeader()) and vice versa, then it'll "work".

It isn't entirely clear why you were thinking in this direction. Generally you should manipulate the model dynamically, not the view (the UI components do not represent the model, they represent the view; making them properties of the model/controller doesn't magically make it the model). One of most straightforward ways is having a list of entities and present them in a dynamic <p:dataGrid> instead of a hardcoded <p:panelGrid> whereby you dynamically set the desired component attribtues based on the currently iterated model item.

E.g. this

<h:form>
    <p:dataGrid value="#{bean.items}" var="item" columns="2">
        <p:column><p:panel header="#{item.header}" /></p:column>
    </p:dataGrid>
    <p:commandButton value="swap" action="#{bean.swap}" update="grid" />    
</h:form>

with this in a @ViewScoped(!) bean:

private List<Item> items; // +getter

@PostConstruct
public void init() {
    items = new ArrayList<>();
    items.add(new Item("Panel 1"));
    items.add(new Item("Panel 2"));
}

public void swap() {
    items.add(items.remove(0)); // Note: works only if you've only 2 items ;)
}

The Item class is just a javabean with a header property. The average IDE can autogenerate the whole class.

See also:

Community
  • 1
  • 1
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • Thanks a lot. I was just prototyping. My problem is, that it is not enough for the desired functionality to change some attributes. I want to be able to have e.g. a Panel with a chart, a Panel with some text and some self written components, that can be rearranged freely in a grid. If this can only be done by reloading the view, then so be it. But perhaps JSF is not the right coice for my use case anyway... – jpstrube Nov 26 '13 at 09:04
  • If you want to conditionally build the view instead of conditionally render the view, then grab JSTL. – BalusC Nov 26 '13 at 09:23
0

I'm a bit rusty on my JSF / PF, but have you tried:

update="panel1 panel2"

Another thing to try is using

actionListener"#{test.onSwitchPanels}"

instead of

listener="#{test.onSwitchPanels(event)}"
Matt
  • 3,077
  • 4
  • 26
  • 50
  • I tried the first but it didn´t work. The second is not possible in `p:ajax`. – jpstrube Nov 25 '13 at 12:14
  • 1
    As far as I know, in JSF2.2 / Primefaces 4 you no longer need to specify p:ajax manually, all ajax-enabled components can update other components directly. Take a look here: http://www.primefaces.org/showcase-labs/ui/pprCounter.jsf – Matt Nov 25 '13 at 12:36
  • Ah, ok. So I tried `` but the result is the same. (For attribute `update` I also tried `@form`, `slot1 slot2` and `panel1 panel2`.) – jpstrube Nov 25 '13 at 12:44
  • Ahhh I remember now having a similar problem a while back. It's to do with the lifecycle where 'form' is involved (essentially the form submits itself or something and refreshes the page incorrectly). I don't have my code with me to see exactly how I fixed it, but there are three things you can try off the top of my head: 1. Add "id='myform'" to the form tag and get your update to update 'myform', 2. create a new form, and have 'grid' and 'button' in different forms, 3. wrap your form in a panel and update the whole panel. If none of these work, I will dig out my old code this evening! – Matt Nov 25 '13 at 12:52
  • Thanks a lot! I tried all three tips but none worked :( It would be great if you could find your solution... – jpstrube Nov 25 '13 at 13:08
  • Ok I'll see what I can dig out. Another theory is that the actual component placeholders are rendered in a different lifecycle phase to the 'binding'. I know it's not as neat but you could try swapping the fields inside each panel, rather than swapping whole panels? – Matt Nov 25 '13 at 14:29
  • It works if I just swap the header texts. But that´s not an option for the use case I have in mind... – jpstrube Nov 25 '13 at 14:36
  • I managed to find a copy of my old code online, although at this stage I'm not sure how helpful it will actually be to you (I think we've covered all the bases!), but mine worked like so: form1, containing a 'gmap' with id 'mygmap', and form2, containing a commandButton with an actionListener that did lots of changes to the underlying model, and then used the syntax -- update=":form1:mygmap" -- to do the ajax update and refresh the screen. I'm afraid I've now pretty much reached the limit of my knowledge on the subject :( – Matt Nov 25 '13 at 15:42
  • (you could try adding immediate="true" to your commandButton?) – Matt Nov 25 '13 at 15:46
  • *"in JSF2.2 / Primefaces 4 you no longer need to specify p:ajax manually"* This is not true. This was already the case in PF3 and applies to `UICommand` components only. The `` is still needed in `UIInput` components. – BalusC Nov 25 '13 at 15:49
0

Why not removing the 'event' parameter from the onSwitchPanels() method? you are not using it any way. You could fire it by

<p:commandButton id="button2" value="Switch Panels" action="#{test.onSwitchPanels}" update="grid" />
yannicuLar
  • 2,903
  • 3
  • 29
  • 48
0

I found a solution adopting this example: http://www.wobblycogs.co.uk/index.php/computing/jee/70-dynamic-dashboard-with-primefaces-part-2

The main point is to create the components programmatically. In the JSF page I just write something like <p:panelGrid id="grid" binding="#{gridBacker.grid}"/> and use the bean to create the rows, columns and panels using Application.createComponent(). The Panels are than added again after the event.

So of course BalusC was right that the action has to take place in the model.

jpstrube
  • 775
  • 1
  • 11
  • 27