Home » tapestry-src-5.0.19 » org.apache.tapestry5.corelib.components » [javadoc | source]

    1   // Copyright 2007, 2008 The Apache Software Foundation
    2   //
    3   // Licensed under the Apache License, Version 2.0 (the "License");
    4   // you may not use this file except in compliance with the License.
    5   // You may obtain a copy of the License at
    6   //
    7   //     http://www.apache.org/licenses/LICENSE-2.0
    8   //
    9   // Unless required by applicable law or agreed to in writing, software
   10   // distributed under the License is distributed on an "AS IS" BASIS,
   11   // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12   // See the License for the specific language governing permissions and
   13   // limitations under the License.
   14   
   15   package org.apache.tapestry5.corelib.components;
   16   
   17   import org.apache.tapestry5;
   18   import org.apache.tapestry5.annotations.Environmental;
   19   import org.apache.tapestry5.annotations.IncludeJavaScriptLibrary;
   20   import org.apache.tapestry5.annotations.IncludeStylesheet;
   21   import org.apache.tapestry5.annotations.Parameter;
   22   import org.apache.tapestry5.corelib.base.AbstractField;
   23   import org.apache.tapestry5.ioc.annotations.Inject;
   24   import org.apache.tapestry5.ioc.internal.util.InternalUtils;
   25   import org.apache.tapestry5.json.JSONArray;
   26   import org.apache.tapestry5.json.JSONObject;
   27   import org.apache.tapestry5.services.ComponentDefaultProvider;
   28   import org.apache.tapestry5.services.Request;
   29   
   30   import java.text.DateFormat;
   31   import java.text.DateFormatSymbols;
   32   import java.text.ParseException;
   33   import java.text.SimpleDateFormat;
   34   import java.util.Calendar;
   35   import java.util.Date;
   36   import java.util.Locale;
   37   
   38   /**
   39    * A component used to collect a provided date from the user using a client-side JavaScript calendar. Non-JavaScript
   40    * clients can simply type into a text field.
   41    * <p/>
   42    * One wierd aspect here is that, because client-side JavaScript formatting and parsing is so limited, we (currently)
   43    * use Ajax to send the user's input to the server for parsing (before raising the popup) and formatting (after closing
   44    * the popup).  Wierd and inefficient, but easier than writing client-side JavaScript for that purpose.
   45    * <p/>
   46    * Tapestry's DateField component is a wrapper around <a href="http://webfx.eae.net/dhtml/datepicker/datepicker.html">WebFX
   47    * DatePicker</a>.
   48    */
   49   // TODO: More testing; see https://issues.apache.org/jira/browse/TAPESTRY-1844
   50   @IncludeStylesheet("${tapestry.datepicker}/css/datepicker.css")
   51   @IncludeJavaScriptLibrary({"${tapestry.datepicker}/js/datepicker.js",
   52           "datefield.js"
   53           })
   54   public class DateField extends AbstractField
   55   {
   56       /**
   57        * The value parameter of a DateField must be a {@link java.util.Date}.
   58        */
   59       @Parameter(required = true, principal = true, autoconnect = true)
   60       private Date value;
   61   
   62       /**
   63        * Request attribute set to true if localization for the client-side DatePicker has been configured.  Used to ensure
   64        * that this only occurs once, regardless of how many DateFields are on the page.
   65        */
   66       static final String LOCALIZATION_CONFIGURED_FLAG = "tapestry.DateField.localization-configured";
   67   
   68       /**
   69        * The format used to format <em>and parse</em> dates. This is typically specified as a string which is coerced to a
   70        * DateFormat. You should be aware that using a date format with a two digit year is problematic: Java (not
   71        * Tapestry) may get confused about the century.
   72        */
   73       @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.LITERAL)
   74       private DateFormat format;
   75   
   76       /**
   77        * If true, then  the text field will be hidden, and only the icon for the date picker will be visible. The default
   78        * is false.
   79        */
   80       @Parameter
   81       private boolean hideTextField;
   82   
   83       /**
   84        * The object that will perform input validation (which occurs after translation). The translate binding prefix is
   85        * generally used to provide this object in a declarative fashion.
   86        */
   87       @Parameter(defaultPrefix = BindingConstants.VALIDATE)
   88       @SuppressWarnings("unchecked")
   89       private FieldValidator<Object> validate;
   90   
   91       @Parameter(defaultPrefix = BindingConstants.ASSET, value = "datefield.gif")
   92       private Asset icon;
   93   
   94       @Environmental
   95       private RenderSupport support;
   96   
   97       @Environmental
   98       private ValidationTracker tracker;
   99   
  100       @Inject
  101       private ComponentResources resources;
  102   
  103       @Inject
  104       private Request request;
  105   
  106       @Inject
  107       private Locale locale;
  108   
  109       @Inject
  110       private ComponentDefaultProvider defaultProvider;
  111   
  112       @Inject
  113       private FieldValidationSupport fieldValidationSupport;
  114   
  115   
  116       private static final String RESULT = "result";
  117   
  118       private static final String ERROR = "error";
  119       private static final String INPUT_PARAMETER = "input";
  120   
  121   
  122       DateFormat defaultFormat()
  123       {
  124           DateFormat shortDateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
  125   
  126           if (shortDateFormat instanceof SimpleDateFormat)
  127           {
  128               SimpleDateFormat simpleDateFormat = (SimpleDateFormat) shortDateFormat;
  129   
  130               String pattern = simpleDateFormat.toPattern();
  131   
  132               String revised = pattern.replaceAll("([^y])yy$", "$1yyyy");
  133   
  134               return new SimpleDateFormat(revised);
  135           }
  136   
  137           return shortDateFormat;
  138       }
  139   
  140       /**
  141        * Computes a default value for the "validate" parameter using {@link ComponentDefaultProvider}.
  142        */
  143       final Binding defaultValidate()
  144       {
  145           return defaultProvider.defaultValidatorBinding("value", resources);
  146       }
  147   
  148       /**
  149        * Ajax event handler, used when initiating the popup. The client sends the input value form the field to the server
  150        * to parse it according to the server-side format. The response contains a "result" key of the formatted date in a
  151        * format acceptable to the JavaScript Date() constructor.  Alternately, an "error" key indicates the the input was
  152        * not formatted correct.
  153        */
  154       JSONObject onParse()
  155       {
  156           String input = request.getParameter(INPUT_PARAMETER);
  157           JSONObject response = new JSONObject();
  158   
  159           try
  160           {
  161               Date date = format.parse(input);
  162   
  163               response.put(RESULT, date.getTime());
  164           }
  165           catch (ParseException ex)
  166           {
  167               response.put(ERROR, ex.getMessage());
  168           }
  169   
  170           return response;
  171       }
  172   
  173       /**
  174        * Ajax event handler, used after the client-side popup completes. The client sends the date, formatted as
  175        * milliseconds since the epoch, to the server, which reformats it according to the server side format and returns
  176        * the result.
  177        */
  178       JSONObject onFormat()
  179       {
  180           String input = request.getParameter(INPUT_PARAMETER);
  181   
  182           JSONObject response = new JSONObject();
  183   
  184           try
  185           {
  186               long millis = Long.parseLong(input);
  187   
  188               Date date = new Date(millis);
  189   
  190               response.put(RESULT, format.format(date));
  191           }
  192           catch (NumberFormatException ex)
  193           {
  194               response.put(ERROR, ex.getMessage());
  195           }
  196   
  197           return response;
  198       }
  199   
  200       void beginRender(MarkupWriter writer)
  201       {
  202           String value = tracker.getInput(this);
  203   
  204           if (value == null) value = formatCurrentValue();
  205   
  206           String clientId = getClientId();
  207           String triggerId = clientId + ":trigger";
  208   
  209           writer.element(INPUT_PARAMETER,
  210   
  211                          "type", hideTextField ? "hidden" : "text",
  212   
  213                          "name", getControlName(),
  214   
  215                          "id", clientId,
  216   
  217                          "value", value);
  218   
  219           writeDisabled(writer);
  220   
  221           validate.render(writer);
  222   
  223           resources.renderInformalParameters(writer);
  224   
  225           decorateInsideField();
  226   
  227           writer.end();
  228   
  229           // Now the trigger icon.
  230   
  231           writer.element("img",
  232   
  233                          "id", triggerId,
  234   
  235                          "class", "t-calendar-trigger",
  236   
  237                          "src", icon.toClientURL(),
  238   
  239                          "alt", "[Show]");
  240           writer.end(); // img
  241   
  242           JSONObject setup = new JSONObject();
  243   
  244           setup.put("field", clientId);
  245           setup.put("parseURL", resources.createEventLink("parse").toAbsoluteURI());
  246           setup.put("formatURL", resources.createEventLink("format").toAbsoluteURI());
  247   
  248           if (request.getAttribute(LOCALIZATION_CONFIGURED_FLAG) == null)
  249           {
  250               JSONObject spec = new JSONObject();
  251   
  252               DateFormatSymbols symbols = new DateFormatSymbols(locale);
  253   
  254               spec.put("months", new JSONArray(symbols.getMonths()));
  255   
  256               StringBuilder days = new StringBuilder();
  257   
  258               String[] weekdays = symbols.getWeekdays();
  259   
  260               Calendar c = Calendar.getInstance(locale);
  261   
  262               int firstDay = c.getFirstDayOfWeek();
  263   
  264               // DatePicker needs them in order from monday to sunday.
  265   
  266               for (int i = Calendar.MONDAY; i <= Calendar.SATURDAY; i++)
  267               {
  268                   days.append(weekdays[i].substring(0, 1));
  269               }
  270   
  271               days.append(weekdays[Calendar.SUNDAY].substring(0, 1));
  272   
  273               spec.put("days", days.toString().toLowerCase(locale));
  274   
  275               // DatePicker expects 0 to be monday. Calendar defines SUNDAY as 1, MONDAY as 2, etc.
  276   
  277               spec.put("firstDay", firstDay == Calendar.SUNDAY ? 6 : firstDay - 2);
  278   
  279               // TODO: Skip localization if locale is English?
  280   
  281               setup.put("localization", spec);
  282   
  283               request.setAttribute(LOCALIZATION_CONFIGURED_FLAG, true);
  284           }
  285   
  286           support.addInit("dateField", setup);
  287       }
  288   
  289       private void writeDisabled(MarkupWriter writer)
  290       {
  291           if (isDisabled()) writer.attributes("disabled", "disabled");
  292       }
  293   
  294   
  295       private String formatCurrentValue()
  296       {
  297           if (value == null) return "";
  298   
  299           return format.format(value);
  300       }
  301   
  302       @Override
  303       protected void processSubmission(String elementName)
  304       {
  305           String value = request.getParameter(elementName);
  306   
  307           tracker.recordInput(this, value);
  308   
  309           Date parsedValue = null;
  310   
  311           try
  312           {
  313               if (InternalUtils.isNonBlank(value))
  314                   parsedValue = format.parse(value);
  315           }
  316           catch (ParseException ex)
  317           {
  318               tracker.recordError(this, "Date value is not parseable.");
  319               return;
  320           }
  321   
  322           try
  323           {
  324               fieldValidationSupport.validate(parsedValue, resources, validate);
  325   
  326               this.value = parsedValue;
  327           }
  328           catch (ValidationException ex)
  329           {
  330               tracker.recordError(this, ex.getMessage());
  331           }
  332       }
  333   
  334       void injectResources(ComponentResources resources)
  335       {
  336           this.resources = resources;
  337       }
  338   
  339       @Override
  340       public boolean isRequired()
  341       {
  342           return validate.isRequired();
  343       }
  344   }

Home » tapestry-src-5.0.19 » org.apache.tapestry5.corelib.components » [javadoc | source]