Permalink
Browse files

DateField improvements:

- Remove the lat/long to time zone conversion code (requesting user's geolocation too intrusive)
- Allow time zone tracking cookie to be secure
- Add min and max parameters to DateField component
  • Loading branch information...
1 parent 0f582ab commit 9eca44493baaf82cf29242efb8304718318b40e2 @hlship committed May 28, 2012
Showing with 277 additions and 365 deletions.
  1. +1 −0 tapx-datefield/build.gradle
  2. +5 −4 tapx-datefield/src/main/java/com/howardlewisship/tapx/datefield/DateFieldSymbols.java
  3. +94 −56 tapx-datefield/src/main/java/com/howardlewisship/tapx/datefield/components/DateField.java
  4. +25 −24 tapx-datefield/src/main/java/com/howardlewisship/tapx/datefield/components/TimeZoneIdentifier.java
  5. +18 −13 tapx-datefield/src/main/java/com/howardlewisship/tapx/datefield/services/ClientTimeZoneData.java
  6. +10 −28 tapx-datefield/src/main/java/com/howardlewisship/tapx/datefield/services/DateFieldModule.java
  7. +0 −20 ...atefield/src/main/java/com/howardlewisship/tapx/datefield/services/LatLongToTimeZoneResolver.java
  8. +5 −6 ...src/main/java/com/howardlewisship/tapx/internal/datefield/services/BestGuessTimeZoneAnalyzer.java
  9. +37 −6 ...src/main/java/com/howardlewisship/tapx/internal/datefield/services/ClientTimeZoneTrackerImpl.java
  10. +0 −113 ...d/src/main/java/com/howardlewisship/tapx/internal/datefield/services/GeonameTimeZoneResolver.java
  11. +0 −38 ...d/src/main/java/com/howardlewisship/tapx/internal/datefield/services/LatLongTimeZoneAnalyzer.java
  12. +7 −3 ...ld/src/main/java/com/howardlewisship/tapx/internal/datefield/services/SystemTimeZoneAnalyzer.java
  13. +14 −32 ...atefield/src/main/resources/com/howardlewisship/tapx/datefield/components/time-zone-identifier.js
  14. +28 −9 tapx-datefield/src/main/resources/com/howardlewisship/tapx/datefield/tapx-datefield.js
  15. +4 −1 tapx-datefield/src/main/resources/com/howardlewisship/tapx/datefield/tapx-datefield.properties
  16. +27 −10 tapx-datefield/src/test/java/demo/pages/DateFieldDemo.java
  17. +2 −2 tapx-datefield/src/test/resources/demo/pages/DateFieldDemo.tml
@@ -2,6 +2,7 @@ description = "Improved JavaScript date/time selection control"
dependencies {
compile project(':tapx-core')
+ provided "javax.servlet:servlet-api:2.5"
}
jar {
@@ -1,4 +1,4 @@
-// Copyright 2009 Howard M. Lewis Ship
+// Copyright 2009, 2012 Howard M. Lewis Ship
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -32,10 +32,11 @@
public static final String THEME = "tapx.datefield.theme";
/**
- * Symbol that defines the base URL for accessing the GeoNames database. This can be overridden just for
- * testing purposes.
+ * If true, then the cookie used to track the user's time zone (once identified) is secure.
+ * Default is false.
*
* @since 1.2
*/
- public static final String GEONAMES_URL = "tapx.geonames.url";
+ public static final String SECURE_TIME_ZONE_COOKIE = "tapx.datefield.secure-timezone-cookie";
+
}
@@ -1,4 +1,4 @@
-// Copyright 2009, 2010, 2011 Howard M. Lewis Ship
+// Copyright 2009, 2010, 2011, 2012 Howard M. Lewis Ship
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,25 +14,11 @@
package com.howardlewisship.tapx.datefield.components;
-import java.lang.annotation.Annotation;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import org.apache.tapestry5.Asset;
-import org.apache.tapestry5.Binding;
-import org.apache.tapestry5.BindingConstants;
-import org.apache.tapestry5.ComponentResources;
-import org.apache.tapestry5.FieldValidationSupport;
-import org.apache.tapestry5.FieldValidator;
-import org.apache.tapestry5.MarkupWriter;
-import org.apache.tapestry5.ValidationException;
-import org.apache.tapestry5.ValidationTracker;
+import com.howardlewisship.tapx.datefield.TimeSignificant;
+import com.howardlewisship.tapx.datefield.TimeZoneVisibility;
+import com.howardlewisship.tapx.datefield.services.ClientTimeZoneTracker;
+import com.howardlewisship.tapx.datefield.services.DateFieldFormatConverter;
+import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
@@ -50,17 +36,18 @@
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-import com.howardlewisship.tapx.datefield.TimeSignificant;
-import com.howardlewisship.tapx.datefield.TimeZoneVisibility;
-import com.howardlewisship.tapx.datefield.services.ClientTimeZoneTracker;
-import com.howardlewisship.tapx.datefield.services.DateFieldFormatConverter;
+import java.lang.annotation.Annotation;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
/**
* A replacement for Tapestry's built-in DateField, built around the <a
* href="http://www.dynarch.com/projects/calendar/old/">Dynarch
* JSCalendar Widget</a>. This is a highly functional calendar, but is distributed as LGPL and so
* can't be built directly into Tapestry.
- * <p>
+ * <p/>
* Starting with version 1.1, there is enhanced control over the time zone displayed to the user (including the optional
* ability to edit the time zone). You will likely want to include a {@link TimeZoneIdentifier} component in your
* application's Layout to ensure that the correct time zone for the client is used.
@@ -93,15 +80,15 @@
/**
* If true, then the calendar will include time selection as well as date selection. This is
* normally false, unless the property has the {@link TimeSignificant} annotation.
- *
+ *
* @since 1.1
*/
@Parameter
private boolean time;
/**
* Controls how the time zone is presented to the user; the default (for compatibility) is NONE.
- *
+ *
* @since 1.1
*/
@Parameter(value = "none", defaultPrefix = BindingConstants.LITERAL)
@@ -118,20 +105,31 @@
* Object that will provide access to annotations (such as {@link TimeSignificant}). This is
* only used when configuring DateField to work within the {@link BeanEditor}. Normally,
* annotations come from the property bound the value parameter.
- *
+ *
* @since 1.1
*/
@Parameter
private AnnotationProvider annotationProvider;
+ /**
+ * Sets optional maximum and minimum dates for the calendar. This is enforced on the client-side
+ * by making dates outside the range non-selectable, and is enforced on the server as a validation error.
+ *
+ * @since 1.2
+ */
+ @Parameter
+ private Date max, min;
+
/**
* The object that will perform input validation (which occurs after translation). The translate
* binding prefix is generally used to provide this object in a declarative fashion.
*/
@Parameter(defaultPrefix = BindingConstants.VALIDATE)
private FieldValidator<Object> validate;
- /** The icon to use for the button that raises the calendar popup. */
+ /**
+ * The icon to use for the button that raises the calendar popup.
+ */
@Parameter(defaultPrefix = BindingConstants.ASSET, value = "datefield.gif")
private Asset icon;
@@ -232,23 +230,27 @@ public void beginRender(MarkupWriter writer)
String value = tracker.getInput(this);
if (value == null)
+ {
value = formatCurrentValue();
+ }
String clientId = getClientId();
String triggerId = clientId + "-trigger";
writer.element("input",
- "type", hideTextField ? "hidden" : "text",
+ "type", hideTextField ? "hidden" : "text",
- "name", getControlName(),
+ "name", getControlName(),
- "id", clientId,
+ "id", clientId,
- "value", value);
+ "value", value);
if (isDisabled())
+ {
writer.attributes("disabled", "disabled");
+ }
validate.render(writer);
@@ -262,19 +264,31 @@ public void beginRender(MarkupWriter writer)
writer.element("img",
- "id", triggerId,
+ "id", triggerId,
- "class", "t-calendar-trigger",
+ "class", "t-calendar-trigger",
- "src", icon.toClientURL(),
+ "src", icon.toClientURL(),
- "alt", "[Show]");
+ "alt", "[Show]");
writer.end(); // img
writeTimeZone(writer);
- JSONObject spec = new JSONObject("clientId", clientId, "clientDateFormat",
- formatConverter.convertToClient(format)).put("time", time).put("singleClick", singleClick);
+ JSONObject spec = new JSONObject("clientId", clientId,
+ "clientDateFormat", formatConverter.convertToClient(format))
+ .put("time", time)
+ .put("singleClick", singleClick);
+
+ if (max != null)
+ {
+ spec.put("max", convertDateToClientTimeZone(max).getTime());
+ }
+
+ if (min != null)
+ {
+ spec.put("min", convertDateToClientTimeZone(min).getTime());
+ }
javascriptSupport.addInitializerCall("tapxDateField", spec);
}
@@ -346,7 +360,9 @@ private void writeTimeZone(MarkupWriter writer)
private String formatCurrentValue()
{
if (value == null)
+ {
return "";
+ }
format.setTimeZone(timeZoneTracker.getClientTimeZone());
@@ -373,20 +389,9 @@ protected void processSubmission(String elementName)
Date inDefaultTimeZone = format.parse(value);
- Calendar c = Calendar.getInstance(locale);
- c.setTime(inDefaultTimeZone);
-
- TimeZone clientZone = timeZoneTracker.getClientTimeZone();
- TimeZone defaultZone = TimeZone.getDefault();
- long now = System.currentTimeMillis();
- int offset = defaultZone.getOffset(now) - clientZone.getOffset(now);
-
- c.add(Calendar.MILLISECOND, offset);
-
- parsedValue = c.getTime();
+ parsedValue = convertDateToClientTimeZone(inDefaultTimeZone);
}
- }
- catch (ParseException ex)
+ } catch (ParseException ex)
{
tracker.recordError(this, messages.format("tapx-date-value-not-parseable", value));
return;
@@ -396,12 +401,46 @@ protected void processSubmission(String elementName)
{
fieldValidationSupport.validate(parsedValue, resources, validate);
- this.value = parsedValue;
- }
- catch (ValidationException ex)
+ } catch (ValidationException ex)
{
tracker.recordError(this, ex.getMessage());
+
+ return;
+ }
+
+ if (min != null && parsedValue.before(min)) {
+ tracker.recordError(this, messages.get("tapx-date-value-to-early"));
+ return;
+ }
+
+ if (max != null && parsedValue.after(max)) {
+ tracker.recordError(this, messages.get("tapx-date-value-too-late"));
+ return;
}
+
+ this.value = parsedValue;
+ }
+
+ /**
+ * Converts a Date in default time zone to client's time zone.
+ *
+ * @param inputDate
+ * a date in the default time zone
+ * @return a date in the client time zone
+ */
+ private Date convertDateToClientTimeZone(Date inputDate)
+ {
+ Calendar c = Calendar.getInstance(locale);
+ c.setTime(inputDate);
+
+ TimeZone clientZone = timeZoneTracker.getClientTimeZone();
+ TimeZone defaultZone = TimeZone.getDefault();
+ long now = System.currentTimeMillis();
+ int offset = defaultZone.getOffset(now) - clientZone.getOffset(now);
+
+ c.add(Calendar.MILLISECOND, offset);
+
+ return c.getTime();
}
private void updateClientTimeZone(String elementName)
@@ -414,6 +453,5 @@ private void updateClientTimeZone(String elementName)
TimeZone tz = TimeZone.getTimeZone(id);
timeZoneTracker.setClientTimeZone(tz);
-
}
}
@@ -1,41 +1,49 @@
-package com.howardlewisship.tapx.datefield.components;
+// Copyright 2011, 2012 Howard M. Lewis Ship
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
-import java.util.TimeZone;
+package com.howardlewisship.tapx.datefield.components;
+import com.howardlewisship.tapx.datefield.services.ClientTimeZoneAnalyzer;
+import com.howardlewisship.tapx.datefield.services.ClientTimeZoneData;
+import com.howardlewisship.tapx.datefield.services.ClientTimeZoneTracker;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.RequestParameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Primary;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-import com.howardlewisship.tapx.datefield.services.ClientTimeZoneAnalyzer;
-import com.howardlewisship.tapx.datefield.services.ClientTimeZoneData;
-import com.howardlewisship.tapx.datefield.services.ClientTimeZoneTracker;
+import java.util.TimeZone;
/**
* Determines if {@linkplain ClientTimeZoneTracker#isClientTimeZoneIdentified() the client has
* identified the time zone} and, if not, adds JavaScript to the page to send the time zone
- * information to the server. The JavaScript will ask for access to geolocation data available on
- * the
- * client (this works in Firefox and Chrome) and will report the client's latitude and longitude.
- * If geolocation data is not available, other date information is used to determine the best
- * matching TimeZone.
- * <p>
+ * information to the server.
+ * <p/>
* Typically, this component is placed into the application's <em>Layout</em> component (a common
* component that defines the global layout of the application).
- * <p>
+ * <p/>
* After the time zone is identified, an event, "tapx:time-zone-identified" is triggered on the
* document object. The memo of the event is a JSON object with key "timeZoneId".
- * <p>
+ * <p/>
* TODO: Seems like collecting this information is just part of a larger cycle of determining
* exactly what's running on the client ... imagine if we knew exactly what browser was out there,
* and what features it supported, and could customize to that!
- *
+ *
* @see ClientTimeZoneTracker
* @see ClientTimeZoneAnalyzer
*/
@@ -75,24 +83,17 @@ void beginRender()
}
Object onIdentifyTimeZone(@RequestParameter("offsetMinutes")
- int offsetMinutes, @RequestParameter("dateString")
+ int offsetMinutes, @RequestParameter("dateString")
String dateString, @RequestParameter("epochMillis")
long epochMillis)
{
TimeZone timeZone = timeZoneAnalyzer.extractTimeZone(new ClientTimeZoneData(dateString,
- offsetMinutes, epochMillis, getDouble("latitude"), getDouble("longitude")));
+ offsetMinutes, epochMillis));
tracker.setClientTimeZone(timeZone);
JSONObject response = new JSONObject("timeZoneId", timeZone.getID());
return response;
}
-
- private Double getDouble(String name)
- {
- String value = request.getParameter(name);
-
- return InternalUtils.isBlank(value) ? null : new Double(value);
- }
}
Oops, something went wrong.

0 comments on commit 9eca444

Please sign in to comment.