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
>
>

Reply via email to