Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute disabled, readonly, contentDisabled, rendered dynamically set fails. #908

Closed
modima65 opened this issue Feb 1, 2018 · 30 comments
Labels
couldn't reproduce Sometimes, we're reported a bug without being able to reproduce it.

Comments

@modima65
Copy link

modima65 commented Feb 1, 2018

When an input component like b:selectOneMenu, b:commandButton and others is dynamically hidden, blurred or not rendered at first page load by setting attributes like disabled, readonly, rendered, contentDisabled beforehand, the component doesn't show the value that matches one of collection items.

Case 1:
XHTML

<b:form>
...
<!-- Set param beforehand -->
<f:param name="disabledToggle" value="true"/>
...
<b:commandButton look="info" value="Enabled Menu" ajax="true" process="@this"
     actionListener="#{cartaportesController.populateMap(cartaporte)}" update"panel1">
     <f:param name="disabledToggle" value="false"/>
</b:commandButton>

<b:panelGrid id="panel1" columns="1">
     <b:selectOneMenu value="#{cartaportesController.selectedCartaporte.flete}" label="Flete:"
          readonly="#{param['disabledToggle']}">
          <f:selectItems value="#{cartaportesController.fletesConverter}"/>
          <f:converter converterId="cartaportesFletConv"/>
     </b:selectOneMenu>
</b:panelGrid>
...
</b:form

ENTITY

@Entity
@Table(name = "flete")
public class Flete implements Serializable {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id_flete")
    private int id;
    
    @Enumerated
    @Column(name = "tipo", columnDefinition = "int")
    private TipoFlete tipo;
    
    @Enumerated
    @Column(name = "status", columnDefinition = "int")
    private StatusFlete status;
    
    @Enumerated
    @Column(name = "finale", columnDefinition = "int")
    private FinaleFlete finale;
    
    @Column(name = "combustible")
    private double combustible;
    
    @Column(name = "viaticos")
    private double viaticos;

    @NotNull
    @ManyToOne
    @JoinColumn(name="id_transportista")
    private Transportista transportista;

    @NotNull
    @ManyToOne
    @JoinColumn(name="id_ruta")
    private Ruta ruta;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated")
    private Date updated;

    public Flete() {
    }

    // Getters - Setters...
    
    @Override
    public String toString() {
        return String.format("%1$05d", id) +  " : " + tipo.getTipoFlete() + " : " + ruta;
    }
}

BEAN SELECTITEMS MAP POPULATION METHOD

@Named(value = "cartaportesController")
@ViewScoped
public class CartaportesController implements Serializable {
    private Map<String, Flete> fletesConverter;
...
    public void populateMap(Cartaporte cartaporte) {
        Flete flete = selectedCartaporte.getFlete();
        
        fletesConverter = new TreeMap<>();
        if (flete == null || flete.getStatus() == StatusFlete.PLANEACION) {
             StatusFlete[] asf = {StatusFlete.PLANEACION};        
             fletesConverter.put("- Selecciona -", null);
             for (Flete f : new FleteDaoImpl().findByStatus(asf)) {
                 fletesConverter.put(f.toString(), f);
             }
        } else {
            fletesConverter.put(flete.toString(), flete);
        }
     }

    public Map<String, Flete> getFletesConverter() {
        return fletesConverter;
    }
}

SIMPLE CONVERTER

@FacesConverter(value = "cartaportesFletConv")
public class CartaportesFletConv implements Converter, Serializable {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null) {
            return null;
        }

        return (Flete) context.getApplication().evaluateExpressionGet(context, "#{cartaportesController}", CartaportesController.class)
                .getFletesConverter().get(value);
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return (value instanceof Flete) ? ((Flete) value).toString() : "";
    }
}

In this requirement, the component value #{cartaportesController.selectedCartaporte.flete} MUST match one of the seletedItems in collection, but it fails to do so because readonly attribute avoids it in some manner. If we change from b:selectOneMenu to JSF h:selectOneMenu or remove the readonly attribute, the value matches fine. The converter plays important part, so I think the menu component doesn't apply the converter when this component is set readonly beforehand.

Case 2:
XHTML

<!-- Set param beforehand -->
<f:param name="disabledToggle" value="true"/>
...
<b:commandButton look="info" value="Open Modal" ajax="true" process="@this"
     actionListener="#{cartaportesController.onView(cartaporte)}" update="@(.modalAgregar)"
     oncomplete="$('.modalAgregar').modal('show')">
     <f:param name="disabledToggle" value="false"/>
</b:commandButton>

<b:modal title="Coordinación" class="modalAgregar">
     <b:panelGrid id="panel2" columns="1">
...
          <b:commandButton value="Agregar" look="success" ajax="true" process="panel2"
               actionListener="#{cartaportesController.addMercancia()}" update="panel2"
               oncomplete="if(!validationFailed) $('.modalAgregar').modal('hide');"
               disabled="#{param['disabledToggle']}"/>
     </b:panelGrid>
</b:modal>

BEAN LISTENERS

@Named(value = "cartaportesController")
@ViewScoped
public class CartaportesController implements Serializable {
    private Cartaporte selectedCartaporte;
...
    public void onView(Cartaporte cartaporte) {
        selectedCartaporte = dao.findById(cartaporte.getId());
        ...
    }
    public void addMercancia() {
        if (selectedCartaporte.getCargas() == null) {
            selectedCartaporte.setCargas(new ArrayList<>());
        }
        selectedCartaporte.addCarga(selectedCarga);
    }
}

In this scenario, b:commandButton Agregar was set to disabled beforehand. User then update param disabledToggle to enable b:commandButton, so as to let him/her apply changes in modal, however the listener is never called because the disabled attribute kept component from registering the listener (I suppose), so it is already spoiled and won't work, even once enabled. Now, if we change from b:commandButton to JSF h:commandButton or remove the disabled attribute the listener works fine.

In short, my project was plagued with this bug since I use consistently disabled, readonly, rendered and the brand new BootsFaces contentDisabled attributes. Sadly, I had to rearrange and redesign my pages once I noticed this behavior.

Thanks.

@stephanrauh
Copy link
Collaborator

I don't know if it's got anything to do with your bug, but the update attributes have a space between the dot and the CSS class name. This might work with jQuery, but it doesn't work with the AJAX engine of BootsFaces because space is already the standard separator between multiple ids.

@modima65
Copy link
Author

modima65 commented Feb 1, 2018

Hi, thanks; it's a typo. I edited my post, it doesn't affect the bug report.

@stephanrauh
Copy link
Collaborator

THX! Can you provide us some of the bean code, such as the converter? You know, it's always a bit tiresome to reverse-engineer beans matching the bug description.

@modima65
Copy link
Author

modima65 commented Feb 1, 2018

Done!

@stephanrauh
Copy link
Collaborator

OK, now I haven't got any excuse left to start working :).

@modima65
Copy link
Author

modima65 commented Feb 1, 2018

I've added converter Map population method code.

@chongma
Copy link
Collaborator

chongma commented Feb 1, 2018

I can't see the full code so I don't know exactly what it does but you are using actionlistener instead of action?

@modima65
Copy link
Author

modima65 commented Feb 1, 2018

I apologizing for giving you the information in chapters. I've updated my post. In both cases, I'm using actionListeners since both scenarios operate inside their respective b:form and everything occurs inside the same page. No navigation, so no action attribute, just partial updates.

@chongma
Copy link
Collaborator

chongma commented Feb 1, 2018

You should still use an action. But just return null from the function. Action listeners are for preparing other things before executing an action

@stephanrauh
Copy link
Collaborator

@chongma I never thought about this, but like @modima65, I always use an actionListener if I don't want to navigate. If I'm using AJAX, I employ yet another approach: I prefer to call the method using onclick="ajax:myMethod() (or the corresponding f:event handler, if my project insists on not using BootsFaces).

@stephanrauh
Copy link
Collaborator

@modima65 You don't have to apologize - I know how difficult and annoying it is to simplify one's application and to remove every company specific code from it without removing the bug, too. I'll try to reproduce your bug tomorrow or Saturday. In the meantime, I'm interested in the AJAX response to the AJAX request activating the command button. Are you familiar with your browser's developer tools? If so, open them (F12 or CTRL-CMD-I on a Mac), inspect the AJAX request in the network tab and send me the AJAX request.

Thanks in advance!
Stephan

@modima65
Copy link
Author

modima65 commented Feb 1, 2018

Well, in case 1, I'm using the listener to prepare the selectItems Map collection to show data updated on panelGrid below. actionListener does its job: the panelGrid updates with fresh data from bean, except that b:selectOneMenu value is failing to detect identical item in collection due to being set disabled or readonly true beforehand. If I remove at all the readonly attribute or set it to false (<f:param name="disabledToggle" value="false"/>) on page loading or use ugly JSF h: selectOneMenu, the value is detected and shown. If I use onclick="ajax:myMethod()" as @stephanrauh suggests the problem persists. I've been using PrimeFaces since version 3.5 in two projects and I've never found such intriguing behavior with readonly, rendered and disabled attributes as with BootsFaces. However BootsFaces is very nice and simple to layout! I love it! I'm tired of complex components. This bug apart......

@stephanrauh
Copy link
Collaborator

@modima65 Thanks for the kind words! I'm sure we'll solve the bug. Although I'm a bit puzzled - looking at our source codes, I just don't get it. Case 2 might be a problem with the AJAX response, but case 1 beats me. I just hope that doesn't mean a lengthy debugging session...

(But then - aren't these debugging sessions the real fun?) :)

@chongma
Copy link
Collaborator

chongma commented Feb 1, 2018

https://stackoverflow.com/questions/3909267/differences-between-action-and-actionlistener this answer explains that listeners trigger before the action which is where the business logic should be. It is not necessary to navigate from an action. Just return null

@chongma
Copy link
Collaborator

chongma commented Feb 1, 2018

Is there a sample application on GitHub that reproduces this problem? I am interested to take a look

@chongma
Copy link
Collaborator

chongma commented Feb 2, 2018

example backing bean:

@Named
@ViewScoped
public class CartaportesController implements Serializable {
  private boolean disabledToggle;
  private Cartaporte selectedCartaporte;
  public String onView(
    return null;
  }
  public boolean getDisabledToggle() {
    return this.disabledToggle;
  }
  public void setDisabledToggle(boolean disabledToggle) {
    this.disabledToggle = disabledToggle;
  }
  public Cartaporte getSelectedCartaporte() {
    return this.selectedCartaporte;
  }
  public void setSelectedCartaporte(boolean selectedCartaporte) {
    this.selectedCartaporte = selectedCartaporte;
  }
}

example xhtml:

<b:commandButton look="info" value="Open Modal" ajax="true" process="@this"
     action="#{cartaportesController.onView}" update="@(.modalAgregar)"
     oncomplete="$('.modalAgregar').modal('show')">
     <f:setPropertyActionListener target="#{cartaportesController.selectedCartaporte}" value="#{cartaporte}"/>
    <f:setPropertyActionListener target="#{cartaportesController.disabledToggle}" value="#{false}"/>
</b:commandButton>

<b:modal title="Coordinación" class="modalAgregar">
     <b:panelGrid id="panel2" columns="1">
...
          <b:commandButton value="Agregar" look="success" ajax="true" process="panel2"
               actionListener="#{cartaportesController.addMercancia()}" update="panel2"
               oncomplete="if(!validationFailed) $('.modalAgregar').modal('hide');"
               disabled="#{cartaportesController.disabledToggle}"/>
     </b:panelGrid>
</b:modal>

@modima65
Copy link
Author

modima65 commented Feb 2, 2018

Hi @chongma ! Thank you for your time. As I commented in my last intervention, I use actionListener to prepare a Map whose items will be used by f:selectItems component or to use the selected row (cartaporte) in backing bean. I don't see such preparation in your bean version.

@modima65
Copy link
Author

modima65 commented Feb 2, 2018

Sorry, I don't know what I saw in your post, I edited mine completely.
To the point:
Yours: action="#{cartaportesController.onView}"
Mine: actionListener="#{cartaportesController.onView(cartaporte)}"

Question: where do you send cartaporte to be used in bean? How do you prepare a Map for f:selectItems as in Case 1?
Thanks.

@modima65
Copy link
Author

modima65 commented Feb 2, 2018

It seems to me redundant, given that I can use actionListener in a line to do what you did with three attributes and three lines. However, I give it a try and if it works, well, that's the way to go. Thank you!!!!

@chongma
Copy link
Collaborator

chongma commented Feb 2, 2018

<f:setPropertyActionListener target="#{cartaportesController.selectedCartaporte}" value="#{cartaporte}"/> sets the selectedCartaporte property on the CartaportesController bean with the current value of #{cartaporte}. in onView you could set your new Map based on the value of selectedCartaporte?

@Named
@ViewScoped
public class CartaportesController implements Serializable {
  private boolean disabledToggle;
  private Cartaporte selectedCartaporte;
  public String onView(
    // here you can access the value of selectedCartaporte
    // disabledToggle will also have been updated
    system.out.println(getSelectedCartaporte());
    cartaportesController.populateMap(getSelectedCartaporte()); //?
    return null;
  }
  // getters and setters
}

@modima65
Copy link
Author

modima65 commented Feb 2, 2018

Ok. I understand. You obliterate actionListener purpose. All my life I've been using actionListener the way I showed it before. I'm implementing your recommendations. I read somewhere (Stackoverflow.com) that one of JSF 2.2's improvements was to let actionListener pass parameters to backing bean. For what?

Thank you again for your time. I'll be back soon with my results.

@chongma
Copy link
Collaborator

chongma commented Feb 3, 2018

also i removed the f:param stuff because i am not aware of it being able to pass parameters in that way. instead i am storing disabledToggle in the bean. also if you want to initialise the disabledToggle to true you need to initialise it in the bean, using a @PostConstruct (although IMHO f:metadata f:viewAction is way superior) e.g.

@Named
@ViewScoped
public class CartaportesController implements Serializable {
  private boolean disabledToggle;
  private Cartaporte selectedCartaporte;
  @PostConstruct
  public void onload() {
    setDisabledToggle(true);
  }
  public String onView(
    ...
  }
  // getters and setters
}

@stephanrauh
Copy link
Collaborator

@chongma What are you currently doing? Your post read like you've managed to reproduce the bug. Have you?

@chongma
Copy link
Collaborator

chongma commented Feb 3, 2018

@stephanrauh no i am just copying the code and changing it to the way that i would approach the problem. i am not really sure what the problem is. but i was unsure that f:param could be declared directly in the root of a b:form. it would be good to have a reproducer to see what is causing the problem.

@stephanrauh
Copy link
Collaborator

@modima65 I've been trying to reproduce case 1, but I haven't been successful yet. Would you mind to have a look at our showcase and to examine the page http://127.0.0.1:8080/BootsFacesWeb/issues/issue908.jsf? The showcase is a simple Maven project, so it should run without further ado. You only have to correct the dependency on BootsFaces in the pom.xml, which already points to the developer SNAPSHOT of BootsFaces 1.2.1.

Can you spot any difference to your project? Apart from being more simple, of course :).

@stephanrauh
Copy link
Collaborator

@modima65 I've just uploaded BootsFaces-1.2.1-SNAPSHOT containing two bugfixes. I still can't reproduce your bug, so I don't believe it's fixed by now but would you mind to have a look?

See #369 on how to get the sneak preview version of BootsFaces.

@modima65
Copy link
Author

modima65 commented Feb 5, 2018

Hi. As I commented in my first post: I had to get rid of almost all conditional rendered, readonly, and disabled attributes in b:selecteOneMenu and b:commandButton from my project. Moreover, I've changed actionListener in favor of action in communication between views and beans, and I now use f:setAttributeListener instead of f:param ('cause what I've learn now is that this facet is for other purpose). So my project has changed a lot from my first post.

Now, one thing I haven't been able to make work correctly yet, after all this modifications, is the line commented in the code fragments below and this STILL has to do with rendered, readonly, and disabled attributes.
The requirement: selectedCartaporte is an object modeling a legal document (in México) for goods transportation that may or may not have a flete property. A flete is an object modeling a freight, an event of transportation of goods by road made with a specific truck, operator, dimensions, weight, etc. If the selected cartaporte already has a flete attached to it, it must be shown selected in the list, along with other fletes to allow user to change it or modify data. If carta porte has not been assigned a flete yet, flete property is null and user is allow to choose from a list of fletes the one available that fits the job well; a label inviting the user to choose must be shown as first option in the list. Also, a flete may have a label (nomenclature), but no object yet, so key/value as String/null is possible for the user to choose.

Bean

@Named(value = "cartaportesController")
@ViewScoped
public class CartaportesController implements Serializable {
    private Cartaporte selectedCartaporte;
    private Map<String, Flete> fletesConverter;
    
    private boolean allowEdit;
    private boolean allowDelete;
    private boolean disableEdition;
    private boolean disableDeletion;
...
    public void onView() {
        selectedCartaporte = dao.mergeCartaporte(selectedCartaporte);
        Flete flete = selectedCartaporte.getFlete();

        allowEdit = false;
        allowDelete = false;
        disableEdition = true; // ******* Notice this value as default at modal show.
        disableDeletion = true;
        
        fletesConverter = new TreeMap<>();
        if (flete == null || flete.getStatus() == StatusFlete.PLANEACION) {
            allowEdit = true;
            allowDelete = true;
            
            StatusFlete[] asf = {StatusFlete.PLANEACION};
            // ****** key/value as String/null for first option item **********
            // user must select a flete and attach it to a cartaporte.
            // *************************************************************
            fletesConverter.put("- Please, select... -", null);
            for (Flete f : new FleteDaoImpl().findByStatus(asf)) {
                fletesConverter.put(f.toString(), f);
            }
        } else {
                fletesConverter.put(flete.toString(), flete);
        }
    }
...

    // getters and setters for every property
}

XHTML

<b:form id="formCarTable" >
    <b:growl id="growlMessages" globalOnly="true"/>

    <b:dataTable id="tableCar" value="#{cartaportesController.cartaportesData}" var="cartaporte">
        <!-- .... -->
        <b:dataTableColumn label="Vew..." searchable="false" orderable="false" contentStyle="text-align: center;">
            <b:commandButton look="info" icon="info-sign" size="sm" ajax="true" process="@this" action="#{cartaportesController.onView}" update="@(.modalCarEdit)" 
                             oncomplete="$('.modalCarEdit').modal('show')">
                <f:setPropertyActionListener target="#{cartaportesController.selectedCartaporte}" value="#{cartaporte}"/>
            </b:commandButton>
        </b:dataTableColumn>
    </b:dataTable>

    <b:modal title="Coordinación : Carta Porte : #{cartaportesController.selectedCartaporte.id}" size="modal-lg" class="modalCarEdit" closable="false">
        <b:label value="FLETE" severity="primary" col-md="12" style="margin-bottom: 10px;"/>

        <b:panelGrid id="panelStatusE" colSpans="6, 3, 3">
            <b:selectOneMenu value="#{cartaportesController.selectedCartaporte.flete}" label-col-md="8" label="Flete:" ajax="true" process="@this" 
                             update="textStatusEdit textFinaleEdit" disabled="#{cartaportesController.disableEdition}">
                <!-- ************************************************************************** -->
                <!-- I could easily use here:
                <f:selectItem itemLabel="Please, select..." itemValue="" noSelectionOption="true"/>
                but I wanted to let it this way in the bug analysis interest. -->
                <!-- ************************************************************************** -->
                <f:selectItems value="#{cartaportesController.fletesConverter}"/>
                <f:converter converterId="cartaportesFletConv"/>
            </b:selectOneMenu>
            <b:inputText id="textStatusEdit" value="#{cartaportesController.selectedCartaporte.flete.status.estadoFlete}" label-col-md="8" label="Estado:" readonly="true"/>
            <b:inputText id="textFinaleEdit" value="#{cartaportesController.selectedCartaporte.flete.finale.finaleFlete}" label-col-md="8" label="Situación:" readonly="true"/>
        </b:panelGrid>

        <b:label value="CARTA PORTE" severity="success" col-md="12" style="margin-bottom: 10px;margin-top: 10px;"/>
        <b:panelGrid id="panelCartaporteE" columns="3">
            <!-- ********************************************** -->
            <!-- more data to edit/show from selectedCartaporte -->
            <!-- ********************************************** -->
            <b:fetchBeanInfos/>
        </b:panelGrid>

        <f:facet name="footer">
            <b:button value="Cancel" smallScreen="one-fourth" dismiss="modal" onclick="return false;"/>

            <b:commandButton id="buttonCarEdit" value="Modify" look="#{cartaportesController.disableEdition ? 'default' : 'warning'}" smallScreen="one-fourth" ajax="true" 
                             process="panelStatusE panelCartaporteE panelMercanciasE" action="#{cartaportesController.edit}" update="panelStatusE panelCartaporteE panelMercanciasE tableCar growlMessages" 
                             oncomplete="if(!validationFailed) $('.modalCarEdit').modal('hide');" 
                             disabled="#{cartaportesController.disableEdition}" style="visibility: #{cartaportesController.allowEdit ? 'visible' : 'hidden'};"/>

            <b:commandButton id="buttonCarDelete" value="Delete" look="#{cartaportesController.disableDeletion ? 'default' : 'danger'}" smallScreen="one-fourth" ajax="true" 
                             process="@this" action="#{cartaportesController.delete}" update="tableCar growlMessages" oncomplete="$('.modalCarEdit').modal('hide');" 
                             disabled="#{cartaportesController.disableDeletion}" style="visibility: #{cartaportesController.allowDelete ? 'visible' : 'hidden'};"/>

            <!-- *************************************************************** -->
            <!-- toggle that allows to modify data by enabling buttons and menus -->
            <!-- *************************************************************** -->
            <b:commandButton value="Allow" smallScreen="one-fourth" look="link" process="@this" update="panelStatusE panelCartaporteE buttonCarEdit buttonCarDelete"
                             rendered="#{cartaportesController.allowEdit}">
                <f:setPropertyActionListener target="#{cartaportesController.disableEdition}" value="#{not cartaportesController.disableEdition}"/>
                <f:setPropertyActionListener target="#{cartaportesController.disableDeletion}" value="#{not cartaportesController.disableDeletion}"/>
            </b:commandButton>
        </f:facet>
    </b:modal>
...
</b:form>

At first modal load, b:selectOneMenu value doesn't show the "- Please, select... -" label option but a blank option. However if you toggle disabled to false and update the component, the label is shown. More, if you change b:selectOneMenu to JSF f:selectOneMenu, the "- Please, select... -" label value appears fine from the beginning (disabled) in the menu. Every other aspect of this bug reporte I've been able to avoid it whether by following your kind recommendations or by rearranging components, try and error, with b:panelGrid, b:column until I find the one that works. I still believe there's something a little bit different in BootsFaces readonly, rendered and disabled attributes from the ones in plain JSF because I've never dealt with such intriguing behavior, even with my right or wrong way of coding.

@modima65
Copy link
Author

modima65 commented Feb 5, 2018

@stephanrauh I've downloaded showcase to my desktop. I'll do some tests. Thank you.

@stephanrauh
Copy link
Collaborator

¿Hay algo nuevo de tus pruebas? ¿Has encontrado alguna diferencia entra nuestro showcase y tu aplicación?

@stephanrauh
Copy link
Collaborator

Reluctantly, I'll close the issue now because I don't know how to reproduce the bug.

@stephanrauh stephanrauh added the couldn't reproduce Sometimes, we're reported a bug without being able to reproduce it. label Jun 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
couldn't reproduce Sometimes, we're reported a bug without being able to reproduce it.
Projects
None yet
Development

No branches or pull requests

3 participants