How to invoke a bean action method using a link? The onclick does not work
Asked Answered
S

3

7

I'm trying to implement a list of users names which can be rearranged by clicking on UP or DOWN links.

<ul>
    <ui:repeat var="user" value="#{cc.attrs.value}">
        <li>
            #{user.name}
            <h:link outcome = "user" value = "left" onclick="#{accountController.moveDown}">
                <f:param name="id" value = "${user.id}" />
            </h:link>
        </li>

    </ui:repeat>
</ul>

The problem here is that it seems that I'm not using the onclick attribute correctly. What is the proper way for doing this?


Edit: Following your advices I placed all the links in a form:

<h:form>
        <ui:repeat value="#{cc.attrs.value}" var = "user">
        <div class = "user">
            <h:commandLink id = "Link1" value = "Up" binding = "#{accountController.ommandLink}" action = "#{accountController.moveUserUp}">
                <f:attribute name = "userId" value = "#{user.id}" />
            </h:commandLink>
            <h:commandLink id = "Link2" value = "Down" binding = "#{accountController.commandLink}" action = "#{accountController.moveUserDown}">
                <f:attribute name = "userId" value = "#{user.id}" />
            </h:commandLink>
            <h:commandLink id = "Link3" value = "Delete" binding = "#{accountController.commandLink}" action = "#{accountController.deleteUser}">
                <f:attribute name = "userId" value = "#{user.id}" />
            </h:commandLink>
    </div>
</h:form>

the Managed Bean:

private UIComponent commandLink;

public void moveUserUp(){
    Integer userId = (Integer)commandLink.getAttributes().get("userId");
    System.out.println("MOVE TAB LEFT :" + userId);
}

public void moveUserDown(){
    Integer userId = (Integer)commandLink.getAttributes().get("userId");
    System.out.println("MOVE TAB RIGHT: " + userId);
}

public void deleteUser(){
    Integer userId = (Integer)commandLink.getAttributes().get("userId");
    System.out.println("DELETE TAB: " + userId);
}

public UIComponent getCommandLink() {
    return commandLink;
}

public void setCommandLink(UIComponent commandLink) {
    this.commandLink = commandLink;
}

The communication between the command Link and the managed bean is working but in the UI only the last commandLink (close action) is displayed.

Shayshaya answered 11/1, 2012 at 19:57 Comment(4)
What's the functional requirement? Moving up/down items by GET requests? Why GET? This kind of actions are absolutely not idempotent (every identical request has a different response) and should surely not be performed by GET.Corybantic
I'm used to request based frameworks and there I was separating everything in GET, POST or AJAX. This is why I chose to take this approach here. I didn't use POST mainly because there is no form there. Just o plain list and some links. What would be a good solution for this problem?Shayshaya
Even in request/restful based frameworks, non-idempotent actions are supposed to be performed by POST/PUT instead of GET. Or do you really want those move up/down actions to be bookmarkable and searchbot-indexable? (i.e. they are executed everytime when the enduser follows the action URL or a searchbot crawls the action URL). In JSF, <h:commandXxx> and <f:ajax> components perform a POST. Surely they require a form, how else can a POST request be performed in plain HTML which is what JSF generates after all?Corybantic
I updated my question. Can you take a look please? Thank you again!Shayshaya
C
14

In order to invoke a bean action method on click of a link, you need <h:commandLink>. This must be enclosed in a <h:form>.

<h:form>
    <h:commandLink ... action="#{bean.action}" />
</h:form>

public String action() {
    // ...

    return "/other.xhtml";
}

In JSF, only the attributes which interpret the EL expression as a MethodExpression can be used to declare action methods. All other attributes are interpreted as ValueExpression and they are immediately executed when the HTML output is generated by JSF. This covers the onclick attribute, whose value should actually represent a JavaScript function.

In case you actually want to use a GET link, then move the action method to a <f:viewAction> in the target page. This will be invoked on page load of the target page.

<h:link ... outcome="/other.xhtml" />

<f:metadata>
    <f:viewAction action="#{bean.onload}" />
</f:metadata>

public void onload() {
    // ...
}

See also:


Following your advices I placed all the links in a form

The communication between the command Link and the managed bean is working but in the UI only the last commandLink (close action) is displayed.

You should not bind multiple physically different components to one and same bean property. Also the <f:attribute> to pass arguments is hacky and not necessary anymore in JSF2. Assuming that you're using a Servlet 3.0 / EL 2.2 container (your question history confirms that you're using Glassfish 3), rather just pass the argument as method argument directly:

<h:commandLink id="Link1" value="Up" action="#{accountController.moveUserUp(user)}" />
<h:commandLink id="Link2" value="Down" action="#{accountController.moveUserDown(user)}" />
<h:commandLink id="Link3" value="Delete" action="#{accountController.deleteUser(user)}" />

with

public void moveUserUp(User user) {
    // ...
}

public void moveUserDown(User user) {
    // ...
}

public void deleteUser(User user) {
    // ...
}

See also:

Corybantic answered 12/1, 2012 at 11:49 Comment(4)
Thank you! It works great. It's even better than what I would have expected. I can see that the changes can be seen right away in the page. No need for me to do a refresh or anything of this kind. Why is that?Shayshaya
Because you did those changes in the POST action methods.Corybantic
Hi again! I'm trying to expand the example I gave here. Is it possible for ajax or post requests to not work properly if there is a GET parameter involved as well? I gave an action parameter which can be either view or settings. If the action parameter is set to settings than I should be able to perform some actions on the user component. The problem is that there is nothing happening when I press up / down / delete.Shayshaya
Put the bean in the view scope.Corybantic
D
3

The onclick attribute is used to invoke JavaScript function (client-side). It is be used when you want to attach a JavaScript click event hanlder.

"#{accountController.moveDown}" is a method-expression. And as the name suggests looks like accountController is a managed bean.

As the h:link doc says:

javax.el.ValueExpression (must evaluate to java.lang.String)

Can be a value expression that must ultimately evaluate to a string.

Javascript code executed when a pointer button is clicked over this element.

Update:

May be what you are looking for is h:commandLink. You can use the action attribute to invoke the backing bean method.

Dexter answered 11/1, 2012 at 20:3 Comment(4)
so is there a way to access a backing bean method in the same manner?Shayshaya
h:commandLink has attributes like action, actionListener with can be used to invoke the managedbean method.Dexter
But for command Link I would have to create a form as well. Is there no other solution for solving this? Some event management of some kind?Shayshaya
First, form (which helps JSF pass information on which method to invoke) is required to invoke the managedbean method. In your case, one workaround could be to use h:commandLink with a form (but keep it hidden) and click it programmaticaly when the h:link is clicked.Dexter
I
1
I have modified your code, let me know if this is what you are looking at achive

    <h:form>
        <a4j:outputPanel id="userList" ajaxRendered="false">
            <ui:repeat  value="#{manageUser.userList}" var="user">
                <div class="user">
                    <h:panelGrid columns="3">
                    <h:outputText value="#{user.userId} ---- #{user.userName} ---- " />

                    <a4j:commandLink id="LinkUp" value="Up" execute="@this" 
                        action="#{manageUser.moveUserUp}" limitRender="true" render="userList" >
                        <f:setPropertyActionListener value="#{user}" target="#{manageUser.user}" />
                    </a4j:commandLink>

                    <a4j:commandLink id="LinkDown" value="down"
                        action="#{manageUser.moveUserDown}" execute="@this" limitRender="true" render="userList" >
                        <f:setPropertyActionListener value="#{user}" target="#{manageUser.user}" />
                    </a4j:commandLink>
                    </h:panelGrid>
                </div>
            </ui:repeat>
        </a4j:outputPanel>      
        </h:form>

Managed Beans (ManageUser)


import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean(name="manageUser")
@ViewScoped
public class ManageUser  implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -5338764155023244249L;

    private List<UserBean> userList;

    private UserBean user;

    /**
     * @return the user
     */
    public UserBean getUser() {

        return user;
    }

    /**
     * @param user the user to set
     */
    public void setUser(UserBean user) {
        this.user = user;
    }


    /**
     * @return the userList
     */
    public List<UserBean> getUserList() {
        return userList;
    }

    /**
     * @param userList the userList to set
     */
    public void setUserList(List<UserBean> userList) {
        this.userList = userList;
    }

    public ManageUser() {
        UserBean user1= new UserBean();
        user1.setUserId("1");
        user1.setUserName("userName1");

        UserBean user2= new UserBean();
        user2.setUserId("2");
        user2.setUserName("userName2");

        UserBean user3= new UserBean();
        user3.setUserId("3");
        user3.setUserName("userName3");

        userList = new ArrayList<UserBean>();
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);
    }

    public void moveUserDown(){

        if(user !=null){
            int indexObj= userList.indexOf(user);

            if(indexObj < userList.size()-1){
                UserBean tempUser=userList.get(indexObj+1);
                userList.set(indexObj+1, user);
                userList.set(indexObj, tempUser);

            }
        }
    }

    public void moveUserUp(){

        if(user !=null){
            int indexObj= userList.indexOf(user);

            if(indexObj > 0){
                UserBean tempUser=userList.get(indexObj-1);
                userList.set(indexObj-1, user);
                userList.set(indexObj, tempUser);

            }
        }
    }

}

UserBean
import java.io.Serializable;

public class UserBean  implements Serializable {



    /**
     * 
     */
    private static final long serialVersionUID = 3820279264217591645L;

    private String userName;

    private String userId;

    /**
     * @return the userName
     */
    public String getUserName() {
        return userName;
    }

    /**
     * @param userName the userName to set
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * @return the userId
     */
    public String getUserId() {
        return userId;
    }

    /**
     * @param userId the userId to set
     */
    public void setUserId(String userId) {
        this.userId = userId;
    }

}
Indelicate answered 12/1, 2012 at 19:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.