3 Replies Latest reply on Mar 31, 2009 12:20 AM by Jeremy Goodell

    a4j:commandLink not working

    Jeremy Goodell Newbie

      I 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


        • 1. Re: a4j:commandLink not working
          Nick Belaevski Master

          Hello Jeremy,

          There are no special switches disabling AJAX support. Are there any JavaScript errors when you click command links? Can you please also post HTML generated (e.g. using http://pastebin.com/ )?

          • 2. Re: a4j:commandLink not working
            Jeremy Goodell Newbie

            Well, this is unexpected. I got your response, and decided to check for js errors and get the html source. I changed my h:commandLinks back to a4j:commandLinks and then started the app. Guess what? It works now.

            I did some major rework on the code today as a result of reading chapter 8 of Seam in Action last night. I made all my persistence context declarations EXTENDED, and I also got rid of some extraneous data structures I was maintaining, once I realized what entitiyManager.find() is for. The code I posted here is after the rework.

            I do have a backup of the code from yesterday, so I will see if I can find some time to rebuild it and check for js errors and get the html. I think it would be useful to figure out what the problem actually was.

            I am still not able to get live validation to work. I think I'll post a separate topic on that since it's obviously not related to this issue.

            Thanks much for your help. Watch this space for more info after I rebuild yesterday's code.

            • 3. Re: a4j:commandLink not working
              Jeremy Goodell Newbie

              I restored yesterday's code and the a4j:commandLink works.

              I restored code from the weekend and the a4j:commandLink works.

              I'm not crazy. These links worked for a while, then did NOTHING for several weeks, and now they work again. It appears to have nothing to do with the actual code, since code that did not work yesterday works today.

              I believe that I must have somehow changed something about the configuration or the deployment. Perhaps a file was missing in the JBoss deployment? And now, I've somehow fixed it.

              If anyone can shed any light on this, I'd be interested to hear your thoughts. Otherwise, I guess this case is closed.

              Jeremy