View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.custom.date;
20  
21  import java.io.Serializable;
22  import java.text.ParseException;
23  import java.util.Calendar;
24  import java.util.Date;
25  import java.util.Locale;
26  import java.util.TimeZone;
27  
28  import javax.faces.component.html.HtmlInputText;
29  import javax.faces.context.FacesContext;
30  import javax.faces.convert.Converter;
31  
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.myfaces.component.AlignProperty;
34  import org.apache.myfaces.component.ForceIdAware;
35  import org.apache.myfaces.component.UserRoleAware;
36  import org.apache.myfaces.component.UserRoleUtils;
37  import org.apache.myfaces.component.html.util.HtmlComponentUtils;
38  import org.apache.myfaces.custom.calendar.DateBusinessConverter;
39  import org.apache.myfaces.custom.calendar.DefaultDateBusinessConverter;
40  
41  /**
42   * Custom input control for dates and times. 
43   * 
44   * Unless otherwise specified, all attributes accept static values or EL expressions.
45   * 
46   * @JSFComponent
47   *   name = "t:inputDate"
48   *   class = "org.apache.myfaces.custom.date.HtmlInputDate"
49   *   tagClass = "org.apache.myfaces.custom.date.HtmlInputDateTag"
50   *   tagSuperclass = "org.apache.myfaces.custom.date.AbstractHtmlInputDateTag"
51   *   tagHandler = "org.apache.myfaces.custom.date.HtmlInputDateTagHandler"
52   *   
53   * @since 1.1.7
54   * @author Sylvain Vieujot (latest modification by $Author: lu4242 $)
55   * @version $Revision: 1327313 $ $Date: 2012-04-17 17:38:42 -0500 (Tue, 17 Apr 2012) $
56   */
57  public abstract class AbstractHtmlInputDate extends HtmlInputText 
58      implements UserRoleAware, ForceIdAware, AlignProperty {
59      public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlInputDate";
60      public static final String COMPONENT_FAMILY = "javax.faces.Input";
61      private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Date";
62      
63      /**
64       * Overriden to support the force id, since the parent is not an extended component 
65       */
66      public String getClientId(FacesContext context)
67      {
68          String clientId = HtmlComponentUtils.getClientId(this, getRenderer(context), context);
69          if (clientId == null)
70          {
71              clientId = super.getClientId(context);
72          }
73  
74          return clientId;
75      }
76          
77      public boolean isRendered(){
78          if (!UserRoleUtils.isVisibleOnUserRole(this)) return false;
79          return super.isRendered();
80      }
81      
82      public UserData getUserData(Locale currentLocale){
83          return new UserData((Date) getDateBusinessConverter(this).getDateValue(getFacesContext(), this, getValue()), currentLocale, getTimeZone(), isAmpm(), getType());
84      }
85      
86      private DateBusinessConverter getDateBusinessConverter(AbstractHtmlInputDate component)
87      {
88          DateBusinessConverter dateBusinessConverter = component.getDateBusinessConverter(); 
89          if (dateBusinessConverter == null)
90          {
91              dateBusinessConverter = new DefaultDateBusinessConverter();
92          }
93          return dateBusinessConverter;
94      }
95  
96      public static class UserData implements Serializable {
97          private static final long serialVersionUID = -6507279524833267707L;
98          private String day;
99          private String month;
100         private String year;
101         private String hours;
102         private String minutes;
103         private String seconds;
104         private TimeZone timeZone = null;
105         private String ampm;
106         private boolean uses_ampm;
107         private String type;
108 
109         public UserData(Date date, Locale currentLocale, String _timeZone, boolean uses_ampm, String type){
110             this.uses_ampm = uses_ampm;
111             this.type = type;
112 
113             Calendar calendar = Calendar.getInstance(currentLocale);
114             if (_timeZone != null) {
115                 timeZone = TimeZone.getTimeZone(_timeZone);
116                 calendar.setTimeZone(timeZone);
117             }
118             
119             if(date == null)
120                 return;
121           
122             calendar.setTime( date );
123             day = Integer.toString(calendar.get(Calendar.DAY_OF_MONTH));
124             month = Integer.toString(calendar.get(Calendar.MONTH)+1);
125             year = Integer.toString(calendar.get(Calendar.YEAR));
126             if (uses_ampm) {
127                 int int_hours = calendar.get(Calendar.HOUR);
128                 // ampm hours must be in range 0-11 to be handled right; we have to handle "12" specially
129                 if (int_hours == 0) {
130                     int_hours = 12;
131                 }
132                 hours = Integer.toString(int_hours);
133                 ampm = Integer.toString(calendar.get(Calendar.AM_PM));
134             } else {
135                 hours = Integer.toString(calendar.get(Calendar.HOUR_OF_DAY));
136             }
137             minutes = Integer.toString(calendar.get(Calendar.MINUTE));
138             seconds = Integer.toString(calendar.get(Calendar.SECOND));
139         }
140 
141         public Date parse() throws ParseException{
142             Date retDate = null;
143             Calendar tempCalendar=Calendar.getInstance();
144             tempCalendar.setLenient(Boolean.FALSE.booleanValue());
145             if (timeZone != null)
146                    tempCalendar.setTimeZone(timeZone);
147             try{
148                 if(!isSubmitValid(uses_ampm, type)) {
149                     return null;
150                 }
151                 //There are this types: date | time | short_time | both | full
152                 if(! (type.equals( "time" ) || type.equals( "short_time" )) ) {
153                     //Set day, month and year for type date, both, full
154                     tempCalendar.set(Calendar.DAY_OF_MONTH,Integer.parseInt(day));
155                     tempCalendar.set(Calendar.MONTH,Integer.parseInt(month)-1);
156                     tempCalendar.set(Calendar.YEAR,Integer.parseInt(year));
157                     
158                     if( type.equals("date") ) {
159                         //Reset hour, minute, second and milisecond to type date
160                         
161                         // According to Calendar javadoc: "... The HOUR_OF_DAY, HOUR 
162                         // and AM_PM fields are handled independently and the the 
163                         // resolution rule for the time of day is applied. 
164                         // Clearing one of the fields doesn't reset the hour of day 
165                         // value of this Calendar. Use set(Calendar.HOUR_OF_DAY, 0)
166                         // to reset the hour value.  
167                         tempCalendar.set(Calendar.HOUR_OF_DAY, 0);
168                         tempCalendar.set(Calendar.MINUTE, 0);
169                         tempCalendar.set(Calendar.SECOND, 0);
170                         tempCalendar.set(Calendar.MILLISECOND, 0);
171                         
172                         // Call to clear sets the given calendar field value and the 
173                         // time value (millisecond offset from the Epoch) of this 
174                         // Calendar undefined.
175                         tempCalendar.clear(Calendar.HOUR);
176                         tempCalendar.clear(Calendar.HOUR_OF_DAY);
177                         tempCalendar.clear(Calendar.AM_PM);
178                         tempCalendar.clear(Calendar.MINUTE);
179                         tempCalendar.clear(Calendar.SECOND);
180                         tempCalendar.clear(Calendar.MILLISECOND);
181 
182                         return new java.sql.Date(tempCalendar.getTimeInMillis());
183                     }
184                 }
185 
186                 if(! type.equals( "date" )) {
187                     //Set hour, ampm, minute, second to
188                     //type time, short_time, both, full
189                     if (uses_ampm) {
190                         int int_hours = Integer.parseInt(hours);
191                         // ampm hours must be in range 0-11 to be handled right; we have to handle "12" specially
192                         if (int_hours == 12) {
193                             int_hours = 0;
194                         }
195                         tempCalendar.set(Calendar.HOUR,int_hours);
196                         tempCalendar.set(Calendar.AM_PM,Integer.parseInt(ampm));
197                     } else {
198                         tempCalendar.set(Calendar.HOUR_OF_DAY,Integer.parseInt(hours));
199                     }
200                     tempCalendar.set(Calendar.MINUTE,Integer.parseInt(minutes));
201                     
202                     if (seconds != null & (type.equals("full") || type.equals("time") || type.equals("short_time"))) {
203                         tempCalendar.set(Calendar.SECOND,Integer.parseInt(seconds));
204                     }
205                     else
206                     {
207                         //Reset seconds for both type
208                         tempCalendar.set(Calendar.SECOND,0);
209                     }
210                 }
211                 tempCalendar.set(Calendar.MILLISECOND, 0);
212                 retDate = tempCalendar.getTime();
213             } catch (NumberFormatException e) {
214                 throw new ParseException(e.getMessage(),0);
215             } catch (IllegalArgumentException e) {
216                 throw new ParseException(e.getMessage(),0);
217             } 
218             return retDate;
219         }
220 
221         private String formatedInt(String toFormat){
222             if( toFormat == null )
223                 return null;
224 
225             int i = -1;
226             try{
227                 i = Integer.parseInt( toFormat );
228             }catch(NumberFormatException nfe){
229                 return toFormat;
230             }
231             if( i >= 0 && i < 10 )
232                 return "0"+i;
233             return Integer.toString(i);
234         }
235         
236         private boolean isDateSubmitted(boolean usesAmpm, String type) {
237             boolean isDateSubmitted = ! (StringUtils.isEmpty(getDay()) && ((getMonth() == null) || getMonth().equals("-1")) && StringUtils.isEmpty(getYear()));
238             if(usesAmpm)
239                 isDateSubmitted = isDateSubmitted || isAmpmSubmitted();
240             return isDateSubmitted;
241         }
242         
243         private boolean isTimeSubmitted(boolean usesAmpm, String type) {
244             boolean isTimeSubmitted = ! (StringUtils.isEmpty(getHours()) && StringUtils.isEmpty(getMinutes()));
245             if(type.equals("time") || type.equals("full"))
246                 isTimeSubmitted = isTimeSubmitted || ! StringUtils.isEmpty(getSeconds());
247             if(usesAmpm)
248                 isTimeSubmitted = isTimeSubmitted || isAmpmSubmitted();
249             return isTimeSubmitted;
250         }
251         
252         private boolean isSubmitValid(boolean usesAmpm, String type) {
253             if(type.equals("date"))
254                 return isDateSubmitted(usesAmpm, type);
255             else if(type.equals("time") || (type.equals("short_time")))
256                 return isTimeSubmitted(usesAmpm, type);
257             else if(type.equals("full") || type.equals("both"))
258                 return isDateSubmitted(usesAmpm, type) || isTimeSubmitted(usesAmpm, type);
259             else
260                 return false;
261         }
262         
263         private boolean isAmpmSubmitted() {
264             if(getAmpm() == null)
265                 return false;
266             else
267                 return ! getAmpm().equals("-1");
268         }
269 
270         public String getDay() {
271             return formatedInt( day );
272         }
273         public void setDay(String day) {
274             this.day = day;
275         }
276 
277         public String getMonth() {
278             return month;
279         }
280         public void setMonth(String month) {
281             this.month = month;
282         }
283 
284         public String getYear() {
285             return year;
286         }
287         public void setYear(String year) {
288             this.year = year;
289         }
290 
291         public String getHours() {
292             return formatedInt( hours );
293         }
294         public void setHours(String hours) {
295             this.hours = hours;
296         }
297         public String getMinutes() {
298             return formatedInt( minutes );
299         }
300         public void setMinutes(String minutes) {
301             this.minutes = minutes;
302         }
303 
304         public String getSeconds() {
305             return formatedInt( seconds );
306         }
307         public void setSeconds(String seconds) {
308             this.seconds = seconds;
309         }
310         
311         public String getAmpm() {
312             return ampm;
313         }
314         public void setAmpm(String ampm) {
315             this.ampm = ampm;
316         }
317         
318         public String getType() {
319             return type;
320         }
321         public void setType(String type) {
322             this.type = type;
323         }
324     }
325 
326     /**
327      * Indicate an object used as a bridge between the java.util.Date instance
328      * used by this component internally and the value object used on the bean,
329      * referred as a "business" value.
330      * 
331      * <ul>
332      * <li>If the value is literal, look for the mentioned class instance, 
333      * create a new instance and assign to the component property.</li>
334      * <li>If it the value a EL Expression, set the expression to the 
335      * component property.</li>
336      * </ul> 
337      * 
338      * @JSFProperty stateHolder="true" inheritedTag="true"
339      */
340     public abstract DateBusinessConverter getDateBusinessConverter();
341     
342     public abstract void setDateBusinessConverter(DateBusinessConverter dateBusinessConverter);
343 
344     /**
345      * @JSFProperty
346      */
347     public abstract String getTimeZone();
348     
349     /**
350      * Specifies the type of value to be accepted. 
351      * Valid values are: date | time | short_time | both | full
352      * 
353      * @JSFProperty
354      *   defaultValue = "date"
355      */
356     public abstract String getType();
357     
358     /**
359      *  If true, use 12hr times with AM/PM selector; if false, use 24hr time. Default false.
360      * 
361      * @JSFProperty
362      *   defaultValue = "false"
363      */
364     public abstract boolean isAmpm();
365     
366     /**
367      * @JSFProperty
368      *   defaultValue = "false"
369      */ 
370     public abstract boolean isPopupCalendar();
371         
372     /**
373      * Label to be used when displaying an empty month selection
374      * 
375      * @JSFProperty
376      *   defaultValue = "\"\""
377      */ 
378     public abstract String getEmptyMonthSelection();
379         
380     /**
381      * Label to be used when displaying an empty ampm selection
382      * 
383      * @JSFProperty
384      *   defaultValue = "\"\""
385      */    
386     public abstract String getEmptyAmpmSelection();
387 
388     /**
389      * HTML: When true, indicates that this component cannot be modified by the user. 
390      * The element may receive focus unless it has also been disabled.
391      * 
392      * @JSFProperty
393      */
394     public abstract boolean isReadonly();
395 
396     /**
397      * HTML: When true, this element cannot receive focus.
398      * 
399      * @JSFProperty
400      *   defaultValue = "false"
401      */    
402     public abstract boolean isDisabled();
403 
404     /**
405      * Retrieve the converter used by this component. 
406      * <p>
407      * If no converter is selected, submitted values are converted to 
408      * its inner class UserData on decode method.
409      * </p>
410      * <p>
411      * If some converter is used, submitted values are decoded as
412      * a String with the following format:
413      * </p>
414      * <p></p>
415      * <p>year=yyyy</p>
416      * <p>month=mm</p>
417      * <p>day=dd</p>
418      * <p>hours=hh</p>
419      * <p>minutes=mm</p>
420      * <p>seconds=ss</p>
421      * <p>ampm=ampm</p>
422      * <p></p>
423      * <p>
424      * Note that submitted values could be wrong and it is necessary to
425      * restore values on render response phase. The converter receive 
426      * a string with this format on getAsObject method and it is expected
427      * the converter encode it on getAsString method, so the renderer can
428      * restore the submitted values correctly.
429      * </p>
430      * 
431      * @JSFProperty
432      */
433     public Converter getConverter()
434     {
435         return super.getConverter();
436     }
437 }