ValidationTracker !! Thanks for sharing regards Taha
On Thu, Mar 31, 2011 at 1:06 AM, Adam Zimowski <zimowsk...@gmail.com> wrote: > Tapestry friends! > > Your silence combined with my milestone demo today only motivated me > to stay up couple of nights in a row, fire off debugger and step into > Tapestry source to get this solved. And so, amazing what can be done > under the pressure from management and when your job possibly may be > on the line... > > In any case, my project is about 60% along the way, on target for go > live in Feb 2012. I now have a fully working advanced shopping cart. I > am sharing the code below for sake of completness of this thread, but > since my service layer runs the EJBs, the backend details are left > out. If there is interest, I can write a wiki page on advanced > shopping cart, covering the following features: > > * pre existing errors (persisted by the ejb) > * form submission errors > * multiple form design (vs. single form) > * integrated use of Josh's (Canfield) AttachError mixin > * integrated use of indexed trackers > > As a reminder, this shopping cart mimics functional behavior of our > current cart website: www.chdist.com , www.avenuesupply.ca and > www.industrialsupplies.com > > Here is the code: > > /** > * Controls display and processing of data on the shopping cart page. > Delegates > * all business logic to the service layer. > * > * @author Adam Zimowski > */ > public class ShoppingCart extends BasePage { > > // component id's used within this class > public static final String ID_CART_FORM = "cartForm"; > public static final String ID_QUANTITY_FIELD = "quantity"; > public static final String ID_REMOVE_LINK = "remove"; > > @Inject > private Logger log; > > @Inject > private CatalogServiceRemote catalogService; > > @Inject > private ShoppingCartServiceRemote cartService; > > @Property > private CartItemDisplayBean cartDisplayItem; > > @Component(id=ID_QUANTITY_FIELD, parameters = > {"AttachError.message=fieldError"}) > @MixinClasses(value=AttachError.class) > private TextField quantityField; > > @Component(parameters = {"tracker=tracker"}) > private Form cartForm; > > @Persist(PersistenceConstants.FLASH) > private Map<Integer, ValidationTracker> indexedTrackers; > > @Property > private int index; > > private int submittedQuantity; > > private int submittedLineNumber; > > private String submittedSku; > > > public ValidationTracker getTracker() { > if(indexedTrackers == null) return new > ValidationTrackerImpl(); > return indexedTrackers.get(index); > } > > public void setTracker(ValidationTracker aTracker) { > > if(indexedTrackers == null) { > if(log.isTraceEnabled()) log.trace("crating indexed > trackers map"); > indexedTrackers = new HashMap<Integer, > ValidationTracker>(); > } > > if(log.isTraceEnabled()) { > log.trace("setting tracker for index: " + index); > } > > indexedTrackers.put(index, aTracker); > } > > public String getFieldError() { > String error = null; > ValidationTracker tracker = getTracker(); > if(tracker != null && tracker.getError(quantityField) != > null) { > return null; > } > CartItemBean cib = > findCartItem(cartDisplayItem.getLineNumber()); > if(!cib.isValid()) { > List<MessageHolderBean> lineItemErrors = > cib.getErrorMessages(); > int counter; > for(counter = 0; counter < lineItemErrors.size(); > ++counter) { > MessageHolderBean lineItemError = > lineItemErrors.get(counter); > String liErrId = > lineItemError.getMessageId(); > // for now we only pull out the first error; > in the future we > // may need to account for the entire > collection. As of 4.0, > // collection always contains only 1 item > if(counter == 0) { > String liErrTemplate = > getMessages().get(liErrId); > List<Object> liErrParams = > lineItemError.getParameters(); > error = > MessageFormat.format(liErrTemplate, liErrParams.toArray()); > } > } > } > > return error; > } > > @OnEvent(value=EventConstants.PREPARE_FOR_SUBMIT, > component=ID_CART_FORM) > void beforeFormSubmit(int aIndex, int aLineNumber, String aSku) { > if(log.isDebugEnabled()) { > log.debug("beforeFormSubmit -> aIndex: " + aIndex + > ", aLineNumber: " + aLineNumber + ", > aSku: " + aSku); > } > index = aIndex; > submittedLineNumber = aLineNumber; > submittedSku = aSku; > indexedTrackers = new HashMap<Integer, ValidationTracker>(); > } > > @OnEvent(value=EventConstants.VALIDATE, component=ID_QUANTITY_FIELD) > void validateQuantity(int aQuantity) { > > if(log.isDebugEnabled()) { > log.debug("validating... aQuantity: " + aQuantity); > } > > if(aQuantity <= 0) { > String error = > getMessages().format("error-quantity-value", > submittedSku); > cartForm.recordError(quantityField, error); > } > } > > @OnEvent(value=EventConstants.FAILURE, component=ID_CART_FORM) > void failure() { > if(log.isDebugEnabled()) { > log.debug("form submit ERROR!"); > } > } > > /** > * Performs update of quantity upon successful submission of a line > item. > * Note that removal of the line item is not done as part of form > * submission, because we want to bypass any sort of line item > validation > * if user simply wants to remove it. > */ > @OnEvent(value=EventConstants.SUCCESS, component=ID_CART_FORM) > void onUpdate() { > > if(log.isDebugEnabled()) { > log.debug("form submit OK! lineNo: " + > submittedLineNumber); > } > > cartService.updateCart( > getCartId(), submittedLineNumber, > submittedQuantity); > } > > /** > * Performs removal of a line item from a shopping cart, bypassing > any > * form validation. > */ > @OnEvent(value=EventConstants.ACTION, component=ID_REMOVE_LINK) > void onRemove(int aLineNumber) { > if(log.isDebugEnabled()) log.debug("remove lineNumber: " + > aLineNumber); > > cartService.removeFromCart(getCartId(), aLineNumber); > } > > @Cached > public List<CartItemDisplayBean> getShoppingCartForDisplay() { > > List<CartItemDisplayBean> cartItems = new > LinkedList<CartItemDisplayBean>(); > ShoppingCartBean shoppingCart = getShoppingCart(); > List<CartItemBean> shoppingCartItems = > shoppingCart.getCartItems(); > > for (CartItemBean ci : shoppingCartItems) { > CartItemDisplayBean cartItem = new > CartItemDisplayBean(); > ProductBean product = > catalogService.getProduct(ci.getProductId(), > getLocale()); > // product may be null if invalid sku was added to > the cart > cartItem.setValid(product != null); > if(cartItem.isValid()) { > cartItem.setSku(product.getSku()); > cartItem.setPrice(ci.getCartPrice()); > cartItem.setQuantity(ci.getQuantity()); > String usuallyShipsMsg = > buildUsuallyShipsMessage(ci > .getUsuallyShipsMessage()); > > cartItem.setUsuallyShipsMessage(usuallyShipsMsg); > > cartItem.setDescription(product.getShortDescription()); > cartItem.setTotal(ci.getExtendedPrice()); > cartItem.setLineNumber(ci.getLineNumber()); > } > else { > cartItem.setSku(ci.getInvalidProductId()); > cartItem.setQuantity(ci.getQuantity()); > cartItem.setLineNumber(ci.getLineNumber()); > } > cartItems.add(cartItem); > } > > Collections.sort(cartItems); > return cartItems; > } > > private String buildUsuallyShipsMessage(MessageHolderBean aBean) { > if(aBean == null) return "??"; > > String msgId = aBean.getMessageId(); > List<Object> msgParams = aBean.getParameters(); > String msgTemplate = getMessages().get(msgId); > String message = MessageFormat.format(msgTemplate, > msgParams.toArray()); > > return message; > } > > /** > * Given line number, locates line item within the shopping cart and > * returns its instance. If line number is invalid resulting in > failed > * search, null is returned. > * > * @param aLineNumber line number for the item to be found > * @return > */ > private CartItemBean findCartItem(int aLineNumber) { > ShoppingCartBean cart = getShoppingCart(); > List<CartItemBean> cartItems = cart.getCartItems(); > for(CartItemBean cartItem : cartItems) { > int lineNumber = cartItem.getLineNumber(); > if(lineNumber == aLineNumber) return cartItem; > } > return null; > } > > public int getQuantity() { > return cartDisplayItem.getQuantity(); > } > > public void setQuantity(int aQuantity) { > if(log.isTraceEnabled()) log.trace("setQuantity: " + > aQuantity); > submittedQuantity = aQuantity; > } > > public String getQuantityLabel() { > return submittedSku; > } > > /** > * Determines if form under current loop iteration has errors or > not. > * > * @return true if form has errors, false otherwise > */ > public boolean isFormInError() { > ValidationTracker tracker = null; > if(indexedTrackers != null) { > tracker = indexedTrackers.get(index); > } > return tracker != null && tracker.getHasErrors(); > } > > /** > * Determines if overall, entire shopping cart is valid. This > includes > * persistent errors (line item errors), as well as UI input errors, > all > * combined to a single boolean result. This value should be used to > * control the display of a generic state of the cart (or or not). > * > * @return true if cart is in perfect state, including all of its > line > * items, false if there is even the slightest problem. > */ > public boolean isCartValid() { > ShoppingCartBean cart = getShoppingCart(); > boolean valid = cart.isValid(); > if(log.isDebugEnabled()) log.debug("cart.isValid ? " + > valid); > if(valid && indexedTrackers != null) { > if(log.isDebugEnabled()) log.debug("analyzing > trackers..."); > Collection<ValidationTracker> trackers = > indexedTrackers.values(); > for(ValidationTracker tracker : trackers) { > valid = valid && !tracker.getHasErrors(); > if(!valid) break; > } > } > return valid; > } > > /** > * Determines if the cart line item under current loop iteration > contains > * invalid sku. This information can be used to disable certain ui > * components such as quantity update button. > * > * @return true if cart item under current iteration contains > invalid sku, > * false otherwise > */ > public boolean isLineItemWrongSku() { > boolean inError = false; > if(cartDisplayItem != null) inError = > !cartDisplayItem.isValid(); > return inError; > } > } > > <t:layout xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" > t:title="${pageTitle}" xmlns:p="tapestry:parameter"> > <h1>${message:page-title}</h1> > <style type="text/css"> > td.kk-currency-cell { > text-align: right; > } > </style> > > <div t:type="if" t:test="cartValid" style="float:right;font-weight:bold;"> > <span style="color:green;">${message:cart-status-ok}</span> > <p:else><span > style='color:red;'>${message:cart-status-error}</span></p:else> > </div> > > <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> > <t:loop t:source="shoppingCartForDisplay" t:value="cartDisplayItem" > t:index="index"> > <t:form t:id="cartForm" t:context="[index, cartDisplayItem.lineNumber, > cartDisplayItem.sku]"> > <tr> > <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" t:context="index" > 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> > <input t:type="submit" t:id="update" t:disabled="lineItemWrongSku" > t:value="message:update-value"/> > <a t:type="actionLink" t:id="remove" > t:context="cartDisplayItem.lineNumber"><input type="button" > value="${message:remove-value}"/></a> > </td> > </tr> > <t:if test="formInError"> > <tr> > <td colspan="8"><t:error for="quantity"/></td> > </tr> > </t:if> > </t:form> > </t:loop> > <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:layout> > > Cheers! (I'm a happy guy today, the demo went well) > > :-) Adam > > --------------------------------------------------------------------- > To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org > For additional commands, e-mail: users-h...@tapestry.apache.org > >