An attempt at the calendar selection widget. Displays a text field, with an icon next to it, click the icon and you get a calendar in a modal pop-up window. Select a date, and the date will be entered into the text field. Select cancel and the pop-up window closes.
This component has had some testing, but nothing thorough. It works well in Firefox 3, IE8, but not in Opera. It could also use some more work in integrating with stylesheets.
Additionally I'm attaching a screenshot of what the component looks like, circled in red is the Textfield and icon, right below is what the pop-up looks like.
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import nextapp.echo.app.Alignment;
import nextapp.echo.app.Button;
import nextapp.echo.app.Color;
import nextapp.echo.app.Column;
import nextapp.echo.app.ContentPane;
import nextapp.echo.app.Extent;
import nextapp.echo.app.ImageReference;
import nextapp.echo.app.Row;
import nextapp.echo.app.TextField;
import nextapp.echo.app.WindowPane;
import nextapp.echo.app.event.ActionEvent;
import nextapp.echo.app.event.ActionListener;
import nextapp.echo.app.layout.ColumnLayoutData;
import nextapp.echo.app.layout.RowLayoutData;
import nextapp.echo.extras.app.CalendarSelect;
/**
*
*
* A composite text field and calendar widget component. This component will render a text field, and next
* to it a button, when clicked the button will open a pop-up window containing a calendar widget. The
* pop-up window is modal, and contains an accept and cancel button. If the user clicks accept, the text
* field component will be set to a String representation of the date selected as formatted by
* either a default DateFormat, or one specified by the class's constructor. Alternatively, the user
* may enter in a date into the input field directly. <br />
*
* <b> THIS COMPONENT DOES NOT WORK WELL WITH OPERA,
* it does though work well with Firefox 3.0.7, and IE 8.0.6.001.18702</b>
* <br /> <br />
*
* Sample Usage:
* <code><pre>
* public class MainWindow extends ApplicationInstance() {
* ...
* private CalendarFieldGroup calendarField;
*
* public Window init() {
* Window w = new Window();
* ContentPane contentPane = w.getContentPane();
* Column column = new Column();
*
* Row calWidgetRow = new Row();
* calendarField = new CalendarFieldGroup( contentPane, getCalendarIconImage() );
* calWidgetRow.add( calendarField );
* column.add( calWidgetRow );
*
* Row formatLabelRow = new Row();
* formatLabelRow.add( new Label("Format MM/DD/YYYY" ));
* column.add( formatLabelRow );
*
* contentPane.add( column );
* }
*
* public Date getSelectedDate() {
* if( calendarField.isValidFormat() ) {
* return calendarField.getValue();
* } else {
* return null;
* }
* }
*
* public ImageReference getCalendarIconImage() {
* ....
* }
* ...
* }
* </pre></code>
*
* @author Daniel H. Van Atta
* @date March 31, 2009
*
*
* TODO Override method, setStyle( Style )
*/
public class CalendarFieldGroup extends Row {
/** A default date format (English/American short date format) */
public static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat( "MM/dd/yy");
/** Serial Version UID */
private static final long serialVersionUID = 4258673927215923782L;
/** The text input field that a user can directly edit, or is populated by the calendar widget. */
private TextField field;
/** The date format that will be rendered to the text field, and also used for parsing the text field. */
private SimpleDateFormat dateFormat;
/** A button that appears next to the text field, once clicked, will open a pop-up window with a calendar widget. */
private Button calendarOpenButton;
/** The background color for the text field while it is enabled */
private Color backgroundColor = DEFAULT_BG_COLOR;
/** The background color for the text field while it is disabled */
private Color disabledBackgroundColor = DEFAULT_BG_COLOR;
/** The width in pixels of the calendar pop-up window. */
private int windowHeight = DEFAULT_WINDOW_HEIGHT;
/** The height in pixels of the calendar pop-up window. */
private int windowWidth = DEFAULT_WINDOW_WIDTH;
/** The default background color (enabled and disabled) for the text field component. */
private static final Color DEFAULT_BG_COLOR = Color.WHITE;
/** A default title for the pop-up window that contains the calendar widget. */
private static final String DEFAULT_TITLE = "Select a Date";
private static final int DEFAULT_WINDOW_HEIGHT = 248;
private static final int DEFAULT_WINDOW_WIDTH = 222;
private String styleName = null;
/**
* Constructs a new CalendarFieldGroup.
* @param parentContainer A reference to a container that is allowed a WindowPane as a child, the
* calendar pop-up window will become a child of this element. Needed solely for the pop-up window.
* @param calendarIcon An image to use for the calendar pop-up button.
* @throws IllegalArgumentException thrown if parentContainer or calendarIcon is null, or if the parentContainer
* does not allow <code>WindowPane</code> as a child element.
*/
public CalendarFieldGroup( final ContentPane parentContainer , final ImageReference calendarIcon ) {
this( DEFAULT_TITLE, parentContainer, calendarIcon , DEFAULT_DATE_FORMAT );
}
/**
* Constructs a new CalendarFieldGroup.
* @param title A title for the calendar pop-up window.
* @param parentContainer A reference to a container that is allowed a WindowPane as a child, the
* calendar pop-up window will become a child of this element.
* @param calendarIcon An image to use for the calendar pop-up button.
* @param dateFormat The dateFormat to use, translates date selection to the input field's text value,
* and also vice versa, input text field value parsed to calendar selection.
* @throws IllegalArgumentException thrown if any of parentContainer, calendarIcon, or dateFormat are null, or if the parentContainer
* does not allow <code>WindowPane</code> as a child element.
*/
public CalendarFieldGroup( final String title, final ContentPane parentContainer , final ImageReference calendarIcon , SimpleDateFormat dateFormat ) {
checkArgs( parentContainer, calendarIcon, dateFormat );
this.dateFormat = dateFormat;
calendarOpenButton = new Button( calendarIcon );
calendarOpenButton.addActionListener( new ActionListener() {
private static final long serialVersionUID = -3563519603461923740L;
public void actionPerformed(ActionEvent e ) {
showCalendarWindow(title, parentContainer );
}
});
field = new TextField();
add( field );
add( calendarOpenButton );
}
public DateFormat getDateFormat() {
return dateFormat;
}
/**
* Verifies parameter correctness.
* @param parentContainer Valid if not null, and if the container allows WindowPanes as a child.
* @param calendarIcon Valid if not null.
* @param dateFormat Valid if not null.
* @throws IllegalArgumentException Thrown if any of the parameters are not 'valid'
*/
private void checkArgs( final ContentPane parentContainer, final ImageReference calendarIcon, final SimpleDateFormat dateFormat ) {
if( parentContainer == null ) {
throw new IllegalArgumentException("In CalendarFieldGroup constructor - Content pane container must not be null.");
}
if( !parentContainer.isValidChild( new WindowPane() ) ) {
throw new IllegalArgumentException( "Illegal parent container, parent container must be allow a 'WindowPane' as a child element.");
}
if( calendarIcon == null ) {
throw new IllegalArgumentException("An image must be specified for the calendar pop-up button.");
}
if( dateFormat == null ) {
throw new IllegalArgumentException("Date format must not be null.");
}
}
/**
* Opens the calendar pop-up window.
* @param title The title for the pop-up window.
* @param parentContainer The parent container for the pop-up window, it must support <code>WindowPane</code>
* components as children.
*/
private void showCalendarWindow( final String title, final ContentPane parentContainer ) {
this.setInputFieldEnabled( false );
final WindowPane windowPane = new WindowPane();
if( styleName != null ) {
windowPane.setStyleName( styleName );
}
windowPane.setTitle( title );
parentContainer.getApplicationInstance().setFocusedComponent( windowPane );
windowPane.setWidth( new Extent( windowWidth , Extent.PX ));
windowPane.setHeight( new Extent( windowHeight, Extent.PX ));
windowPane.setResizable( false );
windowPane.setModal( true );
// the content column will store the pop-up window's contents.
Column contentColumn = new Column();
Row topRow = new Row();
topRow.setAlignment( new Alignment( Alignment.CENTER, Alignment.TOP ) );
Date parsedDate = null;
if( isValidFormat() ) {
parsedDate = getValue();
}
final CalendarSelect calSelect = new CalendarSelect( parsedDate );
if( styleName != null ) {
calSelect.setStyleName( styleName );
}
//calSelect.
topRow.add( calSelect );
contentColumn.add( topRow );
Row buttonRow = new Row();
RowLayoutData rowLayout = new RowLayoutData();
buttonRow.setLayoutData( rowLayout );
buttonRow.setAlignment( new Alignment( Alignment.CENTER, Alignment.BOTTOM ));
// add row content, create a Left Hand Side and RHS column, add the columns to the
// row, and buttons to the columns
Column leftButtonRowColumn = new Column();
ColumnLayoutData leftButtonColLayout = new ColumnLayoutData();
leftButtonColLayout.setAlignment( new Alignment( Alignment.LEFT, Alignment.BOTTOM ));
leftButtonRowColumn.setLayoutData( leftButtonColLayout );
Button cancelButton = new DefaultButton("Cancel", 54 );
if( styleName != null ) {
cancelButton.setStyleName( styleName );
}
cancelButton.addActionListener( new ActionListener() {
private static final long serialVersionUID = -3690374877942702860L;
public void actionPerformed(ActionEvent e ) {
setInputFieldEnabled( true );
parentContainer.remove( windowPane );
windowPane.dispose();
}
});
leftButtonRowColumn.add( cancelButton );
buttonRow.add( leftButtonRowColumn );
Column rightButtonRowColumn = new Column();
ColumnLayoutData rightButtonColLayout = new ColumnLayoutData();
rightButtonColLayout.setAlignment( new Alignment( Alignment.RIGHT, Alignment.BOTTOM ));
Button acceptButton = new DefaultButton("Accept" , 54 );
if( styleName != null ) {
acceptButton.setStyleName( styleName );
}
acceptButton.addActionListener( new ActionListener() {
private static final long serialVersionUID = -9081949167486436165L;
public void actionPerformed(ActionEvent e ) {
setInputFieldEnabled( true );
Date selectedDate = calSelect.getDate();
if( selectedDate == null ) { // selectedDate will be null if the user does
// not click on the calendar first,
// since the current date is selected in the gui, the user
// will expect to be selecting today's date
selectedDate = new Date();
}
String dateText = dateFormat.format( selectedDate );
field.setText( dateText );
parentContainer.remove( windowPane );
windowPane.dispose();
}
});
rightButtonRowColumn.add( acceptButton );
buttonRow.add( rightButtonRowColumn );
contentColumn.add( buttonRow );
windowPane.add( contentColumn );
windowPane.setClosable( true );
windowPane.setVisible( true );
parentContainer.add( windowPane );
}
public void setStyleName( String styleName ) {
this.styleName = styleName;
this.field.setStyleName( styleName );
this.calendarOpenButton.setStyleName( styleName );
}
/**
* Parses the text of the input field, if the input is of a valid date format this method
* will return true, otherwise false.
* @return True if the input text field contains a valid date (as determined by the
* SimpleDateFormat field of this class), otherwise false.
*/
public boolean isValidFormat() {
String fieldText = field.getText();//.trim();
if( fieldText == null || fieldText.trim().equals("" )) {
return true;
}
fieldText = fieldText.trim();
Date parsed = null;
try {
parsed = dateFormat.parse( fieldText );
} catch( ParseException e ) {
return false;
}
return parsed != null;
}
/**
* @return A Date object as parsed from the input text field.
* @throws RuntimeException thrown if the text field value is not parse-able by the SimpleDateFormat.
*/
public Date getValue( ) {
if( !isValidFormat() ) {
throw new IllegalStateException( "Can not retrieve the value of an invalid date." );
}
if( field.getText() == null || field.getText().trim().equals("")) {
return null;
}
Date parsed = null;
try {
parsed = dateFormat.parse( field.getText().trim() );
return parsed;
} catch( ParseException veryUnexpected ) {
throw new RuntimeException("Calendar widget encountered an unexpected parse error on the following value: " + field.getText() + ", using the following date format: " + dateFormat.toPattern() );
}
}
/**
* Sets the value of the input text field to the date specified (as formatted by this class's dateFormat).
* @param newValue The new Date value to set to this component.
*/
public void setValue( Date newValue ) {
field.setText( dateFormat.format( newValue ));
}
/**
* Sets the text field to an arbitrary value, does not check the value, so the date may not
* be correctly formatted.
* @param newValue The new String to place in the textfield for this calendar field group.
*/
public void setValue( String newValue ) {
field.setText( newValue );
}
/**
* @return Returns the String text value found in the input text field.
*/
public String getTextValue() {
return field.getText();
}
/**
* Use to specify the background color of the input text field for when this component is <b>disabled</b>.
* @param disabledBackgroundColor The color of the input text field's background for when
* this component is disabled.
*
*/
public void setInputFieldDisabledBackgroundColor( final Color disabledBackgroundColor ) {
this.disabledBackgroundColor = disabledBackgroundColor;
if( !this.isEnabled() ) {
field.setBackground( disabledBackgroundColor );
}
}
/**
* Use to specify the background color of the input text field for when this component is <b>enabled</b>.
* @param disabledBackgroundColor The color of the input text field's background for when
* this component is enabled.
*/
public void setInputFieldBackgroundColor( final Color backgroundColor) {
this.backgroundColor = backgroundColor;
if( this.isEnabled() ) {
field.setBackground( backgroundColor );
}
}
/**
* Enables/disables the text input field.
* @param enabled A boolean value, true will enable the input text field,
* false will disable it.
*/
public void setInputFieldEnabled( final boolean enabled ) {
if( enabled ) {
field.setEnabled( true );
field.setBackground( backgroundColor );
} else {
field.setEnabled( false );
field.setBackground( disabledBackgroundColor );
}
}
/**
* Enable/disable this component. When disabled this component will not draw the calendar
* pop-up button (meaning the user will not be able to access the calendar widget), and the
* input text field will be read-only.
* @param enabled Whether to disable or enable this component, true will enable, false will disable.
*/
public void setEnabled( final boolean enabled ) {
boolean previouslyEnabled = super.isEnabled();
// only do anything if the enabled state changed from enabled to disabled, or vice versa
if( previouslyEnabled && !enabled ) {
setInputFieldEnabled( enabled );
this.remove( calendarOpenButton );
} else if( !previouslyEnabled && enabled ) {
setInputFieldEnabled( enabled );
this.add( calendarOpenButton );
}
super.setEnabled( enabled );
}
public void setCalendarOpenButtonStyleName( String newStyleName ) {
calendarOpenButton.setStyleName( newStyleName );
}
public void setCalendarWindowHeight( int newHeight ) {
windowHeight = newHeight;
}
public void setCalendarWindowWidth( int newWidth ) {
windowWidth = newWidth;
}
private class DefaultButton extends Button {
private static final long serialVersionUID = -5418447638159027189L;
private static final int DEFAULT_WIDTH = 120;
public DefaultButton( String buttonText) {
this( buttonText, DEFAULT_WIDTH );
}
public DefaultButton( String buttonText, int buttonWidth ) {
super( buttonText );
setStyleName("Default");
setWidth(new Extent(buttonWidth, Extent.PX ));
}
}
}
The EchoPoint sources
The EchoPoint sources (subversion) has a DateTime field from JQuery. I don't think we have made an official release for it yet, but 3.0.0b1 should be coming out soon with a few new components.
Good to hear that 3.0.0b1
Good to hear that 3.0.0b1 will be out soon.
I searched for a DateTime class, or something similar, but couldn't find one in either echo3 core or extras (checked out from subversion 04.28.09). Do you recall which package it is in?
EchoPoint is not Echo3 or
EchoPoint is not Echo3 or Extras.
Look here: http://wiki.nextapp.com/echowiki/EchoPoint and follow the links
André
D'oh, figures why i couldn't
D'oh, figures why i couldn't find it. Thanks.
Nice work
Although there is a nice echopoint component called DateField based on jQuery I like your composite component. It is assembled with echo3 extras and core components, that is pretty neat.
I think this topic about a component should be shifted to another topic "Component Distribution" or to be put in the echo3 wiki.
Thanks for the nice words
Thanks for the nice words Oliver. Once the component is 'complete' I'll ask for this to be moved to component distribution group.
Just discovered that the default window height and width is not sufficient for IE, and needs to be made larger, an extra few pixels is sufficient. To really 'finish' this component, the background window color should be transparent, and the combined button widths should be the exact width of the calendar selection area, and not just a close approximation.
dang it IE
Some more testing has revealed that IE can not render the windowpane/calendar widget correctly at all for a 64-bit Server VM, though it renders fine on a 32 bit client VM. On the server VM the window pane height/width properties are ignored, and the calendar selection component does not draw correctly at all. When switching months and years, you'll see a transition, but once the transition is complete, then you see a blank area where the dates should be. I've attached a screenshot.
This bug discovery has postponed my software release. I might be using the Jquery component after all, not sure yet.
Did a bit more poking into
Did a bit more poking into the issue, when I remove all styling and the buttons, the calendar select renders fine in the pop-up window. I added the buttons back in, and it stopped rendering well again. I instead used a split pane, added the calendar select to the split pane, and the buttons, and that makes it render okay again.
Added the styling back in, and it changed the window sizing but doesn't affect the rendering.