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