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.corelib.base.AbstractField;
030import org.apache.tapestry5.ioc.Messages;
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}