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(" "); 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 }