0

I have a table and in each row user can click on a link which triggers book availability check. So I have a commandLink with action and it works, but this action is executed every time user clicks on a link. I want it to be available only once. Also I don't want to hide link after click as it has onclick code which hides and shows details. Is it possible to remove action from commandlink after executing action?

J.Doe
  • 287
  • 1
  • 5
  • 16

1 Answers1

0

The answer covered in Is it possible to use EL conditional operator in action attribute? is one of the ways that you can solve this. With that being said, since the release of JSF 2.2, there are also other alternatives. While removing the action attribute in JSF is problematic (it can be done with some trickery) - another solution is to use actionListeners together with an f:event binding that is connected to the preValidate event. This allows you to remove any of the connected actionListeners whenever you choose to do so.

Here is a complete solution with an event listener that modifies the component prior to it being processed for the view. Basically, you can do something like this;

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Dynamic action demo</title>
    </h:head>
    <h:body>
        <h:form>
            <h:dataTable var="row" value="#{removeActionBackingBean.rows}">
                <h:column>#{row.primaryColumn}</h:column>
                <h:column>#{row.hasBeenClicked}</h:column>
                <h:column>
                    <h:commandButton actionListener="#{removeActionBackingBean.onPressed(row)}">
                        <f:attribute name="clicked" value="#{row.hasBeenClicked}"/>
                        <f:event listener="#{removeActionBackingBean.onModify}" type="preValidate" />
                        <f:ajax event="click" render="@form" />
                    </h:commandButton>
                </h:column>
            </h:dataTable>
        </h:form>
    </h:body>
</html>

For the backing bean, here is a solution with a complete model (using Lombok);

@Data
@Named
@ViewScoped
public class RemoveActionBackingBean implements Serializable {
    private List<Row> rows;

    @PostConstruct
    private void init() {
         rows = new ArrayList<>();

         for (int i = 0; i < 10; i++) {
             rows.add(new Row(RandomStringUtils.randomAscii(10)));
         }
    }

    public void onPressed(Row row) {
        row.hasBeenClicked = true;
        System.out.println(String.format("'%s' has been pressed!", row.primaryColumn));
    }

    public void onModify(ComponentSystemEvent event) {
        final boolean isRowClicked = (boolean) event.getComponent().getAttributes().get("clicked");

        if (isRowClicked) {
            for (ActionListener al : ((UICommand) event.getComponent()).getActionListeners()) {
                ((UICommand) event.getComponent()).removeActionListener(al);
            }
        }
    }

    @Data
    @RequiredArgsConstructor
    public class Row {
        private @NonNull String primaryColumn;
        private boolean hasBeenClicked;
    }
}

The key sections to look at is f:event and the onModify() method binding. As you can see, we simply check if a certain "row" is considered as clicked - if this is the case, we clear all the actionListeners currently defined on the component. Effectively, there will be no actionEvent called when the button is pressed.

While the above solution modifies the actionListeners of a button, it can be adopted and used for other types of components and when you want to modify certain attributes of a component based on some condition - so it's extremely useful to know this trick.

Adam Waldenberg
  • 2,271
  • 9
  • 26