The onContextMenu Event Handler

January 26th, 2008

onContextMenu event does not fire on disabled fields

The onContextMenu event (right-click in Windows) is a proprietary handler in HTML, but is very useful for displaying custom context menus on web pages. For this particular article, I was investigating an application where the business owner wanted a context menu available on disabled fields. The problem specifically occurs because all click events are apparently ignored on disabled fields. Note that Opera apparently ignores the onContextMenu event completely, because it is not a W3C compliant attribute.

Regardless of the W3C non-compliance, the business still wanted this behavior (as far as we know everyone is using IE or FF). Try the following in your browser to see how it behaves.

JAVA:
  1. <input id="input1" type="text" value="input1" oncontextmenu="alert('input1');return false;" />

Attempt 1: Nest it in a div

The first thing we tried was to wrap the input fields in a div element that had an onContextMenu attribute defined. We found that while this solved the problem in IE7, FF did not "bubble up" the event through the disabled input field. However, any text inside the div element did respond to the onContextMenu event. Try the following in your browser to see how it behaves.

Inside div, outside input3 field

Inside div, outside input4 field

JAVA:
  1. <div oncontextmenu="alert('input3');return false;">Inside div, outside input3 field
  2.     <input id="input3" type="text" value="input3" />
  3. </div>

Solution 1: Convert from disabled to read-only

After a little more research, I found another attribute named "readonly" that I had not used before. I tried it, and found that the onContextMenu event worked just fine in both IE7 and FF.

Inside div, outside input6 field

JAVA:
  1. <input id="input5" type="text" value="input5" readonly="readonly" oncontextmenu="alert('input5');return false;" />

Solution 2: Make an external, clickable object

I definitely prefer W3C compliant code, however, so a compliant solution would have to involve an external, clickable element to trigger the event. The » symbol below has an onClick event, which works in IE, FF, and Opera.

»

JAVA:
  1. <input id="input7" type="text" value="input7" disabled="disabled" /> <span onclick="alert('input7');return false;" style="cursor:pointer;">&raquo;</span>

There are some accessibility issues with this approach, of course. One might want to use a link instead of a span to underline the clickable element or choose a more obvious graphical symbol. There are other inherent accessibility issues with the entire context menu approach, but I will not go into those here.

JSP/Java Form Signature

May 16th, 2007

At long last I have been able to set up a form signature for a Java/JSP application. The application I developed uses the Faces framework, and I set up a custom tag to handle the functionality. I am including the important Java classes here. The first Java class is the most important since it contains the functions that generate the signature components. I have documented the code in the javadoc comments.

FormSignatureValidator.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import java.security.MessageDigest;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Calendar;
  6. import java.util.Date;
  7.  
  8. import javax.faces.application.FacesMessage;
  9. import javax.faces.component.UIComponent;
  10. import javax.faces.context.FacesContext;
  11. import javax.faces.validator.Validator;
  12. import javax.faces.validator.ValidatorException;
  13. import javax.servlet.ServletContext;
  14.  
  15. import org.apache.log4j.Logger;
  16.  
  17. import com.threeleaf.agentguidedselling.model.StringEncrypter;
  18. import com.threeleaf.agentguidedselling.model.StringEncrypter.EncryptionException;
  19.  
  20. /**
  21. * Add a "random" but verifiable form field in Faces to prevent many kinds of
  22. * scripted spam attacks. The idea is to tie a browser session to a web address
  23. * during a given timeframe. The goal is to make sure that 1) no generated
  24. * script will be valid for more than an hour 2) robots to different machines
  25. * cannot be spawned. The technique can be worked around, but the hope is that
  26. * spammers will not find it worth their while to do so. If stronger prevention
  27. * is needed, then captchas can be used.
  28. *
  29. * @author John A. Marsh
  30. */
  31. public class FormSignatureValidator implements Validator {
  32.     private Logger logger = Logger.getLogger(this.getClass().getName());
  33.  
  34.     /*
  35.      * Generates the variable to be used in the form signature input tag. The
  36.      * value changes every hour. It is assumed
  37.      * that no single form will ever take an hour to fill out and submit. Every
  38.      * hour, the length of the random variable will change. The object is to
  39.      * twart script robots. It is not perfect, but it works well without
  40.      * requiring captchas.
  41.      *
  42.      * @param optional calendar object with which to calculate the field.
  43.      * Overloaded to default to the current time.
  44.      *
  45.      * @return a reproducable "random" string to be used as a form field name.
  46.      *
  47.      * @author John A. Marsh
  48.      * 
  49.      */
  50.     public static String formSignatureID(Calendar now) {
  51.         // The filed name will change once an hour
  52.         SimpleDateFormat formatter = new SimpleDateFormat("hhdDEEEEFMMMMyyyyzzzz");
  53.         String fieldName = formatter.format(now.getTime());
  54.         // We want to have the string be between 7 and 14 characters in a
  55.         // reproducably "random" manner
  56.         int stringLength = 7 + fieldName.length() % 7;
  57.         fieldName = md5(fieldName);
  58.         /*
  59.          * We want the first character to begin with a letter, not a number, to
  60.          * guarantee browser and language compatibilty We want to start as close
  61.          * to the beginning as possible. It is highly improbably that one of the
  62.          * hex letters would not be found closer to the beginning than the first
  63.          * calculation, but it is there just in case.
  64.          */
  65.         int firstCharacter = 31 - stringLength;
  66.         if (fieldName.indexOf("a")> -1)
  67.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("a"));
  68.         if (fieldName.indexOf("b")> -1)
  69.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("b"));
  70.         if (fieldName.indexOf("c")> -1)
  71.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("c"));
  72.         if (fieldName.indexOf("d")> -1)
  73.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("d"));
  74.         if (fieldName.indexOf("e")> -1)
  75.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("e"));
  76.         if (fieldName.indexOf("f")> -1)
  77.             firstCharacter = Math.min(firstCharacter, fieldName.indexOf("f"));
  78.         fieldName = fieldName.substring(firstCharacter, firstCharacter + stringLength);
  79.         return fieldName;
  80.     }
  81.  
  82.     public static String formSignatureID() {
  83.         Calendar now = Calendar.getInstance();
  84.         return formSignatureID(now);
  85.     }
  86.  
  87.     /*
  88.      * Create a form signature. This is basically a timestamp. The time in
  89.      * milliseconds is hashed and then paired with the encrypted value. On the
  90.      * other side, the time is unencrypted and compared with the hashed value.
  91.      *
  92.      * @param seed Optional "password" for the form
  93.      *
  94.      * @return the generated key
  95.      *
  96.      * @trows EncryptionException
  97.      *
  98.      * @author John A. Marsh
  99.      * 
  100.      */
  101.     public static String formSignatureGenerate(String seed) throws EncryptionException {
  102.         Logger logger = Logger.getLogger("FormSignatureValidator.formSignatureGenerate(\"" + seed + "\")");
  103.         Date currentDate = new Date();
  104.         String millitime = Long.toString(currentDate.getTime());
  105.         StringEncrypter encrypter = new StringEncrypter("DES", seed);
  106.         if(logger.isDebugEnabled()) {
  107.             logger.debug("Time in milliseconds: " + millitime);
  108.             logger.debug("md5(millitime): " + md5(millitime));
  109.             logger.debug("encrypter.encrypt(millitime): " + encrypter.encrypt(millitime));
  110.         }
  111.         return md5(millitime) + encrypter.encrypt(millitime);
  112.     }
  113.  
  114.     public static String formSignatureGenerate() {
  115.         ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
  116.         Logger logger = Logger.getLogger("FormSignatureValidator.formSignatureGenerate()");
  117.         String seed = context.getServerInfo();
  118.         logger.debug("Generated seed: " + seed);
  119.         try {
  120.             return formSignatureGenerate(seed);
  121.         } catch (EncryptionException e) {
  122.             return "error";
  123.         }
  124.     }
  125.  
  126.     public static String md5(String string) {
  127.         try {
  128.             MessageDigest md = MessageDigest.getInstance("MD5");
  129.             md.update(string.getBytes());
  130.             byte[] digest = md.digest();
  131.             String hexString = hexify(digest);
  132.             return hexString;
  133.         } catch (Exception e) {
  134.             e.printStackTrace();
  135.             return null;
  136.         }
  137.     }
  138.  
  139.     static private String[] HEX = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
  140.  
  141.     static public String hexify(byte byValue) {
  142.         int nValue = (int) byValue + 128;
  143.         return HEX[nValue / 16] + HEX[nValue % 16];
  144.     }
  145.  
  146.     static public String hexify(byte[] bytes) {
  147.         String hexed = "";
  148.         for (int i = 0; i <bytes.length; i++) {
  149.             hexed += hexify(bytes[i]);
  150.         }
  151.         return hexed;
  152.     }
  153.    
  154. }

The following code is used to render the custom tag in Faces.

FormSignatureRenderer.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import java.io.IOException;
  4. import java.util.Map;
  5.  
  6. import javax.faces.component.UIComponent;
  7. import javax.faces.component.UIInput;
  8. import javax.faces.context.FacesContext;
  9. import javax.faces.context.ResponseWriter;
  10. import javax.faces.render.Renderer;
  11.  
  12. /**
  13. * Dictates how the HTML element is to be rendered.
  14. * The form signature is a hidden field in a form. Its name and
  15. * value are auto generated using the formulas in FormSignatureValidator.
  16. *
  17. * @author      John A. Marsh
  18. * @see         FormSignatureValidator
  19. *
  20. */
  21. public class FormSignatureRenderer extends Renderer {
  22.  
  23.     public FormSignatureRenderer() {
  24.         super();
  25.     }
  26.  
  27.     /* (non-Javadoc)
  28.      *
  29.      * This method is responsible for taking any parameters that were passed in from a form post and setting the value on the component.
  30.      *
  31.      * @see javax.faces.render.Renderer#decode(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
  32.      */
  33.     public void decode(FacesContext context, UIComponent component) {
  34.        
  35.         assertValidInput(context, component);
  36.        
  37.         if (component instanceof UIInput) {
  38.             UIInput input = (UIInput) component;
  39.             String clientId = input.getClientId(context);
  40.             Map requestMap = context.getExternalContext().getRequestParameterMap();
  41.             String newValue = (String) requestMap.get(clientId);
  42.             if (null != newValue) {
  43.                 input.setSubmittedValue(newValue);
  44.             }
  45.         }
  46.     }
  47.  
  48.     /* (non-Javadoc)
  49.      * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
  50.      */
  51.     public void encodeEnd(FacesContext context, UIComponent component)
  52.             throws IOException {
  53.         assertValidInput(context, component);
  54.         String formSignature = "";
  55.        
  56.         ResponseWriter writer = context.getResponseWriter();
  57.         writer.startElement("input", component);
  58.         writer.writeAttribute("type", "hidden", null);
  59.         String id = FormSignatureValidator.formSignatureID();
  60.         writer.writeAttribute("id", component.getParent().getId() + ":" + id, "id");
  61.         writer.writeAttribute("name", id, "id");
  62.         formSignature = FormSignatureValidator.formSignatureGenerate();
  63.         writer.writeAttribute("value", formSignature, "value");
  64.         writer.endElement("input");
  65.     }
  66.  
  67.     /**
  68.      * Verify that the context and the component are not null.
  69.      * Every Faces Renderer component should do this.
  70.      *
  71.      * @author  John A. Marsh
  72.      * @param   context
  73.      * @param   component
  74.      *
  75.      */
  76.     private void assertValidInput(FacesContext context, UIComponent component) {
  77.         if (context == null) {
  78.             throw new NullPointerException("context should not be null");
  79.         } else if (component == null) {
  80.             throw new NullPointerException("component should not be null");
  81.         }
  82.     }
  83. }

FormSignatureTag.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import javax.faces.component.UIComponent;
  4. import javax.faces.context.FacesContext;
  5. import javax.faces.webapp.UIComponentTag;
  6.  
  7. /**
  8. * Code to create the form signature tag
  9. *
  10. * @author      John A. Marsh
  11. *
  12. */
  13. public class FormSignatureTag extends UIComponentTag {
  14.  
  15.     private static final String FORM_SIGNATURE_COMPONENT_TYPE = "FORM_SIGNATURE_INPUT";
  16.     private static final String FORM_SIGNATURE_RENDER_TYPE = "FORM_SIGNATURE_RENDERER";
  17.  
  18.     /* (non-Javadoc)
  19.      * @see javax.faces.webapp.UIComponentTag#getComponentType()
  20.      */
  21.     public String getComponentType() {
  22.         return FORM_SIGNATURE_COMPONENT_TYPE;
  23.     }
  24.     /* (non-Javadoc)
  25.      * @see javax.faces.webapp.UIComponentTag#getRendererType()
  26.      */
  27.     public String getRendererType() {
  28.         // TODO Auto-generated method stub
  29.         return FORM_SIGNATURE_RENDER_TYPE;
  30.     }
  31.    
  32.     protected void setProperties(UIComponent component) {
  33.         FacesContext context = FacesContext.getCurrentInstance();
  34.         super.setProperties(component);
  35.         /*if (null != value) {
  36.             if (isValueReference(value)) {
  37.                 ValueBinding vb = context.getApplication().createValueBinding(
  38.                         value);
  39.                 component.setValueBinding("value", vb);
  40.             } else {
  41.                 ((UIInput) component).setValue(value);
  42.             }
  43.         }*/
  44.     }
  45. }

UIFormSignature.java

JAVA:
  1. package com.threeleaf.jsf.components.formsignature;
  2.  
  3. import javax.faces.component.UIInput;
  4.  
  5. /**
  6. * Sets up the user interface compontent for the form signature.
  7. *
  8. * @author John A. Marsh
  9. */
  10. public class UIFormSignature extends UIInput {
  11.     public static final String FORM_SIGNATURE_FAMILY = "FSFAMILY";
  12.  
  13.     /**
  14.      * Adding the validator directly to the component, so it does not need to be specified separately.
  15.      */
  16.     public UIFormSignature() {
  17.         super();
  18.         addValidator(new FormSignatureValidator());
  19.     }
  20.    
  21.     public String getFamily() {
  22.         return FORM_SIGNATURE_FAMILY;
  23.     }
  24. }

Zip Code Distance Calculations in MySQL and UDB

March 31st, 2007

I am currently involved in a project where the program I am writing is supposed to return a list of the 10 doctors nearest to a visitor's zip code. Note that this is not the same as doctors in a given radius (say 10 miles), but it does use the same basic formula. This article will be the first of a two part series and lays the groundwork for calculating what is nearest to a zip code. In the second part I intend to show how I integrate this logic with a list of doctors with addresses.

Below you will find the results of my experimental queries that compare the classic radius methodology with my own "10 nearest" methodology. My production program uses a UDB (DB2) database, and I thought of the performance contrasts between UDB and MySQL was interesting, so I am going to show all the comparisons below. Finally, I wanted to show what differences there were between the more accurate "Haversine Formula" as compared to the less intensive "Spherical Law of Cosines" when calculating the distance between two points on a sphere. These examples should give you plenty of real life code you can use in your own programs.

The company I work for subscribes to the zip code database (referred to here as ZipUSA) from http://www.zipdatafiles.com/data/ The data comes from the United States Postal Service and other sources, but I have occasionally found strange geocoding results. I will probably write a different article about that using a different application that I have written.

Within 10 miles

Haversine Formula

The following finds the zip codes within 10 miles of 27712 using the haversine (great circle) equation. The field names are based on the ZipUSA tables.

SELECT DISTINCT
	destination.zip_code,
	3956 * 2 * ASIN(SQRT(
		POWER(SIN((origin.lat - destination.lat) * 0.0174532925 / 2), 2) +
		COS(origin.lat * 0.0174532925) *
		COS(destination.lat * 0.0174532925) *
		POWER(SIN((origin.lng - destination.lng) * 0.0174532925 / 2), 2)
	)) distance
 
FROM
	tblZipUSA origin,
	tblZipUSA destination
 
WHERE
	origin.zip_code = '27712'
	AND
	3956 * 2 * ASIN(SQRT(
		POWER(SIN((origin.lat - destination.lat) * 0.0174532925 / 2), 2) +
		COS(origin.lat * 0.0174532925) *
		COS(destination.lat * 0.0174532925) *
		POWER(SIN((origin.lng - destination.lng) * 0.0174532925 / 2), 2)
	)) < 10
 
ORDER BY
	distance, zip_code

The ZipUSA database was set up with the same indexes in MySQL and UDB (DB2). Queries in MySQL were run in the phpAdmin tool. The UDB/DB2 queries were run in Quest.

MySQL
zip_code distance
27712 0
27704 4.75326962990057
27705 5.34287681627437
27708 5.57383981806715
27503 5.58564401941337
27701 6.41245709075393
27706 6.58029004252293
27709 6.59449266010451
27702 6.61586916445661
27710 6.61586916445661
27711 6.61586916445661
27715 6.61586916445661
27717 6.61586916445661
27722 6.61586916445661
27707 8.57121741472298
27703 9.5828790270889
27278 9.58552637370736
27564 9.72570727493623
27509 9.80777888765073

Showing rows 0 - 18 (19 total, Query took 2.1139 sec)

DB2/UDB
zip_code distance
27712 0
27704 4.75326962990023
27705 5.34287681627435
27708 5.57383981806714
27503 5.58564401941335
27701