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        /** 
087         * @since 4.1.1
088         */    
089        public abstract String getTitle();
090    
091        /**
092         * Injected.
093         * 
094         * @since 4.0
095         */
096        public abstract IScript getScript();
097    
098        /**
099         * @see org.apache.tapestry.form.AbstractFormComponent#renderFormComponent(org.apache.tapestry.IMarkupWriter,
100         *      org.apache.tapestry.IRequestCycle)
101         */
102        protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
103        {
104            PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, this);
105    
106            boolean disabled = isDisabled();
107            DateTranslator translator = (DateTranslator) getTranslator();
108            Locale locale = getPage().getLocale();
109            SimpleDateFormat format = translator.getDateFormat(locale);
110    
111            DateFormatSymbols dfs = format.getDateFormatSymbols();
112            Calendar cal = Calendar.getInstance(locale);
113    
114            String name = getName();
115            
116            String title = getTitle();
117            if (title == null)
118                title = format.toLocalizedPattern();
119    
120            String value = getTranslatedFieldSupport().format(this, getValue());
121    
122            Map symbols = new HashMap();
123    
124            symbols.put(SYM_NAME, name);
125            symbols.put(SYM_FORMAT, format.toPattern());
126            symbols.put(SYM_INCL_WEEK, getIncludeWeek() ? Boolean.TRUE : Boolean.FALSE);
127    
128            symbols.put(SYM_MONTHNAMES, makeStringList(dfs.getMonths(), 0, 12));
129            symbols.put(SYM_SHORT_MONTHNAMES, makeStringList(dfs.getShortMonths(), 0, 12));
130            symbols.put(SYM_WEEKDAYNAMES, makeStringList(dfs.getWeekdays(), 1, 8));
131            symbols.put(SYM_SHORT_WEEKDAYNAMES, makeStringList(dfs.getShortWeekdays(), 1, 8));
132            symbols.put(SYM_FIRSTDAYINWEEK, new Integer(cal.getFirstDayOfWeek() - 1));
133            symbols.put(SYM_MINDAYSINFIRSTWEEK, new Integer(cal.getMinimalDaysInFirstWeek()));
134            symbols.put(SYM_CLEAR_BUTTON_LABEL, getMessages().getMessage("clear"));
135            symbols.put(SYM_FORMNAME, getForm().getName());
136            symbols.put(SYM_VALUE, getValue());
137            
138            getScript().execute(this, cycle, pageRenderSupport, symbols);
139            
140            renderDelegatePrefix(writer, cycle);
141    
142            writer.beginEmpty("input");
143            writer.attribute("type", "text");
144            writer.attribute("name", name);
145            writer.attribute("value", value);
146            writer.attribute("title", title);
147    
148            if (disabled)
149                writer.attribute("disabled", "disabled");
150    
151            renderIdAttribute(writer, cycle);
152    
153            renderDelegateAttributes(writer, cycle);
154    
155            getTranslatedFieldSupport().renderContributions(this, writer, cycle);
156            getValidatableFieldSupport().renderContributions(this, writer, cycle);
157    
158            renderInformalParameters(writer, cycle);
159    
160            writer.printRaw("&nbsp;");
161    
162            if (!disabled)
163            {
164                writer.begin("a");
165                writer.attribute("href", (String) symbols.get(SYM_BUTTONONCLICKHANDLER));
166            }
167    
168            IAsset icon = getIcon();
169    
170            writer.beginEmpty("img");
171            writer.attribute("src", icon.buildURL());
172            writer.attribute("alt", getMessages().getMessage("alt"));
173            writer.attribute("border", 0);
174            
175            if (!disabled)
176                writer.end();
177    
178            renderDelegateSuffix(writer, cycle);
179        }
180    
181        /**
182         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter,
183         *      org.apache.tapestry.IRequestCycle)
184         */
185        protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
186        {
187            String value = cycle.getParameter(getName());
188    
189            try
190            {
191                Date date = (Date) getTranslatedFieldSupport().parse(this, value);
192    
193                getValidatableFieldSupport().validate(this, writer, cycle, date);
194    
195                setValue(date);
196            }
197            catch (ValidatorException e)
198            {
199                getForm().getDelegate().record(e);
200            }
201        }
202    
203        /**
204         * Create a list of quoted strings. The list is suitable for initializing a JavaScript array.
205         */
206        private String makeStringList(String[] a, int offset, int length)
207        {
208            StringBuffer b = new StringBuffer();
209            for (int i = offset; i < length; i++)
210            {
211                // JavaScript is sensitive to some UNICODE characters. So for
212                // the sake of simplicity, we just escape everything
213                b.append('"');
214                char[] ch = a[i].toCharArray();
215                for (int j = 0; j < ch.length; j++)
216                {
217                    if (ch[j] < 128)
218                    {
219                        b.append(ch[j]);
220                    }
221                    else
222                    {
223                        b.append(escape(ch[j]));
224                    }
225                }
226    
227                b.append('"');
228                if (i < length - 1)
229                {
230                    b.append(", ");
231                }
232            }
233            return b.toString();
234    
235        }
236    
237        /**
238         * Create an escaped Unicode character.
239         * 
240         * @param c
241         * @return The unicode character in escaped string form
242         */
243        private static String escape(char c)
244        {
245            char unescapedChar = c;
246            StringBuffer b = new StringBuffer();
247            for (int i = 0; i < 4; i++)
248            {
249                b.append(Integer.toHexString(unescapedChar & 0x000F).toUpperCase());
250                unescapedChar >>>= 4;
251            }
252            b.append("u\\");
253            return b.reverse().toString();
254        }
255    
256        /**
257         * Injected.
258         */
259        public abstract ValidatableFieldSupport getValidatableFieldSupport();
260    
261        /**
262         * Injected.
263         */
264        public abstract TranslatedFieldSupport getTranslatedFieldSupport();
265    
266        /**
267         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
268         */
269        public boolean isRequired()
270        {
271            return getValidatableFieldSupport().isRequired(this);
272        }
273    }