Author: jonesde
Date: Mon Jan 22 23:43:47 2007
New Revision: 498945

URL: http://svn.apache.org/viewvc?view=rev&rev=498945
Log:
Added enhancement to the getPreviousNextProducts to support an orderByFields 
list, optionally in the context as well; a couple of misc cleanups too

Modified:
    
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/productdetail.bsh
    
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/configproductdetail.ftl
    
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/productdetail.ftl
    ofbiz/trunk/applications/product/servicedef/services_view.xml
    
ofbiz/trunk/applications/product/src/org/ofbiz/product/category/CategoryServices.java

Modified: 
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/productdetail.bsh
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/productdetail.bsh?view=diff&rev=498945&r1=498944&r2=498945
==============================================================================
--- 
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/productdetail.bsh
 (original)
+++ 
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/productdetail.bsh
 Mon Jan 22 23:43:47 2007
@@ -128,7 +128,13 @@
 
     Map catNextPreviousResult = null;
     if (!UtilValidate.isEmpty(categoryId)) {
-        catNextPreviousResult = dispatcher.runSync("getPreviousNextProducts", 
UtilMisc.toMap("categoryId", categoryId, "productId", productId));
+        prevNextMap = UtilMisc.toMap("categoryId", categoryId, "productId", 
productId);
+        if (context.get("orderByFields") != null) {
+            prevNextMap.put("orderByFields", context.get("orderByFields"));
+        } else {
+            prevNextMap.put("orderByFields", UtilMisc.toList("sequenceNum", 
"productId"));
+        }
+        catNextPreviousResult = dispatcher.runSync("getPreviousNextProducts", 
prevNextMap);
         if (ServiceUtil.isError(catNextPreviousResult)) {
             request.setAttribute("errorMessageList", 
UtilMisc.toList(ServiceUtil.getErrorMessage(catNextPreviousResult)));
             return;
@@ -250,105 +256,105 @@
             }
 
             if (variantTree != null && imageMap != null) {
-                   jsBuf = new StringBuffer();
-                   jsBuf.append("<script language=\"JavaScript\">");
-                   jsBuf.append("var DET = new Array(" + variantTree.size() + 
");");
-                   jsBuf.append("var IMG = new Array(" + variantTree.size() + 
");");
-                   jsBuf.append("var OPT = new Array(" + featureOrder.size() + 
");");
+                jsBuf = new StringBuffer();
+                jsBuf.append("<script language=\"JavaScript\">");
+                jsBuf.append("var DET = new Array(" + variantTree.size() + 
");");
+                jsBuf.append("var IMG = new Array(" + variantTree.size() + 
");");
+                jsBuf.append("var OPT = new Array(" + featureOrder.size() + 
");");
                     jsBuf.append("var VIR = new Array(" + 
virtualVariant.size() + ");");
-                   jsBuf.append("var detailImageUrl = null;");
-                   for (li = 0; li < featureOrder.size(); li++) {
-                       jsBuf.append("OPT[" + li + "] = \"FT" + 
featureOrder.get(li) + "\";");
-                   }
+                jsBuf.append("var detailImageUrl = null;");
+                for (li = 0; li < featureOrder.size(); li++) {
+                    jsBuf.append("OPT[" + li + "] = \"FT" + 
featureOrder.get(li) + "\";");
+                }
                     for (li = 0; li < virtualVariant.size(); li++) {
                         jsBuf.append("VIR[" + li + "] = \"" + 
virtualVariant.get(li) + "\";");                  
                     }
 
-                   // build the top level
-                   topLevelName = featureOrder.get(0);
-                   jsBuf.append("function list" + topLevelName + "() {");
-                   jsBuf.append("document.forms[\"addform\"].elements[\"FT" + 
topLevelName + "\"].options.length = 1;");
-                   jsBuf.append("document.forms[\"addform\"].elements[\"FT" + 
topLevelName + "\"].options[0] = new Option(\"" + 
featureTypes.get(topLevelName) + "\",\"\",true,true);");
-                   if (variantTree != null) {
-                       vTreeKeySet = variantTree.keySet();
-                       vti = vTreeKeySet.iterator();
-                       firstDetailImage = null;
-                       firstLargeImage = null;
-                       counter = 0;
-                       while (vti.hasNext()) {
-                           key = vti.next();
-                           value = variantTree.get(key);
-                           opt = null;
-                           if (featureOrder.size() == 1) {
-                               opt = value.iterator().next();
-                           } else {
-                               opt = "" + counter;
-                           }
-                           // create the variant content wrapper
+                // build the top level
+                topLevelName = featureOrder.get(0);
+                jsBuf.append("function list" + topLevelName + "() {");
+                jsBuf.append("document.forms[\"addform\"].elements[\"FT" + 
topLevelName + "\"].options.length = 1;");
+                jsBuf.append("document.forms[\"addform\"].elements[\"FT" + 
topLevelName + "\"].options[0] = new Option(\"" + 
featureTypes.get(topLevelName) + "\",\"\",true,true);");
+                if (variantTree != null) {
+                    vTreeKeySet = variantTree.keySet();
+                    vti = vTreeKeySet.iterator();
+                    firstDetailImage = null;
+                    firstLargeImage = null;
+                    counter = 0;
+                    while (vti.hasNext()) {
+                        key = vti.next();
+                        value = variantTree.get(key);
+                        opt = null;
+                        if (featureOrder.size() == 1) {
+                            opt = value.iterator().next();
+                        } else {
+                            opt = "" + counter;
+                        }
+                        // create the variant content wrapper
                         contentWrapper = new 
ProductContentWrapper(((GenericValue) imageMap.get(key)), request);
 
-                           // get the default image
-                           virtualDetailImage = 
productContentWrapper.get("DETAIL_IMAGE_URL");
-                           virtualLargeImage = 
productContentWrapper.get("LARGE_IMAGE_URL");
+                        // get the default image
+                        virtualDetailImage = 
productContentWrapper.get("DETAIL_IMAGE_URL");
+                        virtualLargeImage = 
productContentWrapper.get("LARGE_IMAGE_URL");
 
                         // initial image paths
-                           detailImage = 
contentWrapper.get("DETAIL_IMAGE_URL");
-                           largeImage = contentWrapper.get("LARGE_IMAGE_URL");
+                        detailImage = contentWrapper.get("DETAIL_IMAGE_URL");
+                        largeImage = contentWrapper.get("LARGE_IMAGE_URL");
+
+                        if (detailImage == null || detailImage.length() == 0) {
+                            detailImage = virtualDetailImage;
+                        }
+                        if (largeImage == null || largeImage.length() == 0) {
+                            largeImage = virtualLargeImage;
+                        }
+
+                        // full image URLs
+                        detailImageUrl = null;
+                        largeImageUrl = null;
+
+                        // append the content prefix
+                        if (detailImage != null && detailImage.length() > 0) {
+                            detailImageUrl = 
ContentUrlTag.getContentPrefix(request) + detailImage;
+                            // base64 encode the image url for a little 
protection
+                            detailImageUrl = 
URLEncoder.encode(Base64.base64Encode(detailImageUrl), "UTF-8");
+                        }
+                        if (largeImage != null && largeImage.length() > 0) {
+                            largeImageUrl = 
ContentUrlTag.getContentPrefix(request) + largeImage;
+                        }
+
+                        
jsBuf.append("document.forms[\"addform\"].elements[\"FT" + topLevelName + 
"\"].options[" + (counter+1) + "] = new Option(\"" + key + "\",\"" + opt + 
"\");");
+                        jsBuf.append("DET[" + counter + "] = \"" + 
detailImageUrl +"\";");
+                        jsBuf.append("IMG[" + counter + "] = \"" + 
largeImageUrl +"\";");
+
+                        if (firstDetailImage == null || 
firstDetailImage.length() == 0) {
+                            firstDetailImage = detailImageUrl;
+                        }
+                        if (firstLargeImage == null || 
firstLargeImage.length() == 0) {
+                            firstLargeImage = largeImage;
+                        }
+                        counter++;
+                    }
+                    context.put("firstDetailImage", firstDetailImage);
+                    context.put("firstLargeImage", firstLargeImage);
+                }
+                jsBuf.append("}");
 
-                           if (detailImage == null || detailImage.length() == 
0) {
-                               detailImage = virtualDetailImage;
-                           }
-                           if (largeImage == null || largeImage.length() == 0) 
{
-                               largeImage = virtualLargeImage;
-                           }
-
-                           // full image URLs
-                           detailImageUrl = null;
-                           largeImageUrl = null;
-
-                           // append the content prefix
-                           if (detailImage != null && detailImage.length() > 
0) {
-                               detailImageUrl = 
ContentUrlTag.getContentPrefix(request) + detailImage;
-                               // base64 encode the image url for a little 
protection
-                               detailImageUrl = 
URLEncoder.encode(Base64.base64Encode(detailImageUrl), "UTF-8");
-                           }
-                           if (largeImage != null && largeImage.length() > 0) {
-                               largeImageUrl = 
ContentUrlTag.getContentPrefix(request) + largeImage;
-                           }
-
-                           
jsBuf.append("document.forms[\"addform\"].elements[\"FT" + topLevelName + 
"\"].options[" + (counter+1) + "] = new Option(\"" + key + "\",\"" + opt + 
"\");");
-                           jsBuf.append("DET[" + counter + "] = \"" + 
detailImageUrl +"\";");
-                           jsBuf.append("IMG[" + counter + "] = \"" + 
largeImageUrl +"\";");
-
-                           if (firstDetailImage == null || 
firstDetailImage.length() == 0) {
-                               firstDetailImage = detailImageUrl;
-                           }
-                           if (firstLargeImage == null || 
firstLargeImage.length() == 0) {
-                               firstLargeImage = largeImage;
-                           }
-                           counter++;
-                       }
-                       context.put("firstDetailImage", firstDetailImage);
-                       context.put("firstLargeImage", firstLargeImage);
-                   }
-                   jsBuf.append("}");
-
-                   // build dynamic lists
-                   if (variantTree != null) {
-                       topLevelKeys = variantTree.keySet();
-                       tli = topLevelKeys.iterator();
-                       topLevelKeysCt = 0;
-                       while (tli.hasNext()) {
-                           cnt = "" + topLevelKeysCt;
-                           varTree = variantTree.get(tli.next());
-                           if (varTree instanceof Map) {
-                               jsBuf.append(buildNext(varTree, featureOrder, 
featureOrder.get(1), cnt, featureTypes));
-                           }
-                           topLevelKeysCt++;
-                       }
-                   }
+                // build dynamic lists
+                if (variantTree != null) {
+                    topLevelKeys = variantTree.keySet();
+                    tli = topLevelKeys.iterator();
+                    topLevelKeysCt = 0;
+                    while (tli.hasNext()) {
+                        cnt = "" + topLevelKeysCt;
+                        varTree = variantTree.get(tli.next());
+                        if (varTree instanceof Map) {
+                            jsBuf.append(buildNext(varTree, featureOrder, 
featureOrder.get(1), cnt, featureTypes));
+                        }
+                        topLevelKeysCt++;
+                    }
+                }
 
-                   // make a list of variant sku with requireAmount
+                // make a list of variant sku with requireAmount
                 variantsRes = dispatcher.runSync("getAssociatedProducts", 
UtilMisc.toMap("productId", productId, "type", "PRODUCT_VARIANT", 
"checkViewAllow", Boolean.TRUE, "prodCatalogId", currentCatalogId));
                 variants = variantsRes.get("assocProducts");
                 if (variants != null) {
@@ -363,10 +369,10 @@
                     amt.append(" } ");
                 }
                 jsBuf.append(amt.toString());
-                   jsBuf.append("</script>");
+                jsBuf.append("</script>");
 
-                   context.put("virtualJavaScript", jsBuf.toString());
-               }
+                context.put("virtualJavaScript", jsBuf.toString());
+            }
         }
 
 

Modified: 
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/configproductdetail.ftl
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/configproductdetail.ftl?view=diff&rev=498945&r1=498944&r2=498945
==============================================================================
--- 
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/configproductdetail.ftl
 (original)
+++ 
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/configproductdetail.ftl
 Mon Jan 22 23:43:47 2007
@@ -143,11 +143,11 @@
     <tr>
       <td colspan="2" align="right">
         <#if previousProductId?exists>
-          <a 
href='<@ofbizUrl>product/~category_id=${categoryId?if_exists}/~product_id=${previousProductId?if_exists}</@ofbizUrl>'
 class="buttontext">[${uiLabelMap.CommonPrevious}]</a>&nbsp;|&nbsp;
+          <a 
href='<@ofbizUrl>product/~category_id=${categoryId?if_exists}/~product_id=${previousProductId?if_exists}</@ofbizUrl>'
 class="buttontext">${uiLabelMap.CommonPrevious}</a>&nbsp;|&nbsp;
         </#if>
-        <a 
href="<@ofbizUrl>category/~category_id=${categoryId?if_exists}</@ofbizUrl>" 
class="buttontext">${category.description?if_exists}</a>
+        <a 
href="<@ofbizUrl>category/~category_id=${categoryId?if_exists}</@ofbizUrl>" 
class="buttontext">${(category.categoryName)?default(category.description)?if_exists}</a>
         <#if nextProductId?exists>
-          &nbsp;|&nbsp;<a 
href='<@ofbizUrl>product/~category_id=${categoryId?if_exists}/~product_id=${nextProductId?if_exists}</@ofbizUrl>'
 class="buttontext">[${uiLabelMap.CommonNext}]</a>
+          &nbsp;|&nbsp;<a 
href='<@ofbizUrl>product/~category_id=${categoryId?if_exists}/~product_id=${nextProductId?if_exists}</@ofbizUrl>'
 class="buttontext">${uiLabelMap.CommonNext}</a>
         </#if>
       </td>
     </tr>

Modified: 
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/productdetail.ftl
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/productdetail.ftl?view=diff&rev=498945&r1=498944&r2=498945
==============================================================================
--- 
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/productdetail.ftl 
(original)
+++ 
ofbiz/trunk/applications/order/webapp/ordermgr/entry/catalog/productdetail.ftl 
Mon Jan 22 23:43:47 2007
@@ -205,7 +205,7 @@
         <#if previousProductId?exists>
           <a 
href="<@ofbizUrl>product/~category_id=${categoryId?if_exists}/~product_id=${previousProductId?if_exists}</@ofbizUrl>"
 class="buttontext">${uiLabelMap.CommonPrevious}</a>&nbsp;|&nbsp;
         </#if>
-        <a 
href="<@ofbizUrl>category/~category_id=${categoryId?if_exists}</@ofbizUrl>" 
class="linktext">${category.description?if_exists}</a>
+        <a 
href="<@ofbizUrl>category/~category_id=${categoryId?if_exists}</@ofbizUrl>" 
class="linktext">${(category.categoryName)?default(category.description)?if_exists}</a>
         <#if nextProductId?exists>
           &nbsp;|&nbsp;<a 
href="<@ofbizUrl>product/~category_id=${categoryId?if_exists}/~product_id=${nextProductId?if_exists}</@ofbizUrl>"
 class="buttontext">${uiLabelMap.CommonNext}</a>
         </#if>

Modified: ofbiz/trunk/applications/product/servicedef/services_view.xml
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services_view.xml?view=diff&rev=498945&r1=498944&r2=498945
==============================================================================
--- ofbiz/trunk/applications/product/servicedef/services_view.xml (original)
+++ ofbiz/trunk/applications/product/servicedef/services_view.xml Mon Jan 22 
23:43:47 2007
@@ -106,10 +106,12 @@
         <attribute name="categoryMembers" type="java.util.Collection" 
mode="OUT"/>
     </service>
     <service name="getPreviousNextProducts" engine="java"
-            location="org.ofbiz.product.category.CategoryServices" 
invoke="getNextPreviousCategoryMembers">
+        location="org.ofbiz.product.category.CategoryServices" 
invoke="getPreviousNextProducts">
         <description>Gets the previous and next product Ids.</description>
         <attribute name="categoryId" type="String" mode="IN"/>
         <attribute name="productId" type="String" mode="IN"/>
+        <attribute name="activeOnly" type="Boolean" mode="IN" optional="true"/>
+        <attribute name="orderByFields" type="List" mode="IN" optional="true"/>
         <attribute name="category" type="org.ofbiz.entity.GenericValue" 
mode="OUT" optional="true"/>
         <attribute name="previousProductId" type="String" mode="OUT" 
optional="true"/>
         <attribute name="nextProductId" type="String" mode="OUT" 
optional="true"/>

Modified: 
ofbiz/trunk/applications/product/src/org/ofbiz/product/category/CategoryServices.java
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/product/category/CategoryServices.java?view=diff&rev=498945&r1=498944&r2=498945
==============================================================================
--- 
ofbiz/trunk/applications/product/src/org/ofbiz/product/category/CategoryServices.java
 (original)
+++ 
ofbiz/trunk/applications/product/src/org/ofbiz/product/category/CategoryServices.java
 Mon Jan 22 23:43:47 2007
@@ -75,41 +75,43 @@
         return result;
     }
 
-    public static Map getNextPreviousCategoryMembers(DispatchContext dctx, Map 
context) {
+    public static Map getPreviousNextProducts(DispatchContext dctx, Map 
context) {
         GenericDelegator delegator = dctx.getDelegator();
         String categoryId = (String) context.get("categoryId");
         String productId = (String) context.get("productId");
+        boolean activeOnly = (context.get("activeOnly") != null ? ((Boolean) 
context.get("activeOnly")).booleanValue() : true);
         Integer index = (Integer) context.get("index");
 
         if (index == null && productId == null) {
             return ServiceUtil.returnError("Both Index and ProductID cannot be 
null.");
         }
 
-        Map values = getCategoryMembers(dctx, context);
+        List orderByFields = (List) context.get("orderByFields");
+        if (orderByFields == null) orderByFields = FastList.newInstance();
+        String entityName = getCategoryFindEntityName(delegator, 
orderByFields);
 
-        if (values.containsKey(ModelService.ERROR_MESSAGE)) {
-            return values;
-        }
-        if (!values.containsKey("categoryMembers") || 
values.get("categoryMembers") == null) {
-            return ServiceUtil.returnError("Problem reading category data.");
+        GenericValue productCategory;
+        List productCategoryMembers;
+        try {
+            productCategory = 
delegator.findByPrimaryKeyCache("ProductCategory", 
UtilMisc.toMap("productCategoryId", categoryId));
+            productCategoryMembers = delegator.findByAndCache(entityName, 
UtilMisc.toMap("productCategoryId", categoryId), orderByFields);
+        } catch (GenericEntityException e) {
+            String errMsg = "Error finding previous/next product info: " + 
e.toString();
+            Debug.logInfo(e, errMsg, module);
+            return ServiceUtil.returnError(errMsg);
         }
-
-        Collection memberCol = (Collection) values.get("categoryMembers");
-        if (memberCol == null || memberCol.size() == 0) {
-            // this is not going to be an error condition because we don't 
want it to be so critical, ie rolling back the transaction and such
-            return ServiceUtil.returnSuccess("Product not found in the current 
category.");
+        if (activeOnly) {
+            productCategoryMembers = 
EntityUtil.filterByDate(productCategoryMembers, true);
         }
-
-        List memberList = new ArrayList(memberCol);
+        
 
         if (productId != null && index == null) {
-            Iterator i = memberList.iterator();
-
+            Iterator i = productCategoryMembers.iterator();
             while (i.hasNext()) {
                 GenericValue v = (GenericValue) i.next();
-
-                if (v.getString("productId").equals(productId))
-                    index = new Integer(memberList.indexOf(v));
+                if (v.getString("productId").equals(productId)) {
+                    index = new Integer(productCategoryMembers.indexOf(v));
+                }
             }
         }
 
@@ -119,44 +121,40 @@
         }
 
         Map result = ServiceUtil.returnSuccess();
-        result.put("category", values.get("category"));
+        result.put("category", productCategory);
 
         String previous = null;
         String next = null;
 
-        if (index.intValue() - 1 >= 0 && index.intValue() - 1 < 
memberList.size()) {
-            previous = ((GenericValue) memberList.get(index.intValue() - 
1)).getString("productId");
+        if (index.intValue() - 1 >= 0 && index.intValue() - 1 < 
productCategoryMembers.size()) {
+            previous = ((GenericValue) 
productCategoryMembers.get(index.intValue() - 1)).getString("productId");
             result.put("previousProductId", previous);
         } else {
-            previous = ((GenericValue) memberList.get(memberList.size() - 
1)).getString("productId");
+            previous = ((GenericValue) 
productCategoryMembers.get(productCategoryMembers.size() - 
1)).getString("productId");
             result.put("previousProductId", previous);
         }
 
-        if (index.intValue() + 1 < memberList.size()) {
-            next = ((GenericValue) memberList.get(index.intValue() + 
1)).getString("productId");
+        if (index.intValue() + 1 < productCategoryMembers.size()) {
+            next = ((GenericValue) productCategoryMembers.get(index.intValue() 
+ 1)).getString("productId");
             result.put("nextProductId", next);
         } else {
-            next = ((GenericValue) memberList.get(0)).getString("productId");
+            next = ((GenericValue) 
productCategoryMembers.get(0)).getString("productId");
             result.put("nextProductId", next);
         }
         return result;
     }
-
-    public static Map getProductCategoryAndLimitedMembers(DispatchContext 
dctx, Map context) {
-        GenericDelegator delegator = dctx.getDelegator();
-        String productCategoryId = (String) context.get("productCategoryId");
-        boolean limitView = ((Boolean) 
context.get("limitView")).booleanValue();
-        int defaultViewSize = ((Integer) 
context.get("defaultViewSize")).intValue();
-        
-        List orderByFields = (List) context.get("orderByFields");
-        if (orderByFields == null || orderByFields.size() == 0) {
-            orderByFields = FastList.newInstance();
+    
+    private static String getCategoryFindEntityName(GenericDelegator 
delegator, List orderByFields) {
+        // allow orderByFields to contain fields from the Product entity, if 
there are such fields
+        String entityName = "ProductCategoryMember";
+        if (orderByFields == null) {
+            return entityName;
+        }
+        if (orderByFields.size() == 0) {
             orderByFields.add("sequenceNum");
             orderByFields.add("productId");
         }
         
-        // allow orderByFields to contain fields from the Product entity, if 
there are such fields
-        String entityName = "ProductCategoryMember";
         ModelEntity productModel = delegator.getModelEntity("Product");
         ModelEntity productCategoryMemberModel = 
delegator.getModelEntity("ProductCategoryMember");
         Iterator orderByFieldIter = orderByFields.iterator();
@@ -172,8 +170,19 @@
                 }
             }
         }
-        
+        return entityName;
+    }
 
+    public static Map getProductCategoryAndLimitedMembers(DispatchContext 
dctx, Map context) {
+        GenericDelegator delegator = dctx.getDelegator();
+        String productCategoryId = (String) context.get("productCategoryId");
+        boolean limitView = ((Boolean) 
context.get("limitView")).booleanValue();
+        int defaultViewSize = ((Integer) 
context.get("defaultViewSize")).intValue();
+        
+        List orderByFields = (List) context.get("orderByFields");
+        if (orderByFields == null) orderByFields = FastList.newInstance();
+        String entityName = getCategoryFindEntityName(delegator, 
orderByFields);
+        
         String prodCatalogId = (String) context.get("prodCatalogId");
 
         boolean useCacheForMembers = (context.get("useCacheForMembers") != 
null ? ((Boolean) context.get("useCacheForMembers")).booleanValue() : true);


Reply via email to