View Javadoc

1   /*
2    * $Id: DateTimePicker.java 651946 2008-04-27 13:41:38Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.dojo.components;
23  
24  import java.text.DateFormat;
25  import java.text.Format;
26  import java.text.MessageFormat;
27  import java.text.SimpleDateFormat;
28  import java.util.ArrayList;
29  import java.util.Calendar;
30  import java.util.Date;
31  import java.util.List;
32  import java.util.Random;
33  
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.apache.struts2.components.UIBean;
38  import org.apache.struts2.views.annotations.StrutsTag;
39  import org.apache.struts2.views.annotations.StrutsTagAttribute;
40  import org.apache.struts2.views.annotations.StrutsTagSkipInheritance;
41  
42  import com.opensymphony.xwork2.util.ValueStack;
43  import com.opensymphony.xwork2.util.logging.Logger;
44  import com.opensymphony.xwork2.util.logging.LoggerFactory;
45  
46  /***
47   * <!-- START SNIPPET: javadoc -->
48   * <p>
49   * Renders a date/time picker in a dropdown container.
50   * </p>
51   * <p>
52   * A stand-alone DateTimePicker widget that makes it easy to select a date/time, or increment by week, month,
53   * and/or year.
54   * </p>
55   *
56   * <p>
57   * It is possible to customize the user-visible formatting with either the
58   * 'formatLength' (long, short, medium or full) or 'displayFormat' attributes. By defaulty current
59   * locale will be used.</p>
60   * </p>
61   * 
62   * Syntax supported by 'displayFormat' is (http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns):-
63   * <table border="1">
64   *   <tr>
65   *      <td>Format</td>
66   *      <td>Description</td>
67   *   </tr>
68   *   <tr>
69   *      <td>d</td>
70   *      <td>Day of the month</td>
71   *   </tr>
72   *   <tr>
73   *      <td>D</td>
74   *      <td>Day of year</td>
75   *   </tr>
76   *   <tr>
77   *      <td>M</td>
78   *      <td>Month - Use one or two for the numerical month, three for the abbreviation, or four for the full name, or 5 for the narrow name.</td>
79   *   </tr>
80   *   <tr>
81   *      <td>y</td>
82   *      <td>Year</td>
83   *   </tr>
84   *   <tr>
85   *      <td>h</td>
86   *      <td>Hour [1-12].</td>
87   *   </tr>
88   *   <tr>
89   *      <td>H</td>
90   *      <td>Hour [0-23].</td>
91   *   </tr>
92   *   <tr>
93   *      <td>m</td>
94   *      <td>Minute. Use one or two for zero padding.</td>
95   *   </tr>
96   *   <tr>
97   *      <td>s</td>
98   *      <td>Second. Use one or two for zero padding.</td>
99   *   </tr>
100  * </table>
101  * 
102  * <p>
103  * The value sent to the server is a locale-independent value, in a hidden field as defined 
104  * by the name attribute. The value will be formatted conforming to RFC3 339 
105  * (yyyy-MM-dd'T'HH:mm:ss)
106  * </p>
107  * <p>
108  * The following formats(in order) will be used to parse the values of the attributes 'value', 
109  * 'startDate' and 'endDate':
110  * </p>
111  * <ul>
112  *   <li>SimpleDateFormat built using RFC 3339 (yyyy-MM-dd'T'HH:mm:ss)
113  *   <li>SimpleDateFormat.getTimeInstance(DateFormat.SHORT)
114  *   <li>SimpleDateFormat.getDateInstance(DateFormat.SHORT)
115  *   <li>SimpleDateFormat.getDateInstance(DateFormat.MEDIUM)
116  *   <li>SimpleDateFormat.getDateInstance(DateFormat.FULL)
117  *   <li>SimpleDateFormat.getDateInstance(DateFormat.LONG)
118  *   <li>SimpleDateFormat built using the value of the 'displayFormat' attribute(if any)
119  * </ul>
120  * <!-- END SNIPPET: javadoc -->
121  *
122  * <b>Examples</b>
123  *
124  * <pre>
125  * <!-- START SNIPPET: example1 -->
126  *   &lt;sx:datetimepicker name="order.date" label="Order Date" /&gt;
127  *   &lt;sx:datetimepicker name="delivery.date" label="Delivery Date" displayFormat="yyyy-MM-dd"  /&gt;
128  *   &lt;sx:datetimepicker name="delivery.date" label="Delivery Date" value="%{date}"  /&gt;
129  *   &lt;sx:datetimepicker name="delivery.date" label="Delivery Date" value="%{'2007-01-01'}"  /&gt;
130  *   &lt;sx:datetimepicker name="order.date" label="Order Date" value="%{'today'}"/&gt;
131  * <!-- END SNIPPET: example1 -->
132  * </pre>
133  * 
134  * <!-- START SNIPPET: example2 -->
135  * &lt;sx:datetimepicker id="picker" label="Order Date" /&gt;
136  * &lt;script type="text/javascript"&gt;
137  *   function setValue() {
138  *      var picker = dojo.widget.byId("picker");
139  *      
140  *      //string value
141  *      picker.setValue('2007-01-01');
142  *      
143  *      //Date value
144  *      picker.setValue(new Date());
145  *   }
146  *   
147  *   function showValue() {
148  *      var picker = dojo.widget.byId("picker");
149  *      
150  *      //string value
151  *      var stringValue = picker.getValue();
152  *      alert(stringValue);
153  *      
154  *      //date value
155  *      var dateValue = picker.getDate();
156  *      alert(dateValue);
157  *   }
158  * &lt;/script&gt;
159  * <!-- END SNIPPET: example2 -->
160  * 
161  * <!-- START SNIPPET: example3 -->
162  * &lt;sx:datetimepicker id="picker" label="Order Date" valueNotifyTopics="/value"/&gt;
163  * 
164  * &lt;script type="text/javascript"&gt;
165  * dojo.event.topic.subscribe("/value", function(textEntered, date, widget){
166  *     alert('value changed');
167  *     //textEntered: String enetered in the textbox
168  *     //date: JavaScript Date object with the value selected
169  *     //widet: widget that published the topic 
170  * });
171  * &lt;/script&gt;  
172  * <!-- END SNIPPET: example3 -->
173  */
174 @StrutsTag(name="datetimepicker", tldTagClass="org.apache.struts2.dojo.views.jsp.ui.DateTimePickerTag", description="Render datetimepicker")
175 public class DateTimePicker extends UIBean {
176 
177     final public static String TEMPLATE = "datetimepicker";
178     // SimpleDateFormat is not thread-safe see:
179     //   http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6231579
180     //   http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6178997
181     // solution is to use stateless MessageFormat instead:
182     final private static String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
183     final private static String RFC3339_PATTERN = "{0,date," + RFC3339_FORMAT + "}";
184     final protected static Logger LOG = LoggerFactory.getLogger(DateTimePicker.class);
185     final private static transient Random RANDOM = new Random();    
186     
187     protected String iconPath;
188     protected String formatLength;
189     protected String displayFormat;
190     protected String toggleType;
191     protected String toggleDuration;
192     protected String type;
193 
194     protected String displayWeeks;
195     protected String adjustWeeks;
196     protected String startDate;
197     protected String endDate;
198     protected String weekStartsOn;
199     protected String staticDisplay;
200     protected String dayWidth;
201     protected String language;
202     protected String templateCssPath;
203     protected String valueNotifyTopics;
204     
205     public DateTimePicker(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
206         super(stack, request, response);
207     }
208 
209     protected String getDefaultTemplate() {
210         return TEMPLATE;
211     }
212 
213     public void evaluateParams() {
214         super.evaluateParams();
215 
216         if(displayFormat != null)
217             addParameter("displayFormat", findString(displayFormat));
218         if(displayWeeks != null)
219             addParameter("displayWeeks", findString(displayWeeks));
220         if(adjustWeeks != null)
221             addParameter("adjustWeeks", findValue(adjustWeeks, Boolean.class));
222 
223         if(disabled != null)
224             addParameter("disabled", findValue(disabled, Boolean.class));
225 
226         if(startDate != null)
227             addParameter("startDate", format(findValue(startDate)));
228         if(endDate != null)
229             addParameter("endDate", format(findValue(endDate)));
230         if(weekStartsOn != null)
231             addParameter("weekStartsOn", findString(weekStartsOn));
232         if(staticDisplay != null)
233             addParameter("staticDisplay", findValue(staticDisplay, Boolean.class));
234         if(dayWidth != null)
235             addParameter("dayWidth", findValue(dayWidth, Integer.class));
236         if(language != null)
237             addParameter("language", findString(language));
238         if(value != null) 
239             addParameter("value", format(findValue(value)));
240    
241         if(iconPath != null)
242             addParameter("iconPath", findString(iconPath));
243         if(formatLength != null)
244             addParameter("formatLength", findString(formatLength));
245         if(toggleType != null)
246             addParameter("toggleType", findString(toggleType));
247         if(toggleDuration != null)
248             addParameter("toggleDuration", findValue(toggleDuration,
249                     Integer.class));
250         if(type != null)
251             addParameter("type", findString(type));
252         else
253             addParameter("type", "date");
254         if(templateCssPath != null)
255             addParameter("templateCssPath", findString(templateCssPath));
256         if(valueNotifyTopics != null)
257             addParameter("valueNotifyTopics", findString(valueNotifyTopics));
258         
259         // format the value to RFC 3399
260         if(parameters.containsKey("value")) {
261             parameters.put("nameValue", parameters.get("value"));
262         } else {
263             if(name != null) {
264                 addParameter("nameValue", format(findValue(name)));
265             }
266         }
267         
268         // generate a random ID if not explicitly set and not parsing the content
269         Boolean parseContent = (Boolean)stack.getContext().get(Head.PARSE_CONTENT);
270         boolean generateId = (parseContent != null ? !parseContent : true);
271         
272         addParameter("pushId", generateId);
273         if ((this.id == null || this.id.length() == 0) && generateId) {
274             // resolves Math.abs(Integer.MIN_VALUE) issue reported by FindBugs 
275             // http://findbugs.sourceforge.net/bugDescriptions.html#RV_ABSOLUTE_VALUE_OF_RANDOM_INT
276             int nextInt = RANDOM.nextInt();
277             nextInt = nextInt == Integer.MIN_VALUE ? Integer.MAX_VALUE : Math.abs(nextInt);  
278             this.id = "widget_" + String.valueOf(nextInt);
279             addParameter("id", this.id);
280         }
281     }
282     
283     @Override
284     @StrutsTagSkipInheritance
285     public void setTheme(String theme) {
286         super.setTheme(theme);
287     }
288 
289     @Override
290     public String getTheme() {
291         return "ajax";
292     }
293     
294     @StrutsTagAttribute(description="If true, weekly size of calendar changes to acomodate the month if false," +
295                 " 42 day format is used", type="Boolean", defaultValue="false")
296     public void setAdjustWeeks(String adjustWeeks) {
297         this.adjustWeeks = adjustWeeks;
298     }
299 
300     @StrutsTagAttribute(description="How to render the names of the days in the header(narrow, abbr or wide)", defaultValue="narrow")
301     public void setDayWidth(String dayWidth) {
302         this.dayWidth = dayWidth;
303     }
304 
305     @StrutsTagAttribute(description="Total weeks to display", type="Integer", defaultValue="6")
306     public void setDisplayWeeks(String displayWeeks) {
307         this.displayWeeks = displayWeeks;
308     }
309 
310     @StrutsTagAttribute(description="Last available date in the calendar set", type="Date", defaultValue="2941-10-12")
311     public void setEndDate(String endDate) {
312         this.endDate = endDate;
313     }
314 
315     @StrutsTagAttribute(description="First available date in the calendar set", type="Date", defaultValue="1492-10-12")
316     public void setStartDate(String startDate) {
317         this.startDate = startDate;
318     }
319 
320     @StrutsTagAttribute(description="Disable all incremental controls, must pick a date in the current display", type="Boolean", defaultValue="false")
321     public void setStaticDisplay(String staticDisplay) {
322         this.staticDisplay = staticDisplay;
323     }
324 
325     @StrutsTagAttribute(description="Adjusts the first day of the week 0==Sunday..6==Saturday", type="Integer", defaultValue="0")
326     public void setWeekStartsOn(String weekStartsOn) {
327         this.weekStartsOn = weekStartsOn;
328     }
329 
330     @StrutsTagAttribute(description="Language to display this widget in", defaultValue="brower's specified preferred language")
331     public void setLanguage(String language) {
332         this.language = language;
333     }
334     
335     @StrutsTagAttribute(description="A pattern used for the visual display of the formatted date, e.g. dd/MM/yyyy")
336     public void setDisplayFormat(String displayFormat) {
337         this.displayFormat = displayFormat;
338     }
339 
340     @StrutsTagAttribute(description="Type of formatting used for visual display. Possible values are " +
341                 "long, short, medium or full", defaultValue="short")
342     public void setFormatLength(String formatLength) {
343         this.formatLength = formatLength;
344     }
345 
346     @StrutsTagAttribute(description="Path to icon used for the dropdown")
347     public void setIconPath(String iconPath) {
348         this.iconPath = iconPath;
349     }
350 
351     @StrutsTagAttribute(description="Duration of toggle in milliseconds", type="Integer", defaultValue="100")
352     public void setToggleDuration(String toggleDuration) {
353         this.toggleDuration = toggleDuration;
354     }
355 
356     @StrutsTagAttribute(description="Defines the type of the picker on the dropdown. Possible values are 'date'" +
357                 " for a DateTimePicker, and 'time' for a timePicker", defaultValue="date")
358     public void setType(String type) {
359         this.type = type;
360     }
361 
362     @StrutsTagAttribute(description="oggle type of the dropdown. Possible values are plain,wipe,explode,fade", defaultValue="plain")
363     public void setToggleType(String toggleType) {
364         this.toggleType = toggleType;
365     }
366     
367     @StrutsTagAttribute(description="Template css path")
368     public void setTemplateCssPath(String templateCssPath) {
369         this.templateCssPath = templateCssPath;
370     }
371     
372     @StrutsTagAttribute(description="Preset the value of input element")
373     public void setValue(String arg0) {
374         super.setValue(arg0);
375     }
376     
377     @StrutsTagAttribute(description="Comma delimmited list of topics that will published when a value is selected")
378     public void setValueNotifyTopics(String valueNotifyTopics) {
379         this.valueNotifyTopics = valueNotifyTopics;
380     }
381     
382     private String format(Object obj) {
383         if(obj == null)
384             return null;
385 
386         if(obj instanceof Date) {
387             return MessageFormat.format(RFC3339_PATTERN, (Date) obj);
388         } else if(obj instanceof Calendar) {
389             return MessageFormat.format(RFC3339_PATTERN, ((Calendar) obj).getTime());
390         }
391         else {
392             // try to parse a date
393             String dateStr = obj.toString();
394             if(dateStr.equalsIgnoreCase("today"))
395                 return MessageFormat.format(RFC3339_PATTERN, new Date());
396 
397             
398             Date date = null;
399             //formats used to parse the date
400             List<DateFormat> formats = new ArrayList<DateFormat>();
401             formats.add(new SimpleDateFormat(RFC3339_FORMAT));
402             formats.add(SimpleDateFormat.getTimeInstance(DateFormat.SHORT));
403             formats.add(SimpleDateFormat.getDateInstance(DateFormat.SHORT));
404             formats.add(SimpleDateFormat.getDateInstance(DateFormat.MEDIUM));
405             formats.add(SimpleDateFormat.getDateInstance(DateFormat.FULL));
406             formats.add(SimpleDateFormat.getDateInstance(DateFormat.LONG));
407             if (this.displayFormat != null) {
408                 try {
409                     SimpleDateFormat displayFormat = new SimpleDateFormat(
410                         (String) getParameters().get("displayFormat"));
411                     formats.add(displayFormat);
412                 } catch (Exception e) {
413                     // don't use it then (this attribute is used by Dojo, not java code)
414                     LOG.error("Cannot use attribute", e);
415                 }
416             }
417             
418             for (DateFormat format : formats) {
419                 try {
420                     date = format.parse(dateStr);
421                     if (date != null)
422                         return MessageFormat.format(RFC3339_PATTERN, date);
423                 } catch (Exception e) {
424                     //keep going
425                 }
426             }
427             
428            // last resource, assume already in correct/default format
429            if (LOG.isDebugEnabled())
430                LOG.debug("Unable to parse date " + dateStr);
431            return dateStr;
432         }
433     }
434 
435 }