I have a shopping cart. The cart is built with a single form, and a
loop. The basic validation works, such as testing quantity for numeric
input etc. However, I have an additional requirement to display errors
on invalid line items which are part of the cart. An example of
invalid cart item may be wrong quantity (sold in lots of 10, but only
7 added to the cart). For the purposes of this discussion, assume that
quantity can only be odd. It is a business requirement that this check
is not done at the time of adding a product to the cart, but during
cart display.

On this mailing list, I found Josh Canfield's example of AttachError
mixin, which I implemented and it almost works, but with few wrinkles.
Here is my code:

@MixinAfter
public class AttachError {
        
        @Parameter(required = true, allowNull = true)
        private String message;

        @Environmental
        private ValidationTracker tracker;

        @InjectContainer
        private Field field;

        
        void setupRender() {
                if (message != null) {
                        tracker.recordError(field, message);
                }
        }
}

public class ShoppingCart extends BasePage {
        
        @Inject
        private Logger log;

        @Inject
        private ShoppingCartServiceRemote cartService;

        @Property
        private CartItemDisplayBean cartDisplayItem;
        
        @Property
        private List<CartItemDisplayBean> cartDisplayItems;
        
        @Environmental
        private ValidationTracker tracker;

        @Component(id="quantity", parameters = 
{"AttachError.message=fieldError"})
        @MixinClasses(value=AttachError.class)
        private TextField quantityField;

        @Property
        private Integer indexer;


        public String getFieldError() {
                String error = null;
                if(tracker.getError(quantityField) != null) return null;
                if(cartDisplayItem.getQuantity()%2 == 0) {
                        // simple example of preexisting error; don't allow 
even quantity
                        error = "[" + cartDisplayItem.getSku() + "] Quantity 
must be odd";
                }

                return error;
        }
        
        @OnEvent(value=EventConstants.PREPARE, component="cartForm")
        void prepareCartData() {
                cartDisplayItems = getShoppingCartForDisplay();
        }
        
        private List<CartItemDisplayBean> getShoppingCartForDisplay() {
  // retrieve shopping cart (using ShoppingCartServiceRemote EJB)
 }
}

public class CartItemDisplayBean implements Comparable<CartItemDisplayBean> {

        private int lineNumber;
        
        private String sku;
        
        private String description;
        
        private String usuallyShipsMessage;
        
        private Integer quantity;
        
        private Double price;
        
        private Double total;
        
        private boolean deleted;

        public String getSku() {
                return sku;
        }

        public void setSku(String aSku) {
                sku = aSku;
        }

        public String getDescription() {
                return description;
        }

        public void setDescription(String aDescription) {
                description = aDescription;
        }

        public String getUsuallyShipsMessage() {
                return usuallyShipsMessage;
        }

        public void setUsuallyShipsMessage(String aUsuallyShipsMessage) {
                usuallyShipsMessage = aUsuallyShipsMessage;
        }

        public Integer getQuantity() {
                return quantity;
        }

        public void setQuantity(Integer aQuantity) {
                quantity = aQuantity;
        }

        public Double getPrice() {
                return price;
        }

        public void setPrice(Double aPrice) {
                price = aPrice;
        }

        public Double getTotal() {
                return total;
        }

        public void setTotal(Double aTotal) {
                total = aTotal;
        }

        public int getLineNumber() {
                return lineNumber;
        }

        public void setLineNumber(int aLineNumber) {
                lineNumber = aLineNumber;
        }

        public boolean isDeleted() {
                return deleted;
        }

        public void setDeleted(boolean aDeleted) {
                deleted = aDeleted;
        }

        @Override
        public int compareTo(CartItemDisplayBean aAnotherBean) {
                return lineNumber - aAnotherBean.getLineNumber();
        }
}

<t:layout xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd";
t:title="${pageTitle}" xmlns:p="tapestry:parameter">
<h1>Shopping Cart</h1>
<style type="text/css">
td.kk-currency-cell {
 text-align: right;
}
</style>

<t:form t:id="cartForm">
<t:errors/>
<table border="1" width="100%" cellpadding="3">
<tr>
<th>${message:lineNumber-label}</th>
<th>${message:sku-label}</th>
<th>${message:description-label}</th>
<th>${message:usuallyShipsMsg-label}</th>
<th>${message:quantity-label}</th>
<th>${message:price-label}</th>
<th>${message:total-label}</th>
<th width="140px"/>
</tr>
<tr t:type="Loop" t:source="cartDisplayItems"
t:value="cartDisplayItem" t:index="indexer" t:formstate="ITERATION">
 <td>${cartDisplayItem.lineNumber}</td>
 <td><t:pagelink page="searchResults"
context="cartDisplayItem.sku">${cartDisplayItem.sku}</t:pagelink></td>
 <td>${cartDisplayItem.description}</td>
 <td>${cartDisplayItem.usuallyShipsMessage}</td>
 <td><t:textfield t:id="quantity" value="cartDisplayItem.quantity"
label="prop:quantityLabel" validate="required" size="2"/></td>
 <td class='kk-currency-cell'><t:myt5lib.OutputLocale
format="literal:currency" value="cartDisplayItem.price"/></td>
 <td class='kk-currency-cell'><t:myt5lib.OutputLocale
format="literal:currency" value="cartDisplayItem.total"/></td>
 <td>
  <t:submit t:id="update" value="message:update-value" context="indexer"/>
  <t:submit t:id="remove" value="message:remove-value" context="indexer"/>
 </td>
</tr>
<tr>
 <td colspan="4"></td>
 <td>Subtotal:</td>
 <td class='kk-currency-cell'><t:myt5lib.OutputLocale
format="literal:currency" value="shoppingCart.cartSubTotal"/></td>
</tr>
<tr>
 <td colspan="4"></td>
 <td>Promo:</td>
 <td class='kk-currency-cell'><t:myt5lib.OutputLocale
format="literal:currency"
value="shoppingCart.cartDiscountAmount"/></td>
</tr>
<tr>
 <td colspan="4"></td>
 <td>Total:</td>
 <td class='kk-currency-cell'><t:myt5lib.OutputLocale
format="literal:currency" value="shoppingCart.cartTotal"/></td>
</tr>
</table>
<t:errors/>
</t:form>

</t:layout>

Pre-conditional errors are properly displayed, but with some issues:

1) <t:errors/> renders differently if it's located at the top of the
form versus at the bottom. If placed at the top, it renders errors on
every other page request. Literally, clicking same "Shopping Cart"
page link will once display errors, then hide them, then display them
again and so on.. When placed at the bottom of the form, errors are
displayed every time.

2) When I enter invalid quantity (such as empty or non-numeric value)
triggering normal form validation, the additional error is correctly
displayed (if <t:errors/> is placed at the bottom). However, as with a
normal form behavior, if I click the shopping cart link page, I expect
the invalid quantity error to be reset (removed). As it turns out on
the very first request of the page that error remains, and only on the
second request it is removed. I need it to be gone right away if user
navigates away from the page or re-request it by clicking the link.

So, this brings me to my questions:

1) How can I get <t:errors/> to work equally well at the top of the
form and at the bottom? I tried delegating it using <t:delegate> to a
block that's at the bottom of the form but same incorrect behavior was
observed.

2) How can I get trivial validation errors to go away right on a
subsequent first page reqest, such as when clicking on shopping cart
link to re-render the page.

Adam

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org

Reply via email to