001    /*
002     * This file is part of the Echo Point Project.  This project is a
003     * collection of Components that have extended the Echo Web Application
004     * Framework Version 3.
005     *
006     * Version: MPL 1.1
007     *
008     * The contents of this file are subject to the Mozilla Public License Version
009     * 1.1 (the "License"); you may not use this file except in compliance with
010     * the License. You may obtain a copy of the License at
011     * http://www.mozilla.org/MPL/
012     *
013     * Software distributed under the License is distributed on an "AS IS" basis,
014     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
015     * for the specific language governing rights and limitations under the
016     * License.
017     */
018    package echopoint.jquery;
019    
020    import nextapp.echo.app.*;
021    
022    import java.util.Date;
023    import java.util.Calendar;
024    import java.text.SimpleDateFormat;
025    import java.text.ParseException;
026    import java.io.IOException;
027    import java.io.InputStreamReader;
028    import echopoint.able.Sizeable;
029    import echopoint.able.Alignable;
030    import java.util.logging.Level;
031    import java.util.logging.Logger;
032    
033    
034    /**
035     * <code>DateField</code> is a drop down component that contains a text field and a drop down calendar.
036     * The text field is updated with the contents of the DateField calendar.
037     * DateField is both a date- and time picker.
038     * If the component should be used as a time-picker the DateFormat must include an hour field, e.g. setDateFormat("dd.MM.yyyy HH:mm")
039     * It is based on the jQuery plugin DynDateTime
040     *
041     * @author Hans Holmlund - 2009-04-03
042     * @version $Id: DateField.java 242 2009-09-29 20:16:11Z aschild $ 
043     */
044    public class DateField extends Component implements Sizeable, Alignable
045    {
046    
047        /** The logger to use to log the download progress. */
048        protected static final Logger logger = Logger.getAnonymousLogger();
049    
050        private static final String USETIME_PROPERTY = "useTime";
051        public static final String DATE_CHANGED_PROPERTY = "date";
052        public static final String PROPERTY_EDITABLE = "editable";    
053        public static final String PROPERTY_DATEFORMAT = "dateFormat";
054        public static final String PROPERTY_BORDER = "border";
055        public static final String PROPERTY_CALENDAR_ICON = "icon";
056        public static final String PROPERTY_CSS = "css";
057        public static final String PROPERTY_INSETS = "insets";
058        public static final String PROPERTY_LANGUAGE = "language";
059        public static final ImageReference imageReference = new ResourceImageReference("/resource/images/calendar.gif");
060        public static final String cssReference = getFileAsString("resource/js/jquery/calendar-win2k-cold-2.css");
061        public static final String PROPERTY_INPUT_WIDTH = "inputWidth";
062        public static final String PROPERTY_INPUT_HEIGHT = "inputHeight";
063        public static final String PROPERTY_SHOW_WEEKS = "showWeeks";
064        public static final String PROPERTY_FIRST_DAY_OF_WEEK = "firstDayOfWeek";
065    
066    
067        /**
068         * The selected date.
069         */
070        private Date date;
071        private String dateFormatPattern = "yyyy-MM-dd HH:mm";
072        private SimpleDateFormat dateFormatter;
073    
074        /**
075         * Creates a new <code>CalendarSelect</code>.
076         */
077        public DateField() {
078            this((Date)null);
079        }
080    
081        public DateField(Calendar calendar) {
082            this(calendar.getTime());
083        }
084    
085        /**
086         * Creates a new <code>CalendarSelect</code>.
087         *
088         * @param date the initially selected date
089         */
090        public DateField(Date date) {
091            super();
092            this.date = date;
093            setDateFormat(dateFormatPattern);
094            setIcon(imageReference);
095            setCSS(cssReference);
096    //        set(PROPERTY_LANGUAGE, defaultLanguage);
097        }
098    
099        private void appendPattern(StringBuffer datePattern, char currentChar, int numberOfThisChar) {
100            switch (currentChar) {
101                case 'y': {
102                    if (numberOfThisChar >= 3) {
103                        datePattern.append("%").append("Y");
104                    }
105                    else {
106                        datePattern.append("%").append("y");
107                    }
108                    break;
109                }
110                case 'M': {
111                    if (numberOfThisChar >= 4) {
112                        datePattern.append("%").append("B");
113                    }
114                    if (numberOfThisChar == 3) {
115                        datePattern.append("%").append("b");
116                    }
117                    else {
118                        datePattern.append("%").append("m");
119                    }
120                    break;
121                }
122                case 'w': {
123                    datePattern.append("%").append("W");
124                    break;
125                }
126                case 'G': {
127                    throw new IllegalArgumentException("Era designator is not allowed");
128                }
129                case 'W': {
130                    throw new IllegalArgumentException("Week in month is not allowed");
131                }
132                case 'D': {
133                    datePattern.append("%").append("j");
134                    break;
135                }
136                case 'd': {
137                    datePattern.append("%").append("d");
138                    break;
139                }
140                case 'F': {
141                    throw new IllegalArgumentException("Day of week in month is not allowed");
142                }
143                case 'E': {
144                    if (numberOfThisChar > 3) {
145                        datePattern.append("%").append("A");
146                    }
147                    else {
148                        datePattern.append("%").append("a");
149                    }
150                    break;
151                }
152                case 'a': {
153                    datePattern.append("%").append("p");
154                    break;
155                }
156                case 'H': {
157                    datePattern.append("%").append("H");
158                    break;
159                }
160                case 'k': {
161                    throw new IllegalArgumentException("Hour in day (1-24) is not allowed. Use 'H' instead.");
162                }
163                case 'K': {
164                    throw new IllegalArgumentException("Hour in am/pm (0-11) is not allowed. Use 'h' instead.");
165                }
166                case 'h': {
167                    datePattern.append("%").append("l");
168                    break;
169                }
170                case 'm': {
171                    datePattern.append("%").append("M");
172                    break;
173                }
174                case 's': {
175                    datePattern.append("%").append("S");
176                    break;
177                }
178                case 'S': {
179                    throw new IllegalArgumentException("Milliseconds is not allowed..");
180                }
181                case 'z':
182                case 'Z':{
183                    throw new IllegalArgumentException("Time zone is not allowed..");
184                }
185                default:
186                    datePattern.append(currentChar);
187            }
188        }
189    
190        private String convertDateFormat(String simpleDateFormat) {
191            StringBuffer datePattern = new StringBuffer();
192            char currentChar = 0;
193            int numberOfThisChar = 0;
194            for (char ch : simpleDateFormat.toCharArray()) {
195                if (currentChar == 0) {
196                    currentChar = ch;
197                    numberOfThisChar = 1;
198                }
199                else if (ch == currentChar) {
200                    numberOfThisChar++;
201                }
202                else {
203                    appendPattern(datePattern, currentChar, numberOfThisChar);
204                    currentChar = ch;
205                    numberOfThisChar = 1;
206                }
207            }
208            appendPattern(datePattern, currentChar, numberOfThisChar);
209            return datePattern.toString();
210        }
211    
212        /**
213         * Sets the date format for this calendar.
214         * Depending on the date format the USETIME property will be automatically set.
215         * @param dateFormat
216         * @throws IllegalArgumentException
217         */
218        public void setDateFormat(String dateFormat) throws IllegalArgumentException {
219            String jsDateFormatPattern = convertDateFormat(dateFormat);
220            set(PROPERTY_DATEFORMAT, jsDateFormatPattern);
221            dateFormatPattern = dateFormat;
222            dateFormatter = new SimpleDateFormat(dateFormatPattern);
223            if (jsDateFormatPattern.contains("%H") || jsDateFormatPattern.contains("%l")) {
224                set(USETIME_PROPERTY, true);
225            }
226            else {
227                set(USETIME_PROPERTY, false);
228            }
229        }
230    
231        public String getDateFormat() {
232            return dateFormatPattern;
233        }
234    
235        /**
236         * Sets the inset margin of the content.
237         * Values may only be specified in pixel-based units.
238         *
239         * @param newValue the new inset margin
240         */
241        public void setInsets(Insets newValue) {
242            set(PROPERTY_INSETS, newValue);
243        }
244    
245        /**
246         * Returns the inset margin of the content.
247         *
248         * @return newValue the inset margin
249         */
250        public Insets getInsets() {
251            return (Insets) get(PROPERTY_INSETS);
252        }
253    
254        /**
255         * Sets the icon displayed in the calendar button.
256         *
257         * @param newValue the new icon
258         */
259        public void setIcon(ImageReference newValue) {
260            set(PROPERTY_CALENDAR_ICON, newValue);
261        }
262    
263        /**
264         * Returns the cascading style sheet for this calendar.
265         *
266         * @return the cascading style sheet
267         */
268        public String getCSS() {
269            return (String) get(PROPERTY_CSS);
270        }
271    
272        /**
273         * Sets the cascading style sheet for this calendar.
274         *
275         * @param newValue the new icon
276         */
277        public void setCSS(String newValue) {
278            set(PROPERTY_CSS, newValue);
279        }
280    
281        /**
282         * Returns the alignment of the DateField component.
283         *
284         * @return the alignment
285         */
286        public Alignment getAlignment() {
287            return (Alignment) get(PROPERTY_ALIGNMENT);
288        }
289    
290        /**
291         * Sets the alignment of the DateField component.
292         *
293         * @param newValue the new alignment
294         */
295        public void setAlignment(Alignment newValue) {
296            set(PROPERTY_ALIGNMENT, newValue);
297        }
298    
299        /**
300         * Returns the icon displayed in the calendar button.
301         *
302         * @return the icon
303         */
304        public ImageReference getIcon() {
305            return (ImageReference) get(PROPERTY_CALENDAR_ICON);
306        }
307    
308        /**
309         * Returns the border surrounding the entire component.
310         *
311         * @return the border
312         */
313        public Border getBorder() {
314            return (Border) get(PROPERTY_BORDER);
315        }
316    
317        /**
318         * Sets the width extent of the DateField component.
319         *
320         * @param newValue - the new width extent of the DateField component
321         */
322        public void setWidth(Extent newValue) {
323            set(PROPERTY_WIDTH,newValue);
324        }
325    
326        /**
327         * Returns the width extent of the DateField component.
328         * @return the width extent of the DateField component.
329         */
330        public Extent getWidth() {
331            return (Extent) get(PROPERTY_WIDTH);
332        }
333    
334        /**
335         * Sets the height extent of the DateField component.
336         *
337         * @param newValue - the new height extent of the DateField component
338         */
339        public void setHeight(Extent newValue) {
340            set(PROPERTY_HEIGHT,newValue);
341        }
342    
343        /**
344         * Returns the height extent of DateField component.
345         *
346         * @return the height extent of DateField component.
347         */
348        public Extent getHeight() {
349            return (Extent) get(PROPERTY_HEIGHT);
350        }
351    
352        /**
353         * Returns the selected date.
354         *
355         * @return the selected date
356         */
357        public Date getDate() {
358            return date;
359        }
360    
361        public String getDateStr() {
362            if (date == null) return null;
363            return dateFormatter.format(date);
364        }
365    
366        /**
367         * @see nextapp.echo.app.Component#processInput(java.lang.String, java.lang.Object)
368         */
369        @Override
370        public void processInput(String inputName, Object inputValue) {
371            if (DATE_CHANGED_PROPERTY.equals(inputName)) {
372                if (inputValue != null) {
373                    try {
374                        setDate(dateFormatter.parse((String)inputValue));
375                    } catch (ParseException e) {
376                        // todo
377                    }
378                }
379                else
380                {
381                    setDate(null); // Set date to null when empty string returned from client side
382                }
383            }
384        }
385    
386        /**
387         * Sets the border surrounding the entire component.
388         *
389         * @param newValue the new border
390         */
391        public void setBorder(Border newValue) {
392            set(PROPERTY_BORDER, newValue);
393        }
394    
395        /**
396         * Sets the selected date.
397         *
398         * @param newValue the new date
399         */
400        public void setDate(Date newValue) {
401            Date oldValue = date;
402            date = newValue;
403            firePropertyChange(DATE_CHANGED_PROPERTY, (oldValue != null ? dateFormatter.format(oldValue) : null), (newValue != null ? dateFormatter.format(newValue) : null));
404        }
405    
406        /**
407         * Sets the editable state of this component.
408         *
409         * @param newValue the new editable state
410         */
411        public void setEditable(boolean newValue) {
412            set(PROPERTY_EDITABLE, Boolean.valueOf(newValue));
413        }
414    
415        /**
416         * Determines the editable state of this component. 
417         *
418         * @return <code>true</code> if this component is editable
419         */
420        public boolean isEditable() {
421            Object property = get(PROPERTY_EDITABLE);
422            return null == property ? true : ((Boolean) property).booleanValue();
423        }
424    
425    
426        public static String getFileAsString(String resource) {
427            InputStreamReader in = null;
428            StringBuffer sb = new StringBuffer();
429    
430            try {
431                in = new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(resource));
432                if (in == null) {
433                    throw new IllegalArgumentException("Specified resource does not exist: " + resource + ".");
434                }
435                int character;
436                while ((character = in.read()) != -1) {
437                    sb.append((char) character);
438                }
439            }
440            catch (Exception e) {
441                logger.log(Level.SEVERE, "Could not load resource <"+resource+">", e);
442            }
443            finally {
444                if (in != null) { try { in.close(); } catch (IOException ex) { } }
445            }
446            return sb.toString();
447        }
448    
449        /**
450         * Returns the input field width.
451         *
452         * @return The input field width
453         */
454        public Extent getInputWidth() {
455            return (Extent) get(PROPERTY_INPUT_WIDTH);
456        }
457    
458        /**
459         * Sets the width of the input field for the date.
460         *
461         * @param newValue the new width of the input field
462         */
463        public void setInputWidth(Extent newValue) {
464            set(PROPERTY_INPUT_WIDTH, newValue);
465        }
466    
467        /**
468         * Returns the input field height.
469         *
470         * @return The input field height
471         */
472        public Extent getInputHeight() {
473            return (Extent) get(PROPERTY_INPUT_HEIGHT);
474        }
475    
476        /**
477         * Sets the editable state of this component.
478         *
479         * @param newValue the new height of the input field
480         */
481        public void setInputHeight(Extent newValue) {
482            set(PROPERTY_INPUT_HEIGHT, newValue);
483        }
484    
485        /**
486         * Returns the first day of the week
487         *
488         * @return The first day of the week. (0=Sunday, 1= Monday etc.)
489         */
490        public int getFirstDayOfWeek() {
491            return (Integer) get(PROPERTY_FIRST_DAY_OF_WEEK);
492        }
493    
494        /**
495         * Sets the the first day of week. Used to calculate week numbers.
496         * Usually 0 for US (Sunday) and 1 for europe (Monday)
497         *
498         * @param newValue  (0=Sunday, 1= Monday etc.)
499         */
500        public void setFirstDayOfWeek(int newValue) {
501            set(PROPERTY_FIRST_DAY_OF_WEEK, newValue);
502        }
503    
504        /**
505         * Returns the status of week number display
506         *
507         * @return The week number display mode
508         */
509        public boolean getShowWeeks() {
510            Object property = get(PROPERTY_SHOW_WEEKS);
511            return null == property ? true : ((Boolean) property).booleanValue();
512        }
513    
514        /**
515         * Sets the display of week numbers
516         *
517         * @param newValue Should week numbers be displayed?
518         */
519        public void setShowWeeks(boolean newValue) {
520            set(PROPERTY_SHOW_WEEKS, newValue);
521        }
522    }