4

Primefaces 4.0

I need to reset the initial disable-state of components contained in a p:dialog. The following simplified example shows the problem:

XTML:

<p:dialog header="header" widgetVar="dialog" appendTo="@(body)"
          modal="true" resizable="false">
    <h:form id="form">
         <p:inputText value="#{bean.text}" id="text" />
         <p:commandButton value="Disable InputText" 
                          action="#{bean.disableInputText}" />
         <p:commandButton value="Cancel"
                                 action="#{bean.cancelDialog}"
                                 process="@this"
                                 update="@form" immediate="true">
            <p:resetInput target="@form"/>
        </p:commandButton>
    </h:form>
 </p:dialog>

ManagedBean:

 @ViewScoped
 public class Bean {
      public void disableText() {
         final FacesContext context = FacesContext.getCurrentInstance();
         final UIViewRoot root = context.getViewRoot();
         final UIComponent component = root.findComponent(":text");
         if (uiComponent instanceof InputText) {
             ((InputText) uiComponent).setDisabled(true);
         }
      }

      public void cancel() {
           // reset disable-state of the disable-state of all components in a generic way.
      }
 }

While using the dialog the p:inputText element can be disabled. If the dialog was canceled and opened again, inputText should not disabled. The initial state should have been restored. Please note that this example is simplified and i am looking for a general solution that also works with a formular with 10+ input elements.

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
  • I don't see anything in here for enabling/disabling the inputtext so it is hard to help because it is not fully clear what you mean – Kukeltje Jan 28 '19 at 08:36
  • What are you trying here? Some form of binding but in the end not? (I still see nothing about real disablign). Why not user a 'disabled' attribute on the inputText? Maybe in ccombination with http://showcase.omnifaces.org/taghandlers/massAttribute (and how is the question effectively dialog related?) – Kukeltje Jan 28 '19 at 09:06
  • i'm trying to restore the initial disable-state of all inputtext elements contained in a dialog. – Manuel Drieschmanns Jan 28 '19 at 09:30
  • Ahhh NOW I see what you do, please next time always, always, always create an [mcve] when asking questions. And why do you do it like this? Totally not very common. Personally I'd change the code to use a `disabled` attribute on the inputs, way more explicit, easily maintainable etc... But if you already have a canceled method, why not do the same in there like you do in the other method? – Kukeltje Jan 28 '19 at 10:15
  • 1
    Thanks for your advice. `public void cancel() { ...((InputText) uiComponent).setDisabled(false);}` would not be generic. Imagine a dialog which contains 10+ inputtexts. – Manuel Drieschmanns Jan 28 '19 at 11:52
  • But then setting the initial values **isn't either**.... – Kukeltje Jan 28 '19 at 12:05
  • I removed the java tag since it cannot be reproduced in a plain jdk with not libraries and just a class with a main method. – Kukeltje Jan 29 '19 at 08:27

1 Answers1

4

The general (and broadest) solution

For a general solution you can use the state saving functionality that is available in Java Server Faces. Using your code example as a base (with some minor changes to clean things up), here is an example that uses state saving and restores the previous state of the component;

<?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">
    <h:head>
        <title>Disable Test</title>
    </h:head>
    <h:body>

        <p:dialog  header="header" widgetVar="dialog" appendTo="@(body)" modal="true" resizable="false">
            <h:form id="form">
                <p:inputText value="#{disableTestBean.text}" id="text"/>
                <p:commandButton value="Disable InputText" action="#{disableTestBean.onDisable}" update="@form" />
                <p:commandButton value="Cancel" action="#{disableTestBean.onCancel}" update="@form" onsuccess="PF('dialog').hide()" />
            </h:form>
        </p:dialog>
        <button onclick="PF('dialog').show()">Open</button>
    </h:body>
</html>

To actually see the reset happening, get rid of the onsuccess attribute from the second commandButton - as it currently closes the dialog.

@Data
@Named
@ViewScoped
public class DisableTestBean implements Serializable {
    private String text;
    private Object prevState;

    private UIComponent findComponent(String where) {
        final FacesContext context = FacesContext.getCurrentInstance();
        final UIViewRoot root = context.getViewRoot();
        return (UIComponent) root.findComponent(where);
    }

    public void onDisable() {
        final InputText component = (InputText) findComponent(":form:text");
        component.setDisabled(false);
        component.setValue("");

        prevState = component.saveState(FacesContext.getCurrentInstance());
        component.setValue("meh");
        component.setDisabled(true);
    }

    public void onCancel() throws IOException {
        final InputText component = (InputText) findComponent(":form:text");
        component.restoreState(FacesContext.getCurrentInstance(), prevState);
    }
}

The example targets one specific input component. However, if you need to handle multiple components, you can easily use a loop to accomplish what you want in a general way.

As a demonstration I not only reset the disabled state in the backing bean above, but also the value (content) of the input component. This demonstrates you how you can actually reset the complete state of the component (not only a single attribute or value). So the solution is broad and very general.

The second more direct approach

The second approach is to do what @Kukeltje is hinting at in his comments above. Use the disabled= attribute on the input components and have a backing bean value that just changes the value to false when you press Cancel. This is not as general and wont work for everything else, but it will do the job in your particular use case. If you are only interested in the disabled state it's probably even the prefered way of doing it. If you want an example of that as well, I can extend this answer - just let me know.

Adam Waldenberg
  • 2,271
  • 9
  • 26
  • Why is using the other approach not as general? (not that I disagree, just curious as the specific reason/arguments). And certainly if the whole form needs to be disabled, the omnifaces `massAttribute` can be used (which internally does what you do, recursively without the need for id's). The 'other approach' does not require JSF specific code in beans/businesslogic. So in that sense it is maybe 'cleaner' ;-) – Kukeltje Jan 28 '19 at 14:11
  • regardless of which approach is more generic, the first approach can be better tested with unittests. – Manuel Drieschmanns Jan 28 '19 at 15:48
  • 1
    It's more general in the sense that it allows you to also target other properties and states of the component - not only a certain attribute. OmniFaces `o:massAttribute` would work in the OP's case, so would probably Tomahawks `t:saveState`. But both are non-standard extensions. – Adam Waldenberg Jan 28 '19 at 16:32
  • @ManuelDrieschmanns You are correct concerning tests. However, I would like to also mention that you could use something like *Graphene* (part of Arquillian) in order to make the second approach testable as well. Maybe it's just a wet dream of mine (and will remain so) - but I hope Arquillian becomes part of JakartaEE at some point ;) – Adam Waldenberg Jan 28 '19 at 16:36
  • Agreed about Arquilian and OmniFaces should become part of JakartaEE as well ;-) But seriously now... what other attributes? `readonly` is available via an attribute, as is `value` and `required` So effectively all (relevant ones) are and others can in the first solution only be changed if added in the java code, so not fully general either (or as general) and wrapping in custom tags makes this less code too). @ManuelDrieschmanns About testing, for the first one you'd need some bootstrapping/mocking too wouldn't you? (again, not against the solution, just wanting to have balanced view) – Kukeltje Jan 28 '19 at 16:58
  • @Kukeltje Yeah, you'd have to mock for the unit tests. – Manuel Drieschmanns Jan 28 '19 at 17:08
  • 1
    @Kukeltje You'd have to take a look on the Java side of the component - but to put it plainly - there are often more properties in a component than what is exposed as attributes. It's different from component framework to component framework. – Adam Waldenberg Jan 28 '19 at 17:10
  • I did, lots of times. I was not specifically asking which ones there are but which ones you'd need. And the other ones that are there, who uses them? Component developers mostly. I never needed the fields that that are not defined as attributes in the VDL (there is a reason they are not in there). Some methods, yes but for sort of creating a backing model for the ui no. And hence I did not need a mapping from the backing model to JSF code but could use the backing model directly in the view (no need for 'findComponent either'). And I created a very generic XForms based UI layer for JSF. – Kukeltje Jan 29 '19 at 11:50
  • 1
    @Kukeltje While I agree that most useful properties are exposed, the fact that all of them aren't is why the first option is a more general way of achieving this - you can actually get to the other ones as well. You also need to consider the fact that the save/restore method actually allows you to implement a general solution ... If you have 20 components on a page and want to handle/reset 3 properties (or other internal states mind you), you end up with at least 60 bindings in-page instead of a simple for loop and a few method calls. – Adam Waldenberg Jan 29 '19 at 17:52
  • The other ones have (for me in a fairly complex solution) never been needed. And developing for the eventuality is not the best approach. And regarding the 20 components to 60 attributes: No you don't, you write tags for this https://stackoverflow.com/questions/14751624/how-to-create-a-custom-facelets-tag, and if you want to do this dynamic in the 'general solution', you'd be writing code mapping the model to the JSF code you wrote in addition to adding the components in the UI. In 'my' case, I only add the tags since the mapping to the model is 'implicit'. – Kukeltje Jan 29 '19 at 19:20
  • 1
    @Kukeltje Even if you write a tag, you are still creating those bindings. Both solutions solve the problem in a slightly different manner (with the first one giving you more flexibility and access to more of the component - if you need it). It also follows the programmatic approach that the OP started with in the question. With that said, there is a reason I included both approaches in the answer. – Adam Waldenberg Jan 29 '19 at 20:20
  • 1
    @Kukeltje Also consider what happens if your component has an undefined state (each component is different and all you want to do is get back to the original/previous state). Just because you handle them the same, does not mean they actually have the same state from the start... Then a backing bean value won't even work. – Adam Waldenberg Jan 29 '19 at 20:25
  • I nowhere stated there was just *a* backingbean value... There is a value per component, a 'model' for the view – Kukeltje Jan 29 '19 at 21:19