001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.form;
016    
017    import java.text.DateFormatSymbols;
018    import java.text.SimpleDateFormat;
019    import java.util.Calendar;
020    import java.util.Date;
021    import java.util.HashMap;
022    import java.util.Locale;
023    import java.util.Map;
024    
025    import org.apache.tapestry.IAsset;
026    import org.apache.tapestry.IMarkupWriter;
027    import org.apache.tapestry.IRequestCycle;
028    import org.apache.tapestry.IScript;
029    import org.apache.tapestry.PageRenderSupport;
030    import org.apache.tapestry.TapestryUtils;
031    import org.apache.tapestry.form.translator.DateTranslator;
032    import org.apache.tapestry.valid.ValidatorException;
033    
034    /**
035     * Provides a Form <tt>java.util.Date</tt> field component for selecting dates. [ <a
036     * href="../../../../../ComponentReference/DatePicker.html">Component Reference </a>] As of 4.0,
037     * DatePicker can indicate that it is required, use a custom translator (e.g. for java.sql.Date),
038     * and perform validation on the submitted date.
039     * <p>
040     * As of 4.0, this component can be configurably translated and validated.
041     * 
042     * @author Paul Geerts
043     * @author Malcolm Edgar
044     * @author Paul Ferraro
045     * @since 2.2
046     */
047    
048    public abstract class DatePicker extends AbstractFormComponent implements TranslatedField
049    {
050        private static final String SYM_NAME = "name";
051    
052        private static final String SYM_FORMNAME = "formName";
053    
054        private static final String SYM_MONTHNAMES = "monthNames";
055    
056        private static final String SYM_SHORT_MONTHNAMES = "shortMonthNames";
057    
058        private static final String SYM_WEEKDAYNAMES = "weekDayNames";
059    
060        private static final String SYM_SHORT_WEEKDAYNAMES = "shortWeekDayNames";
061    
062        private static final String SYM_FIRSTDAYINWEEK = "firstDayInWeek";
063    
064        private static final String SYM_MINDAYSINFIRSTWEEK = "minimalDaysInFirstWeek";
065    
066        private static final String SYM_FORMAT = "format";
067    
068        private static final String SYM_INCL_WEEK = "includeWeek";
069    
070        private static final String SYM_CLEAR_BUTTON_LABEL = "clearButtonLabel";
071    
072        private static final String SYM_VALUE = "value";
073    
074        private static final String SYM_BUTTONONCLICKHANDLER = "buttonOnclickHandler";
075        
076        public abstract Date getValue();
077    
078        public abstract void setValue(Date value);
079    
080        public abstract boolean isDisabled();
081    
082        public abstract boolean getIncludeWeek();
083    
084        public abstract IAsset getIcon();
085        
086        public abstract String getImageClass();
087        
088        /** 
089         * @since 4.1.1
090         */    
091        public abstract String getTitle();
092    
093        /**
094         * Injected.
095         * 
096         * @since 4.0
097         */
098        public abstract IScript getScript();
099    
100        /**
101         * @see org.apache.tapestry.form.AbstractFormComponent#renderFormComponent(org.apache.tapestry.IMarkupWriter,
102         *      org.apache.tapestry.IRequestCycle)
103         */
104        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
105        {
106            PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, this);
107    
108            boolean disabled = isDisabled();
109            DateTranslator translator = (DateTranslator) getTranslator();
110            Locale locale = getPage().getLocale();
111            SimpleDateFormat format = translator.getDateFormat(locale);
112    
113            DateFormatSymbols dfs = format.getDateFormatSymbols();
114            Calendar cal = Calendar.getInstance(locale);
115    
116            String name = getName();
117            
118            String title = getTitle();
119            if (title == null)
120                title = format.toLocalizedPattern();
121    
122            String value = getTranslatedFieldSupport().format(this, getValue());
123    
124            Map symbols = new HashMap();
125    
126            symbols.put(SYM_NAME, name);
127            symbols.put(SYM_FORMAT, format.toPattern());
128            symbols.put(SYM_INCL_WEEK, getIncludeWeek() ? Boolean.TRUE : Boolean.FALSE);
129    
130            symbols.put(SYM_MONTHNAMES, makeStringList(dfs.getMonths(), 0, 12));
131            symbols.put(SYM_SHORT_MONTHNAMES, makeStringList(dfs.getShortMonths(), 0, 12));
132            symbols.put(SYM_WEEKDAYNAMES, makeStringList(dfs.getWeekdays(), 1, 8));
133            symbols.put(SYM_SHORT_WEEKDAYNAMES, makeStringList(dfs.getShortWeekdays(), 1, 8));
134            symbols.put(SYM_FIRSTDAYINWEEK, new Integer(cal.getFirstDayOfWeek() - 1));
135            symbols.put(SYM_MINDAYSINFIRSTWEEK, new Integer(cal.getMinimalDaysInFirstWeek()));
136            symbols.put(SYM_CLEAR_BUTTON_LABEL, getMessages().getMessage("clear"));
137            symbols.put(SYM_FORMNAME, getForm().getName());
138            symbols.put(SYM_VALUE, getValue());
139            
140            getScript().execute(this, cycle, pageRenderSupport, symbols);
141            
142            renderDelegatePrefix(writer, cycle);
143    
144            writer.beginEmpty("input");
145            writer.attribute("type", "text");
146            writer.attribute("name", name);
147            writer.attribute("value", value);
148            writer.attribute("title", title);
149    
150            if (disabled)
151                writer.attribute("disabled", "disabled");
152    
153            renderIdAttribute(writer, cycle);
154    
155            renderDelegateAttributes(writer, cycle);
156    
157            getTranslatedFieldSupport().renderContributions(this, writer, cycle);
158            getValidatableFieldSupport().renderContributions(this, writer, cycle);
159    
160            renderInformalParameters(writer, cycle);
161    
162            writer.printRaw("&nbsp;");
163    
164            if (!disabled)
165            {
166                writer.begin("a");
167                writer.attribute("href", (String) symbols.get(SYM_BUTTONONCLICKHANDLER));
168            }
169    
170            IAsset icon = getIcon();
171    
172            writer.beginEmpty("img");
173            writer.attribute("src", icon.buildURL());
174            writer.attribute("alt", getMessages().getMessage("alt"));
175            writer.attribute("border", 0);
176            writer.attribute("class", getImageClass());
177            
178            if (!disabled)
179                writer.end();
180    
181            renderDelegateSuffix(writer, cycle);
182        }
183    
184        /**
185         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter,
186         *      org.apache.tapestry.IRequestCycle)
187         */
188        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
189        {
190            String value = cycle.getParameter(getName());
191    
192            try
193            {
194                Date date = (Date) getTranslatedFieldSupport().parse(this, value);
195    
196                getValidatableFieldSupport().validate(this, writer, cycle, date);
197    
198                setValue(date);
199            }
200            catch (ValidatorException e)
201            {
202                getForm().getDelegate().record(e);
203            }
204        }
205    
206        /**
207         * Create a list of quoted strings. The list is suitable for initializing a JavaScript array.
208         */
209        private String makeStringList(String[] a, int offset, int length)
210        {
211            StringBuffer b = new StringBuffer();
212            for (int i = offset; i < length; i++)
213            {
214                // JavaScript is sensitive to some UNICODE characters. So for
215                // the sake of simplicity, we just escape everything
216                b.append('"');
217                char[] ch = a[i].toCharArray();
218                for (int j = 0; j < ch.length; j++)
219                {
220                    if (ch[j] < 128)
221                    {
222                        b.append(ch[j]);
223                    }
224                    else
225                    {
226                        b.append(escape(ch[j]));
227                    }
228                }
229    
230                b.append('"');
231                if (i < length - 1)
232                {
233                    b.append(", ");
234                }
235            }
236            return b.toString();
237    
238        }
239    
240        /**
241         * Create an escaped Unicode character.
242         * 
243         * @param c
244         * @return The unicode character in escaped string form
245         */
246        private static String escape(char c)
247        {
248            char unescapedChar = c;
249            StringBuffer b = new StringBuffer();
250            for (int i = 0; i < 4; i++)
251            {
252                b.append(Integer.toHexString(unescapedChar & 0x000F).toUpperCase());
253                unescapedChar >>>= 4;
254            }
255            b.append("u\\");
256            return b.reverse().toString();
257        }
258    
259        /**
260         * Injected.
261         */
262        public abstract ValidatableFieldSupport getValidatableFieldSupport();
263    
264        /**
265         * Injected.
266         */
267        public abstract TranslatedFieldSupport getTranslatedFieldSupport();
268    
269        /**
270         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
271         */
272        public boolean isRequired()
273        {
274            return getValidatableFieldSupport().isRequired(this);
275        }
276    }