001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.corelib.components; 014 015import java.text.DateFormat; 016import java.text.ParseException; 017import java.text.SimpleDateFormat; 018import java.util.Date; 019 020import org.apache.tapestry5.Binding; 021import org.apache.tapestry5.BindingConstants; 022import org.apache.tapestry5.ComponentResources; 023import org.apache.tapestry5.EventConstants; 024import org.apache.tapestry5.FieldValidator; 025import org.apache.tapestry5.MarkupWriter; 026import org.apache.tapestry5.ValidationException; 027import org.apache.tapestry5.annotations.Events; 028import org.apache.tapestry5.annotations.Parameter; 029import org.apache.tapestry5.commons.Messages; 030import org.apache.tapestry5.corelib.base.AbstractField; 031import org.apache.tapestry5.ioc.internal.util.InternalUtils; 032import org.apache.tapestry5.services.ComponentDefaultProvider; 033 034/** 035 * A component used to collect a provided date from the user using the native HTML5 date picker 036 * (<input type="date">) 037 * @tapestrydoc 038 * @see Form 039 * @see TextField 040 */ 041@Events(EventConstants.VALIDATE) 042public class Html5DateField extends AbstractField 043{ 044 045 final private static String DATE_FORMAT = "yyyy-MM-dd"; 046 047 /** 048 * The value parameter of a DateField must be a {@link java.util.Date}. 049 */ 050 @Parameter(required = true, principal = true, autoconnect = true) 051 private Date value; 052 053 /** 054 * The object that will perform input validation (which occurs after translation). The translate binding prefix is 055 * generally used to provide this object in a declarative fashion. 056 */ 057 @Parameter(defaultPrefix = BindingConstants.VALIDATE) 058 private FieldValidator<Object> validate; 059 060 /** 061 * Used to override the component's message catalog. 062 * 063 * @since 5.2.0.0 064 * @deprecated Since 5.4; override the global message key "core-date-value-not-parsable" instead (see {@link org.apache.tapestry5.services.messages.ComponentMessagesSource}) 065 */ 066 @Parameter("componentResources.messages") 067 private Messages messages; 068 069 /** 070 * Computes a default value for the "validate" parameter using {@link ComponentDefaultProvider}. 071 */ 072 final Binding defaultValidate() 073 { 074 return defaultProvider.defaultValidatorBinding("value", resources); 075 } 076 077 void beginRender(MarkupWriter writer) 078 { 079 String value = validationTracker.getInput(this); 080 081 if (value == null) 082 { 083 value = formatCurrentValue(); 084 } 085 086 String clientId = getClientId(); 087 088 writer.element("input", 089 090 "type", "date", 091 092 "class", cssClass, 093 094 "name", getControlName(), 095 096 "id", clientId, 097 098 "value", value); 099 100 writeDisabled(writer); 101 102 putPropertyNameIntoBeanValidationContext("value"); 103 104 validate.render(writer); 105 106 removePropertyNameFromBeanValidationContext(); 107 108 resources.renderInformalParameters(writer); 109 110 decorateInsideField(); 111 112 writer.end(); // input 113 114 } 115 116 private void writeDisabled(MarkupWriter writer) 117 { 118 if (isDisabled()) 119 writer.attributes("disabled", "disabled"); 120 } 121 122 private String formatCurrentValue() 123 { 124 if (value == null) 125 return ""; 126 127 return getDateFormat().format(value); 128 } 129 130 private DateFormat getDateFormat() { 131 return new SimpleDateFormat(DATE_FORMAT); 132 } 133 134 @Override 135 protected void processSubmission(String controlName) 136 { 137 String value = request.getParameter(controlName); 138 139 validationTracker.recordInput(this, value); 140 141 Date parsedValue = null; 142 143 try 144 { 145 if (InternalUtils.isNonBlank(value)) 146 parsedValue = getDateFormat().parse(value); 147 } catch (ParseException ex) 148 { 149 validationTracker.recordError(this, messages.format("core-date-value-not-parseable", value)); 150 return; 151 } 152 153 putPropertyNameIntoBeanValidationContext("value"); 154 try 155 { 156 fieldValidationSupport.validate(parsedValue, resources, validate); 157 158 this.value = parsedValue; 159 } catch (ValidationException ex) 160 { 161 validationTracker.recordError(this, ex.getMessage()); 162 } 163 164 removePropertyNameFromBeanValidationContext(); 165 } 166 167 void injectResources(ComponentResources resources) 168 { 169 this.resources = resources; 170 } 171 172 @Override 173 public boolean isRequired() 174 { 175 return validate.isRequired(); 176 } 177}