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