DateTextFieldWithPicker.java

/*
 * Copyright 2011 Marc Grue.
 *
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.qi4j.sample.dcicargo.sample_b.infrastructure.wicket.form;

import com.google.code.joliratools.StatelessAjaxEventBehavior;
import java.util.Date;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.datetime.DateConverter;
import org.apache.wicket.datetime.PatternDateConverter;
import org.apache.wicket.datetime.markup.html.form.DateTextField;
import org.apache.wicket.devutils.stateless.StatelessComponent;
import org.apache.wicket.extensions.yui.calendar.DatePicker;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.validation.validator.DateValidator;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;

/**
 * DateTextFieldWithPicker
 *
 * Customized {@link DateTextField} with a {@link DatePicker}.
 */
@StatelessComponent
public class DateTextFieldWithPicker extends DateTextField
{
    DatePicker datePicker;

    // Configurable widget options
    LocalDate earliestDate;
    LocalDate selectedDate;

    final static String YUI_DATE_FORMAT = "MM/dd/yyyy";

    public DateTextFieldWithPicker( String id, String label, Component model )
    {
//      this( id, new PropertyModel<Date>( model, id ), new StyleDateConverter( "S-", true ) );
        this( id, label, new PropertyModel<Date>( model, id ), new PatternDateConverter( "yyyy-MM-dd", true ) );
    }

    public DateTextFieldWithPicker( String id, String label, IModel<Date> model, DateConverter converter )
    {
        super( id, model, converter );

        // Make the text field reachable by Ajax requests
        setOutputMarkupId( true );

        // Set required as default
        setRequired( true );

        // Add Date Picker with callback configuration
        add( newDatePicker().setShowOnFieldClick( true ) );

        setNonEmptyLabel( label );

        // Show calendar if tabbing into the text field (weird it isn't default)
        add( new StatelessAjaxEventBehavior( "onfocus" )
        {
            @Override
            protected void onEvent( AjaxRequestTarget target )
            {
                String componentId = getComponent().getMarkupId();
                String widgetId = componentId + "DpJs";
                target.appendJavaScript(
                    "Wicket.DateTime.showCalendar(" +
                    "YAHOO.wicket['" + widgetId + "'], " +
                    "document.getElementById('" + componentId + "').value, " +
                    "'yyyy-MM-dd');"
                );
            }

            @Override
            protected PageParameters getPageParameters()
            {
                return null;
            }
        } );

//        add( new StatelessAjaxEventBehavior( "onBlur" )
//        {
//            @Override
//            protected void onEvent( AjaxRequestTarget target )
//            {
//                String componentId = getComponent().getMarkupId();
//                String widgetId = componentId + "DpJs";
//                target.appendJavaScript(
//                      "Wicket.DateTime.showCalendar(" +
//                            "YAHOO.wicket['" + widgetId + "'], " +
//                            "document.getElementById('" + componentId + "').value, " +
//                            "'yyyy-MM-dd');" +
//                      "YAHOO.wicket['" + widgetId + "'].hide();" );
//            }
//
//            @Override
//            protected PageParameters getPageParameters()
//            {
//                return null;
//            }
//        } );
    }

    private void setNonEmptyLabel( String label )
    {
        if( label == null )
        {
            return;
        }

        if( label.isEmpty() )
        {
            throw new IllegalArgumentException( "Can't set an empty label on the drop down selector." );
        }

        setLabel( Model.of( label ) );
    }

    /**
     * The DatePicker that gets added to the DateTextField component. Users may override this method
     * with a DatePicker of their choice.
     *
     * @return a new {@link DatePicker} instance
     */
    protected DatePicker newDatePicker()
    {
        return new DatePicker()
        {
            private static final long serialVersionUID = 1L;

            @Override
            protected void configure( final Map<String, Object> widgetProperties,
                                      final IHeaderResponse response, final Map<String, Object> initVariables
            )
            {
                super.configure( widgetProperties, response, initVariables );

                DateTextFieldWithPicker.this.configure( widgetProperties );
            }
        };
    }

    /**
     * Gives overriding classes the option of adding (or even changing/ removing) configuration
     * properties for the javascript widget. See <a
     * href="http://developer.yahoo.com/yui/calendar/">the widget's documentation</a> for the
     * available options. If you want to override/ remove properties, you should call
     * super.configure(properties) first. If you don't call that, be aware that you will have to
     * call {@literal #localize(Map)} manually if you like localized strings to be added.
     *
     * @param widgetProperties the current widget properties
     */
    protected void configure( Map<String, Object> widgetProperties )
    {
        // Set various YUI calendar options - add more if necessary
        widgetProperties.put( "mindate", getEarliestDateStr() );
        widgetProperties.put( "selected", getSelectedDateStr() );
    }

    public DateTextFieldWithPicker earliestDate( LocalDate newEarliestDate )
    {
        if( selectedDate != null && newEarliestDate.isAfter( selectedDate ) )
        {
            throw new IllegalArgumentException( "Earliest date can't be before selected day." );
        }

        earliestDate = newEarliestDate;

        // Input field validation - date should be _after_ minimumDate (not the same)
        LocalDate minimumDate = newEarliestDate.minusDays( 1 );
        Date convertedMinimumDate = new DateTime( minimumDate.toDateTime( new LocalTime() ) ).toDate();
        add( DateValidator.minimum( convertedMinimumDate ) );

        return this;
    }

    // Add latestDate(..) + other configuration options if needed..

    public DateTextFieldWithPicker selectedDate( LocalDate newSelectedDate )
    {
        if( earliestDate != null && newSelectedDate.isBefore( earliestDate ) )
        {
            throw new IllegalArgumentException( "Selected date can't be before earliest day." );
        }

        selectedDate = newSelectedDate;

        return this;
    }

    private String getSelectedDateStr()
    {
        if( selectedDate != null )
        {
            return selectedDate.toString( YUI_DATE_FORMAT );
        }

        // Select today or earliest date (if later) as default
        return earliestDate == null ?
               new LocalDate().toString( YUI_DATE_FORMAT ) :
               earliestDate.toString( YUI_DATE_FORMAT );
    }

    private String getEarliestDateStr()
    {
        return earliestDate == null ? "" : earliestDate.toString( YUI_DATE_FORMAT );
    }
}