a4j:commandLink not working
jeremygood Mar 30, 2009 4:35 PMI have an h:selectOneMenu which displays a list of coupons. Each coupon has an active flag. By default the combo box displays only active coupons. A commandLink button allows the user to show/hide the inactive coupons. The command link calls an action that recreates the SelectItemGroup list with/without inactive coupons.
This code works perfectly when I use an <h:commandLink>, but if I change it to <a4j:commandLink>, the action does not get called. As a matter of fact, it appears that nothing happens at all, except that a "#" is added to the end of the URL.
My <a4j:commandLink>s did work at one time, so I'm wondering if I did something to the configuration to disable AJAX support? I see no error messages of any kind. I've stepped through the code in the debugger and added liberal debug messages. It would appear that little to nothing actually happens when the a4j:commandLink is clicked.
Potentially supporting, but possibly tangential information to help diagnose my problem: on another view, I was trying to get live validation to work. I copied the <a4j:support> code directly from Seam in Action. But, the ajax validation just doesn't happen. Validation DOES occur when the form is submitted. This reinforces my opinion that perhaps I have accidentally configurationally disabled AJAX support.
I have spent between 2 and 4 hours on each of three different days researching this issue, reading many, many forum posts about similar, but seemingly different problems. I even created a skeleton form just to see if I could get the ajax commandLink to work isolated from the rest of my code, but I can't make a dent in this problem.
I saw many posts describing how the ajax commandLink doesn't work inside a data table control, but I DO NOT have a data table control in my view. What might or might not be of interest is that I noticed that my command links were not working shortly after I experimented with changing my selectOneMenu into a dataTable. I didn't like it and changed it back. I can't be sure that the commandLinks stopped working at that exact point, because it's not a part of the interface that I was exercising on a regular basis. But it does seem like enough of a coincidence to mention. I have searched through the entire workspace for any reference to dataTable and there are none.
In the view code below, there are two commandLinks, but only one of them is visible at any given time, due to the render attribute.
View Snippet:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:s="http://jboss.com/products/seam/taglib" xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich" template="template.xhtml"> .... <a:form id="contentForm"> <div id="selectPanel"> <h:outputLabel id="couponLabel" for="couponSelect">Coupons:</h:outputLabel> <h:selectOneMenu id="couponSelect" value="#{selectedCoupon.selectedCouponId}"> <f:selectItems value="#{couponSelections}" /> </h:selectOneMenu> <a4j:commandLink id="hideInactiveButton" styleClass="smallButton" action="#{couponList.activeOnlyOn}" reRender="selectPanel,hideInactiveButton,showInactiveButton,couponSelect" rendered="#{!activeCouponsOnly}" value="Hide Inactive" /> <a4j::commandLink id="showInactiveButton" styleClass="smallButton" action="#{couponList.activeOnlyOff}" reRender="selectPanel,hideInactiveButton,showInactiveButton,couponSelect" rendered="#{activeCouponsOnly}" value="Show Inactive" /> </div> </a:form> ...
Class Snippet for CouponListBean (note that the coupon list is grouped by campaign, so there's a lot of extraneous work that goes into building the combo box list):
@Stateful @Name("couponList") @Scope(value=CONVERSATION) @Restrict("#{identity.loggedIn}") public class CouponListBean implements CouponList { @Logger private Log log; @PersistenceContext(type=EXTENDED) private EntityManager entityManager; @In(required=false) @Out(required=false) private List<Coupon> coupons; @In(required=false) @Out private List<SelectItemGroup> couponSelections; @In(required=false) @Out private List<Coupon> noCampaignCoupons; @In(required=false) @Out private Map<Campaign, List<Coupon>> campaignCoupons; @In(required=false) @Out private Boolean activeCouponsOnly; // called only on inital request, not on postbacks public void load() { activeCouponsOnly = new Boolean(true); // initialize bijected context value getCouponSelections(); } @SuppressWarnings("unchecked") @Factory public void getCouponSelections() { if(coupons == null) { coupons = entityManager.createQuery( "select distinct coupon " + "from Coupon coupon " + "left join fetch coupon.definer " + "order by coupon.startDate, coupon.shortName") .getResultList(); if(coupons == null) coupons = new ArrayList<Coupon>(); } if(coupons.size() > 0) { // there is at least one coupon SelectItemGroup _itemGroup = null; Coupon _coupon = null; Campaign _campaign = null; couponSelections = new ArrayList<SelectItemGroup>(); if( noCampaignCoupons == null ) { // Create two lists: one is a list of coupons not affiliated with any campaign, // and the second is a map of campaigns to the coupons affiliated with the campaign. noCampaignCoupons = new ArrayList<Coupon>(); campaignCoupons = new HashMap<Campaign, List<Coupon>>(); Iterator couponIter = coupons.iterator(); while(couponIter.hasNext()) { _coupon = (Coupon)couponIter.next(); if(_coupon.getCampaign() == null) { // add item to null campaign list noCampaignCoupons.add(_coupon); } else { // coupon is part of a campaign _campaign = _coupon.getCampaign(); log.info("Campaign: name: " +_campaign.getName()); if( campaignCoupons.containsKey(_campaign) ) { // campaign already in list, add coupon to it campaignCoupons.get(_campaign).add(_coupon); } else { // new campaign: add it to list List<Coupon> couponVector = new Vector<Coupon>(); couponVector.add( _coupon ); campaignCoupons.put( _campaign, couponVector ); } } } } // Now build the SelectItemGroups for the items, no-campaign coupons first. // Include inactive coupons if this.activeOnly is false. ArrayList<SelectItem> _items = new ArrayList<SelectItem>(); SelectItem[] _itemArray = new SelectItem[1]; _itemArray[0] = new SelectItem("0", "** CREATE NEW COUPON **", "", false); _itemGroup = new SelectItemGroup( "", "", false, _itemArray ); couponSelections.add(_itemGroup); // add the create coupon group Iterator noCampaignCouponsIter = noCampaignCoupons.iterator(); while(noCampaignCouponsIter.hasNext()) { _coupon = (Coupon)noCampaignCouponsIter.next(); if ( !(activeCouponsOnly.booleanValue()) || _coupon.isActiveExtended()) { _items.add( new SelectItem( _coupon.getId(), _coupon.getShortNameWithInactiveSuffix(), _coupon.getLongName(), false) ); } } if( _items.size() > 0 ) { _itemArray = new SelectItem[_items.size()]; _itemArray = _items.toArray(_itemArray); _itemGroup = new SelectItemGroup( "No Campaign Association", "Coupons not affiliated with a campaign.", false, _itemArray ); couponSelections.add(_itemGroup); // add the no-campaign group } Iterator<Campaign> campaignIter = campaignCoupons.keySet().iterator(); while(campaignIter.hasNext()) { _campaign = campaignIter.next(); List<Coupon> _couponList = campaignCoupons.get(_campaign); _items = new ArrayList<SelectItem>(); Iterator _couponsIter = _couponList.iterator(); while(_couponsIter.hasNext()) { _coupon = (Coupon)_couponsIter.next(); if ( !(activeCouponsOnly.booleanValue()) || _coupon.isActiveExtended() ) { _items.add( new SelectItem( _coupon.getId(), _coupon.getShortNameWithInactiveSuffix(), _coupon.getLongName(), false) ); } } if( _items.size() > 0 ) { _itemArray = new SelectItem[_items.size()]; _itemArray = _items.toArray(_itemArray); _itemGroup = new SelectItemGroup(_campaign.getName(), _campaign.getDescription(), false, _itemArray ); couponSelections.add(_itemGroup); } } } } public void activeOnlyOff() { activeCouponsOnly = new Boolean(false); getCouponSelections(); // force re-make of list } public void activeOnlyOn() { activeCouponsOnly = new Boolean(true); getCouponSelections(); // force re-make of list } @Remove @Destroy public void destroy() { } }
Class Snippet for SelectedCouponBean:
@Stateful @Name("selectedCoupon") @Scope(value=CONVERSATION) @AutoCreate @Restrict("#{identity.loggedIn}") public class SelectedCouponBean implements SelectedCoupon { @Logger private Log log; @PersistenceContext(type=EXTENDED) private EntityManager entityManager; @In(required= false, create=true) @Out private Long selectedCouponId; @Out @SuppressWarnings("unused") private Long selectedCouponCampaignId = 0L; public Long getSelectedCouponId() { this.selectedCouponId = (this.selectedCouponId == null) ? 0L : this.selectedCouponId; return(this.selectedCouponId); } public void setSelectedCouponId(Long selectedCouponId) { this.selectedCouponId = selectedCouponId; // outjected Coupon _coupon = entityManager.find(Coupon.class, selectedCouponId); // Also set the campaign id for the campaign associated with the selected coupon. if (_coupon == null || _coupon.getCampaign() == null) { // NEW coupon or existing with no campaign selectedCouponCampaignId = -1L; // No campaign assigned (outjected) } else { // existing coupon, existing campaign selectedCouponCampaignId = _coupon.getCampaign().getId(); //outjected } } @Remove @Destroy public void destroy() { } }
Thanks very much for any help you can give me. Let me know if you need to see any other code or configuration files. I'm using Seam 2.1.1.GA and JBoss 4.2.3.GA.
Jeremy Goodell