package com.sksamuel.jqm4gwt.plugins.datebox; import java.util.Date; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.JsDate; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.i18n.shared.DateTimeFormat; import com.sksamuel.jqm4gwt.Empty; import com.sksamuel.jqm4gwt.JQMCommon; import com.sksamuel.jqm4gwt.JQMContext; import com.sksamuel.jqm4gwt.JsUtils; import com.sksamuel.jqm4gwt.StrUtils; import com.sksamuel.jqm4gwt.form.elements.JQMText; import com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBoxEvent.DisplayChangeData; import com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBoxEvent.OffsetData; /** * <p> When you add {@literal <inherits name='com.sksamuel.Jqm4gwt-datebox' />} to yourApp.gwt.xml * the following scripts will be included automatically to resulting war: </p> * <pre> jtsage-datebox-calbox-nnn.min.js, jtsage-datebox-nnn.min.css, * jtsage-datebox.i18n.en.utf8.min.js, and datebox.png (in case you'll want a custom icon) </pre> * <p> You can add additional languages by injecting something like: </p> * <pre> jtsage-datebox.i18n.ru.utf8.min.js </pre> * <p> after your application.onModuleLoad() called, see ScriptUtils.waitJqmLoaded() </p> * * See also: * <p><a href="http://dev.jtsage.com/DateBox/">DateBox</a></p> * <p><a href="http://dev.jtsage.com/DateBox/doc/2-0-installing/">Installing instructions</a></p> * */ public class JQMCalBox extends JQMText { /** <a href="http://dev.jtsage.com/DateBox/doc/3-3-output/">Date Format Options</a> */ public static final String FMT_MMDDYY = "%m/%d/%y"; // HasValue<String> declared in JQMText and cannot be overridden as HasValue<Date> in this class. // So we are going to return well formatted string representation of date as getValue() result, // and expecting the same string format when setValue() method called. // By default ISO 8601 format is used, but it could be changed by setting valueStrFmt property. public static final DateTimeFormat VALUE_DFLT_STR_FMT = DateTimeFormat.getFormat("yyyy-MM-dd"); private static DateTimeFormat valueStrFmt = VALUE_DFLT_STR_FMT; public static final String YEAR_PICK_NOW = "NOW"; protected static final String MODE_CALBOX = "\"mode\": \"calbox\""; protected static final String USE_INLINE = "\"useInline\":"; // Show control inline in the page, negating any open and close actions protected static final String USE_INLINE_BLIND = "\"useInlineBlind\":"; // Attach the control directly to the input element, and roll it down from there when opened protected static final String HIDE_CONTAINER = "\"hideContainer\":"; // Cause the original fieldcontain to be hidden on the page - really only appropriate with "useInline" protected static final String OVERRIDE_DATE_FMT = "\"overrideDateFormat\":"; protected static final String WEEK_START_DAY = "\"overrideCalStartDay\":"; protected static final String DIALOG_LABEL = "\"overrideDialogLabel\":"; protected static final String USE_CLEAR_BUTTON = "\"useClearButton\":"; protected static final String LOCK_INPUT = "\"lockInput\":"; protected static final String BUTTON_ICON = "\"buttonIcon\":"; protected static final String NEXT_MONTH_ICON = "\"calNextMonthIcon\":"; protected static final String PREV_MONTH_ICON = "\"calPrevMonthIcon\":"; // See http://dev.jtsage.com/DateBox/doc/5-0-control/ // CalBox Specific - Display protected static final String SHOW_DAYS = "\"calShowDays\":"; protected static final String SHOW_WEEK = "\"calShowWeek\":"; protected static final String SHOW_ONE_MONTH_ONLY = "\"calOnlyMonth\":"; protected static final String HIGHLIGHT_TODAY = "\"calHighToday\":"; protected static final String HIGHLIGHT_SELECTED = "\"calHighPick\":"; protected static final String COMPACT_DATE_BUTTONS = "\"calControlGroup\":"; // See http://dev.jtsage.com/DateBox/doc/5-0-control/ // CalBox Specific - Control protected static final String USE_TODAY_BUTTON = "\"useTodayButton\":"; protected static final String USE_TOMORROW_BUTTON = "\"useTomorrowButton\":"; protected static final String USE_PICKERS = "\"calUsePickers\":"; protected static final String USE_PICKERS_ICONS = "\"calUsePickersIcons\":"; protected static final String YEAR_PICK_MIN = "\"calYearPickMin\":"; protected static final String YEAR_PICK_MAX = "\"calYearPickMax\":"; protected static final String MIN_YEAR = "\"minYear\":"; protected static final String MAX_YEAR = "\"maxYear\":"; protected static final String MIN_DAYS = "\"minDays\":"; protected static final String MAX_DAYS = "\"maxDays\":"; protected static final String NO_HEADER = "\"calNoHeader\":"; protected static final String NO_TITLE = "\"useHeader\":"; // Refers to the header with the close button and the title // See http://dev.jtsage.com/DateBox/doc/3-1-themes/ protected static final String THEME = "\"theme\":"; // false means inherited theme protected static final String THEME_HEADER = "\"themeHeader\":"; // Theme for header protected static final String THEME_MODAL = "\"useModalTheme\":"; // Theme for modal background of control. Shade the background with this color swatch. From the default themes, “a” is a very light grey, “b” is a slighly darker grey. protected static final String THEME_DATE = "\"themeDate\":"; // Theme for otherwise un-specified date buttons protected static final String THEME_DATETODAY = "\"themeDateToday\":"; // Theme for “today” protected static final String THEME_DATEPICK = "\"themeDatePick\":"; // Theme for choosen date (used last after other options fail) protected static final String THEME_DAYHIGH = "\"themeDayHigh\":"; // Theme for highlighted DAYS protected static final String THEME_DATEHIGH = "\"themeDateHigh\":"; // Theme for highlighted DATES protected static final String THEME_DATEHIGH_ALT = "\"themeDateHighAlt\":"; // Theme for highlighted ALTERNATE DATES protected static final String THEME_DATEHIGH_REC = "\"themeDateHighRec\":"; // Theme for highlighted RECURRING DATES private Boolean useInline = null; private Boolean useInlineBlind = null; private Boolean hideContainer = null; private String dateFormat = null; private Integer weekStartDay = null; private String dialogLabel = null; private Boolean useClearButton = null; private Boolean editable = true; private Boolean lockInput = null; private String buttonIcon = null; private String nextMonthIcon = null; private String prevMonthIcon = null; private Boolean useTodayButton = null; private Boolean useTomorrowButton = null; private Boolean usePickers = null; private Boolean usePickersIcons = null; private String yearPickMin = null; private String yearPickMax = null; private Integer minYear = null; private Integer maxYear = null; private Integer minDays = null; private Integer maxDays = null; private Boolean noHeader = null; private Boolean noTitle = null; private Boolean showDays = null; private Boolean showWeek = null; private Boolean showOneMonthOnly = null; private Boolean highlightToday = null; private Boolean highlightSelected = null; private Boolean compactDateButtons = null; private String theme = null; private String themeHeader = null; private String themeModal = null; private String themeDate = null; private String themeDateToday = null; private String themeDatePick = null; private String themeDayHigh = null; private String themeDateHigh = null; private String themeDateHighAlt = null; private String themeDateHighRec = null; /** * GWT Date and JsDate are both created in current browser's timezone. * Calbox's setTheDate() takes date and use year/month/day from it (time and timezone are ignored). * <br> Example: we are in PST (i.e. GMT-8) timezone. * So new JsDate(0) gives us: 12/31/1969 16:00 GMT-8 * <br> Therefore we cannot use 0 constant, and need proper number for our timezone, * which is 2.88E7 in that case. * <br> For person in GMT timezone this constant is 0 of course. */ @SuppressWarnings("deprecation") private static final double NULL_DATE = new Date(70, 0, 1).getTime(); private Date delayedSetDate = null; // used when not initialized yet private boolean isInternSetDate; private Date internDateToSet; // works when isInternSetDate == true private boolean invalidateUnlockedInputOnBlur = true; /** Additional information can be added to days (1..31) buttons. */ public static interface GridDateFormatter { String format(int yyyy, int mm, int dd, String iso8601, boolean selectedDateVisible); } public static interface GridDateFormatterEx extends GridDateFormatter { /** * @return - additional space separated classes for CSS styling (coloring, shaping, ...) */ String getStyleNames(int yyyy, int mm, int dd, String iso8601, boolean selectedDateVisible); } private GridDateFormatter gridDateFormatter; public static class JsDateBoxData extends JavaScriptObject { protected JsDateBoxData() {} /** Numeric Date */ public final native int getDate() /*-{ return this.date; }-*/; /** Numeric Month, zero based */ public final native int getMonth() /*-{ return this.month; }-*/; /** Date can be selected */ public final native boolean getEnabled() /*-{ return this.enabled; }-*/; /** You can disable any date button right in GridDateBox.beforeAppend() handler. * <br> Example: * <pre> * data.setEnabled(false); * JQMCommon.setEnabled(box, false); * box.getStyle().setColor("red"); * </pre> **/ public final native void setEnabled(boolean value) /*-{ this.enabled = value; }-*/; } public static interface GridDateBox { void beforeAppend(Element box, JsDateBoxData data); } private GridDateBox gridDateBox; private boolean calBoxHandlerAdded; private boolean blurHandlerAdded; private boolean isInternalBlur; private boolean created; static { addJsParts(); } private static native void addJsParts() /*-{ $wnd.mobileDateboxCallbackFalse = function() { return false; }; }-*/; public JQMCalBox() { this(null); } public JQMCalBox(String text) { super(text); //setType("date"); // it's servicing by jtsage-datebox, so type must not be set as "date" setInputAttribute("data-role", "datebox"); input.addBlurHandler(new BlurHandler() { @Override public void onBlur(BlurEvent event) { if (isInternalBlur) return; if (lockInput != null && !lockInput && invalidateUnlockedInputOnBlur) { String oldText = input.getText(); if (oldText == null || oldText.isEmpty()) return; if (oldText.trim().isEmpty()) { input.setText(""); ValueChangeEvent.fire(input, getValue()); return; } if (!smartConvertInputText()) updateInputText(); String newText = input.getText(); if (!oldText.equals(newText)) ValueChangeEvent.fire(input, getValue()); } } }); refreshDataOptions(); } @Override public HandlerRegistration addBlurHandler(BlurHandler handler) { blurHandlerAdded = true; return super.addBlurHandler(handler); } protected static String bool2Str(boolean value) { return value ? "true" : "false"; } protected String constructDataOptions() { StringBuilder sb = new StringBuilder(); sb.append('{').append(MODE_CALBOX); if (useInline != null) { sb.append(',').append(USE_INLINE).append(bool2Str(useInline)); } if (useInlineBlind != null) { sb.append(',').append(USE_INLINE_BLIND).append(bool2Str(useInlineBlind)); } if (hideContainer != null) { sb.append(',').append(HIDE_CONTAINER).append(bool2Str(hideContainer)); } if (dateFormat != null && !dateFormat.isEmpty()) { sb.append(',').append(OVERRIDE_DATE_FMT).append('"').append(dateFormat).append('"'); } if (usePickers != null) { sb.append(',').append(USE_PICKERS).append(bool2Str(usePickers)); } if (usePickersIcons != null) { sb.append(',').append(USE_PICKERS_ICONS).append(bool2Str(usePickersIcons)); } if (noHeader != null) { sb.append(',').append(NO_HEADER).append(bool2Str(noHeader)); } if (noTitle != null) { sb.append(',').append(NO_TITLE).append(bool2Str(!noTitle)); } if (weekStartDay != null) { sb.append(',').append(WEEK_START_DAY).append(String.valueOf(weekStartDay)); } if (useTodayButton != null) { sb.append(',').append(USE_TODAY_BUTTON).append(bool2Str(useTodayButton)); } if (useTomorrowButton != null) { sb.append(',').append(USE_TOMORROW_BUTTON).append(bool2Str(useTomorrowButton)); } if (showDays != null) { sb.append(',').append(SHOW_DAYS).append(bool2Str(showDays)); } if (showWeek != null) { sb.append(',').append(SHOW_WEEK).append(bool2Str(showWeek)); } if (showOneMonthOnly != null) { sb.append(',').append(SHOW_ONE_MONTH_ONLY).append(bool2Str(showOneMonthOnly)); } if (highlightToday != null) { sb.append(',').append(HIGHLIGHT_TODAY).append(bool2Str(highlightToday)); } if (highlightSelected != null) { sb.append(',').append(HIGHLIGHT_SELECTED).append(bool2Str(highlightSelected)); } if (compactDateButtons != null) { sb.append(',').append(COMPACT_DATE_BUTTONS).append(bool2Str(compactDateButtons)); } if (dialogLabel != null && !dialogLabel.isEmpty()) { sb.append(',').append(DIALOG_LABEL).append('"').append(dialogLabel).append('"'); } if (useClearButton != null) { sb.append(',').append(USE_CLEAR_BUTTON).append(bool2Str(useClearButton)); } if (editable != null && editable == false) { sb.append(',').append("\"beforeOpenCallback\": \"mobileDateboxCallbackFalse\""); getElement().addClassName("jqm4gwt-non-editable"); } else { getElement().removeClassName("jqm4gwt-non-editable"); } if (yearPickMin != null && !yearPickMin.isEmpty()) { if (YEAR_PICK_NOW.equals(yearPickMin)) { sb.append(',').append(YEAR_PICK_MIN).append('"').append(yearPickMin).append('"'); } else { sb.append(',').append(YEAR_PICK_MIN).append(yearPickMin); } if (minYear == null) { Integer minYY = calcMinYear(); if (minYY != null && minYY.intValue() > 0) { sb.append(',').append(MIN_YEAR).append(String.valueOf(minYY)); } } } if (yearPickMax != null && !yearPickMax.isEmpty()) { if (YEAR_PICK_NOW.equals(yearPickMax)) { sb.append(',').append(YEAR_PICK_MAX).append('"').append(yearPickMax).append('"'); } else { sb.append(',').append(YEAR_PICK_MAX).append(yearPickMax); } if (maxYear == null) { Integer maxYY = calcMaxYear(); if (maxYY != null && maxYY.intValue() > 0) { sb.append(',').append(MAX_YEAR).append(String.valueOf(maxYY)); } } } if (minYear != null) { sb.append(',').append(MIN_YEAR).append(String.valueOf(minYear)); } if (maxYear != null) { sb.append(',').append(MAX_YEAR).append(String.valueOf(maxYear)); } if (minDays != null) { sb.append(',').append(MIN_DAYS).append(String.valueOf(minDays)); } if (maxDays != null) { sb.append(',').append(MAX_DAYS).append(String.valueOf(maxDays)); } if (lockInput != null) { sb.append(',').append(LOCK_INPUT).append(bool2Str(lockInput)); } if (buttonIcon != null && !buttonIcon.isEmpty()) { sb.append(',').append(BUTTON_ICON).append('"').append(buttonIcon).append('"'); } if (nextMonthIcon != null && !nextMonthIcon.isEmpty()) { sb.append(',').append(NEXT_MONTH_ICON).append('"').append(nextMonthIcon).append('"'); } if (prevMonthIcon != null && !prevMonthIcon.isEmpty()) { sb.append(',').append(PREV_MONTH_ICON).append('"').append(prevMonthIcon).append('"'); } if (theme != null && !theme.isEmpty()) { sb.append(',').append(THEME).append('"').append(theme).append('"'); } if (themeHeader != null && !themeHeader.isEmpty()) { sb.append(',').append(THEME_HEADER).append('"').append(themeHeader).append('"'); } if (themeModal != null && !themeModal.isEmpty()) { sb.append(',').append(THEME_MODAL).append('"').append(themeModal).append('"'); } if (themeDate != null && !themeDate.isEmpty()) { sb.append(',').append(THEME_DATE).append('"').append(themeDate).append('"'); } if (themeDateToday != null && !themeDateToday.isEmpty()) { sb.append(',').append(THEME_DATETODAY).append('"').append(themeDateToday).append('"'); } if (themeDatePick != null && !themeDatePick.isEmpty()) { sb.append(',').append(THEME_DATEPICK).append('"').append(themeDatePick).append('"'); } if (themeDayHigh != null && !themeDayHigh.isEmpty()) { sb.append(',').append(THEME_DAYHIGH).append('"').append(themeDayHigh).append('"'); } if (themeDateHigh != null && !themeDateHigh.isEmpty()) { sb.append(',').append(THEME_DATEHIGH).append('"').append(themeDateHigh).append('"'); } if (themeDateHighAlt != null && !themeDateHighAlt.isEmpty()) { sb.append(',').append(THEME_DATEHIGH_ALT).append('"').append(themeDateHighAlt).append('"'); } if (themeDateHighRec != null && !themeDateHighRec.isEmpty()) { sb.append(',').append(THEME_DATEHIGH_REC).append('"').append(themeDateHighRec).append('"'); } sb.append('}'); return sb.toString(); } public Integer calcMinYear() { if (minYear != null) return minYear; if (yearPickMin != null && !yearPickMin.isEmpty()) { if (YEAR_PICK_NOW.equals(yearPickMin)) { @SuppressWarnings("deprecation") int curYY = (new Date()).getYear() + 1900; return curYY; } else { int yy = Integer.parseInt(yearPickMin); if (yy < 1800) return minYear; else return yy; } } else { return minYear; } } public Integer calcMaxYear() { if (maxYear != null) return maxYear; if (yearPickMax != null && !yearPickMax.isEmpty()) { if (YEAR_PICK_NOW.equals(yearPickMax)) { @SuppressWarnings("deprecation") int curYY = (new Date()).getYear() + 1900; return curYY; } else { int yy = Integer.parseInt(yearPickMax); if (yy < 1800) return maxYear; else return yy; } } else { return maxYear; } } protected void refreshDataOptions() { setInputAttribute("data-options", constructDataOptions()); } private void setInputAttribute(String name, String value) { if (input == null) return; input.getElement().setAttribute(name, value); } public Boolean getUseInline() { return useInline; } /** Show control inline in the page, negating any open and close actions */ public void setUseInline(Boolean useInline) { this.useInline = useInline; refreshDataOptions(); } public Boolean getUseInlineBlind() { return useInlineBlind; } /** Attach the control directly to the input element, and roll it down from there when opened */ public void setUseInlineBlind(Boolean useInlineBlind) { this.useInlineBlind = useInlineBlind; refreshDataOptions(); } public Boolean getHideContainer() { return hideContainer; } /** Cause the original fieldcontain to be hidden on the page - really only appropriate with "useInline" */ public void setHideContainer(Boolean value) { this.hideContainer = value; refreshDataOptions(); } public String getDateFormat() { return dateFormat; } /** * @param dateFormat - <a href="http://dev.jtsage.com/DateBox/doc/3-3-output/">Date Format Options</a> */ public void setDateFormat(String dateFormat) { this.dateFormat = dateFormat; refreshDataOptions(); } public String getActiveDateFormat() { if (dateFormat != null) return dateFormat; if (input == null) return null; // see __fmt() in jtsage-datebox-calbox.js String fmt = internGetLangOptionStr(input.getElement(), "dateFormat"); return fmt; } public JsArrayString getMonthNames() { if (input == null) return null; JavaScriptObject months = internGetLangOption(input.getElement(), "monthsOfYear"); return months.cast(); } public JsArrayString getMonthShortNames() { if (input == null) return null; JavaScriptObject months = internGetLangOption(input.getElement(), "monthsOfYearShort"); return months.cast(); } public static JsArrayString getClazzMonthNames() { JavaScriptObject months = internGetProtoLangOption("monthsOfYear"); return months.cast(); } public static JsArrayString getClazzMonthShortNames() { JavaScriptObject months = internGetProtoLangOption("monthsOfYearShort"); return months.cast(); } public Boolean getUsePickers() { return usePickers; } public void setUsePickers(Boolean usePickers) { this.usePickers = usePickers; if (this.usePickers != null && this.usePickers && noHeader == null) noHeader = true; refreshDataOptions(); } public Boolean getUsePickersIcons() { return usePickersIcons; } /** * Only works with calNoHeader and calUsePickers turned on (true). */ public void setUsePickersIcons(Boolean value) { this.usePickersIcons = value; refreshDataOptions(); } public Boolean getNoHeader() { return noHeader; } /** Hide standard header (by default a plus button, the Month/Year combo, and a minus button) */ public void setNoHeader(Boolean noHeader) { this.noHeader = noHeader; refreshDataOptions(); } public Boolean getNoTitle() { return noTitle; } /** Refers to the header with the close button and the title */ public void setNoTitle(Boolean value) { this.noTitle = value; refreshDataOptions(); } public Integer getWeekStartDay() { return weekStartDay; } /** * @param weekStartDay - 0-6, where 0=Sunday, 1=Monday... */ public void setWeekStartDay(Integer weekStartDay) { this.weekStartDay = weekStartDay; refreshDataOptions(); } public String getDialogLabel() { return dialogLabel; } /** * Needed in case for example you don't want placeholder to be shown as date selection dialog title. */ public void setDialogLabel(String dialogLabel) { this.dialogLabel = dialogLabel; refreshDataOptions(); } public Boolean getUseTodayButton() { return useTodayButton; } public void setUseTodayButton(Boolean useTodayButton) { this.useTodayButton = useTodayButton; refreshDataOptions(); } public Boolean getUseTomorrowButton() { return useTomorrowButton; } public void setUseTomorrowButton(Boolean useTomorrowButton) { this.useTomorrowButton = useTomorrowButton; refreshDataOptions(); } public Boolean getShowDays() { return showDays; } public void setShowDays(Boolean showDays) { this.showDays = showDays; refreshDataOptions(); } public Boolean getShowWeek() { return showWeek; } public void setShowWeek(Boolean showWeek) { this.showWeek = showWeek; refreshDataOptions(); } public Boolean getShowOneMonthOnly() { return showOneMonthOnly; } public void setShowOneMonthOnly(Boolean showOneMonthOnly) { this.showOneMonthOnly = showOneMonthOnly; refreshDataOptions(); } public Boolean getHighlightToday() { return highlightToday; } public void setHighlightToday(Boolean highlightToday) { this.highlightToday = highlightToday; refreshDataOptions(); } public Boolean getHighlightSelected() { return highlightSelected; } public void setHighlightSelected(Boolean highlightSelected) { this.highlightSelected = highlightSelected; refreshDataOptions(); } public Boolean getCompactDateButtons() { return compactDateButtons; } public void setCompactDateButtons(Boolean compactDateButtons) { this.compactDateButtons = compactDateButtons; refreshDataOptions(); } public Boolean getUseClearButton() { return useClearButton; } public void setUseClearButton(Boolean useClearButton) { this.useClearButton = useClearButton; refreshDataOptions(); } public Boolean getEditable() { return editable; } /** * Read only mode for this widget, if false - open calendar button will be disabled and input locked. */ public void setEditable(Boolean editable) { this.editable = editable; if (!editable) lockInput = true; refreshDataOptions(); } public String getYearPickMin() { return yearPickMin; } /** * See {@link JQMCalBox#setYearPickMax(String)} */ public void setYearPickMin(String yearPickMin) { this.yearPickMin = yearPickMin; refreshDataOptions(); } public String getYearPickMax() { return yearPickMax; } /** * yearPickMin and yearPickMax - valid options are an integer less than 1800, * which will be added/subtracted from the current year * (with Max, use a negative integer to go into the past - negative numbers for min will be abs()ed appropriatly), * or if the number is greater than 1800, it will be assumed to be a hard year. * <br> Finally, the string "NOW" (UPCASE!) will use the current year (today's date, not the picker year). */ public void setYearPickMax(String yearPickMax) { this.yearPickMax = yearPickMax; refreshDataOptions(); } public Integer getMinDays() { return minDays; } /** See <a href="http://dev.jtsage.com/DateBox/api/minDays/">Minimum amount of days before today</a> */ public void setMinDays(Integer value) { minDays = value; refreshDataOptions(); } public Integer getMaxDays() { return maxDays; } /** See <a href="http://dev.jtsage.com/DateBox/api/maxDays/">Maximum number of days past today</a> */ public void setMaxDays(Integer value) { maxDays = value; refreshDataOptions(); } public Integer getMinYear() { return minYear; } /** See <a href="http://dev.jtsage.com/DateBox/api/minYear/">Minimum allowed year</a> */ public void setMinYear(Integer value) { minYear = value; refreshDataOptions(); } public Integer getMaxYear() { return maxYear; } /** See <a href="http://dev.jtsage.com/DateBox/api/maxYear/">Maximum allowed year</a> */ public void setMaxYear(Integer value) { maxYear = value; refreshDataOptions(); } public Boolean getLockInput() { return lockInput; } /** * When false - user can type-in date manually (default is true, i.e. no manual typing). */ public void setLockInput(Boolean lockInput) { this.lockInput = lockInput; refreshDataOptions(); } public String getButtonIcon() { return buttonIcon; } /** * This is the class of the button in the input element. * <br>Default value is calendar. * <br><a href="http://demos.jquerymobile.com/1.4.5/icons/">Icons</a> */ public void setButtonIcon(String buttonIcon) { this.buttonIcon = buttonIcon; refreshDataOptions(); } public String getNextMonthIcon() { return nextMonthIcon; } /** * This allows customization of the Next Month button in the calendar header. * <br>Default value is plus. * <br><a href="http://demos.jquerymobile.com/1.4.5/icons/">Icons</a> */ public void setNextMonthIcon(String nextMonthIcon) { this.nextMonthIcon = nextMonthIcon; refreshDataOptions(); } public String getPrevMonthIcon() { return prevMonthIcon; } /** * This allows customization of the Previous Month button in the calendar header. * <br>Default value is minus. * <br><a href="http://demos.jquerymobile.com/1.4.5/icons/">Icons</a> */ public void setPrevMonthIcon(String prevMonthIcon) { this.prevMonthIcon = prevMonthIcon; refreshDataOptions(); } @Override public String getTheme() { if (theme == null || theme.isEmpty()) return super.getTheme(); else return theme; } @Override public void setTheme(String theme) { super.setTheme(theme); this.theme = theme; refreshDataOptions(); } public String getThemeHeader() { return themeHeader; } public void setThemeHeader(String themeHeader) { this.themeHeader = themeHeader; refreshDataOptions(); } public String getThemeModal() { return themeModal; } public void setThemeModal(String themeModal) { this.themeModal = themeModal; refreshDataOptions(); } public String getThemeDate() { return themeDate; } public void setThemeDate(String themeDate) { this.themeDate = themeDate; refreshDataOptions(); } public String getThemeDateToday() { return themeDateToday; } public void setThemeDateToday(String themeDateToday) { this.themeDateToday = themeDateToday; refreshDataOptions(); } public String getThemeDatePick() { return themeDatePick; } public void setThemeDatePick(String themeDatePick) { this.themeDatePick = themeDatePick; refreshDataOptions(); } public String getThemeDayHigh() { return themeDayHigh; } public void setThemeDayHigh(String themeDayHigh) { this.themeDayHigh = themeDayHigh; refreshDataOptions(); } public String getThemeDateHigh() { return themeDateHigh; } public void setThemeDateHigh(String themeDateHigh) { this.themeDateHigh = themeDateHigh; refreshDataOptions(); } public String getThemeDateHighAlt() { return themeDateHighAlt; } public void setThemeDateHighAlt(String themeDateHighAlt) { this.themeDateHighAlt = themeDateHighAlt; refreshDataOptions(); } public String getThemeDateHighRec() { return themeDateHighRec; } public void setThemeDateHighRec(String themeDateHighRec) { this.themeDateHighRec = themeDateHighRec; refreshDataOptions(); } public boolean isInvalidateUnlockedInputOnBlur() { return invalidateUnlockedInputOnBlur; } /** * When lockInput == false this property controls if proper/parsed date is forcefully set on blur/exit (default: true) */ public void setInvalidateUnlockedInputOnBlur(boolean invalidateUnlockedInputOnBlur) { this.invalidateUnlockedInputOnBlur = invalidateUnlockedInputOnBlur; } public static DateTimeFormat getValueStrFmt() { return valueStrFmt; } public static void setValueStrFmt(DateTimeFormat fmt) { valueStrFmt = fmt; } protected static class CalBoxValueChangeEvent extends ValueChangeEvent<String> { public CalBoxValueChangeEvent(String value) { super(value); } } protected static class CalBoxValueChangeHandler implements ValueChangeHandler<String> { private final ValueChangeHandler<String> handler; private final JQMCalBox calBox; public CalBoxValueChangeHandler(ValueChangeHandler<String> handler, JQMCalBox calBox) { this.handler = handler; this.calBox = calBox; } @Override public void onValueChange(final ValueChangeEvent<String> event) { if (calBox.isInternSetDate) return; if (event instanceof CalBoxValueChangeEvent) { handler.onValueChange(event); return; } if (calBox.lockInput != null && !calBox.lockInput) { // On manual input calBox.getValue() is still old here, so we have to wait Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { ValueChangeEvent<String> newEvent = new CalBoxValueChangeEvent(calBox.getValue()); handler.onValueChange(newEvent); doArtificialBlur(); } }); } else { ValueChangeEvent<String> newEvent = new CalBoxValueChangeEvent(calBox.getValue()); handler.onValueChange(newEvent); doArtificialBlur(); } } private void doArtificialBlur() { if (calBox.isAttached() && calBox.created && calBox.blurHandlerAdded) { calBox.isInternalBlur = true; try { // For example to rerun JQMForm's validation for this dropdown DomEvent.fireNativeEvent(Document.get().createBlurEvent(), calBox.input); } finally { calBox.isInternalBlur = false; } } } } @Override public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) { ValueChangeHandler<String> newHandler = new CalBoxValueChangeHandler(handler, this); return input.addValueChangeHandler(newHandler); } @Override public String getValue() { Date d = getDate(); return d == null ? null : valueStrFmt.format(d); } @Override public void setValue(String value) { if (value == null || value.isEmpty()) { setDate(null); return; } Date d = valueStrFmt.parse(value); setDate(d); } @Override public void setValue(String value, boolean fireEvents) { Date oldD = fireEvents ? getDate() : null; setValue(value); if (fireEvents) { Date newD = getDate(); boolean eq = newD != null ? newD.equals(oldD) : oldD == null; if (!eq) ValueChangeEvent.fire(input, getValue()); } } private static native void bindCreated(Element elt, JQMCalBox cal) /*-{ $wnd.$(elt).on( 'dateboxcreate', function( event, ui ) { cal.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::created()(); }); }-*/; private static native void unbindCreated(Element elt) /*-{ $wnd.$(elt).off( 'dateboxcreate' ); }-*/; private void created() { created = true; setDate(delayedSetDate); initGridDateFormatter(); initGridDateBoxBeforeAppend(); initDisplayChange(); initOffset(); } @Override protected void onLoad() { super.onLoad(); bindCreated(input.getElement(), this); if (created) setDate(delayedSetDate); } @Override protected void onUnload() { final Date d = getDate(); unbindCreated(input.getElement()); super.onUnload(); delayedSetDate = d; } public void setDate(Date d) { setDate(d, false); } /** * Refresh after a programmatic change has taken place. */ public void refresh() { refresh(input.getElement()); } private native void refresh(Element elt) /*-{ var w = $wnd.$(elt); if (w.data('jtsage-datebox') !== undefined) { w.datebox('refresh'); } }-*/; private static native boolean isCalboxReady(Element elt) /*-{ if ($wnd.$ === undefined || $wnd.$ === null) return false; // jQuery is not loaded var w = $wnd.$(elt); if (w.data('jtsage-datebox') !== undefined) { return true; } else { return false; } }-*/; private boolean isReady() { return input.isAttached() && isCalboxReady(input.getElement()); } /** * @param fireEvents - when true {@link com.google.gwt.event.logical.shared.ValueChangeEvent} * will be fired if date changed. */ public void setDate(Date d, boolean fireEvents) { if (input == null) return; if (!isReady()) { delayedSetDate = d; return; } Date oldD = fireEvents ? getDate() : null; if (d == null) { internSetDate(d); input.setText(""); } else { internSetDate(d); // had to update text manually, because JS function 'setTheDate' didn't do that for some reason updateInputText(); } if (fireEvents) { Date newD = getDate(); boolean eq = newD != null ? newD.equals(oldD) : oldD == null; if (!eq) ValueChangeEvent.fire(input, getValue()); } } private void updateInputText() { Element elt = input.getElement(); JsDate jsd = internGetDate(elt); String fs = internFormat(elt, getActiveDateFormat(), jsd); input.setText(fs); } /** @return - true in case input text was successfully processed by "smart" converter. */ private boolean smartConvertInputText() { Element elt = input.getElement(); String v = input.getValue(); if (v != null && !v.isEmpty()) { v = v.trim(); // supports (mmddyy or mmddyyyy) or (ddmmyy or ddmmyyyy) input without any separators if (!v.isEmpty() && StrUtils.isDigitsOnly(v) && (v.length() == 6 || v.length() == 8)) { boolean mmFirst = true; String fmt = getDateFormat(); if (!Empty.is(fmt)) { int ddPos = fmt.indexOf("%d"); int mmPos = fmt.indexOf("%m"); if (ddPos >= 0 && mmPos >= 0) { mmFirst = mmPos < ddPos; } } final int mm; final int dd; if (mmFirst) { mm = Integer.parseInt(v.substring(0, 2)); dd = Integer.parseInt(v.substring(2, 4)); } else { dd = Integer.parseInt(v.substring(0, 2)); mm = Integer.parseInt(v.substring(2, 4)); } int yy = Integer.parseInt(v.substring(4)); Date d = new Date(); @SuppressWarnings("deprecation") int currentYear = d.getYear() + 1900; if (yy < 100) { if (yy + 2000 <= currentYear + 20) yy += 2000; else yy += 1900; } if (dd >= 1 && dd <= 31 && mm >= 1 && mm <= 12 && yy >= 1900 && yy <= currentYear + 100) { Integer minYY = calcMinYear(); if (minYY != null && yy < minYY.intValue()) return false; Integer maxYY = calcMaxYear(); if (maxYY != null && yy > maxYY.intValue()) return false; @SuppressWarnings("deprecation") Date newd = new Date(yy - 1900, mm - 1, dd); isInternSetDate = true; internDateToSet = newd; try { // ValueChange may occur! internalSetDate(elt, yy, mm - 1, dd); } finally { isInternSetDate = false; } return true; } } } return false; } public Date getDate() { if (input == null) return null; if (!isReady()) return delayedSetDate; if (isInternSetDate) return internDateToSet; String s = input.getText(); // open/close calendar even without any selection, sets js control's date to Now, // but we don't want this behavior, i.e. text is empty means no date is chosen! if (s == null || s.isEmpty()) return null; JsDate jsd = internGetDate(input.getElement()); return JQMContext.jsDateToDate(jsd); } public String getIso8601() { if (input == null) return null; if (!isReady()) return dateAsIso8601(delayedSetDate); if (isInternSetDate) return dateAsIso8601(internDateToSet); String s = input.getText(); // open/close calendar even without any selection, sets js control's date to Now, // but we don't want this behavior, i.e. text is empty means no date is chosen! if (s == null || s.isEmpty()) return null; JsDate jsd = internGetDate(input.getElement()); if (jsd == null) return null; int yyyy = jsd.getFullYear(); int mm = jsd.getMonth() + 1; int dd = jsd.getDate(); return dateAsIso8601(yyyy, mm, dd); } @SuppressWarnings("deprecation") public static String dateAsIso8601(Date d) { if (d == null) return null; int yyyy = d.getYear() + 1900; int mm = d.getMonth() + 1; int dd = d.getDate(); return dateAsIso8601(yyyy, mm, dd); } /** * @return - date in ISO8601 format, i.e. 2015-01-09 */ public static String dateAsIso8601(int yyyy, int mm, int dd) { StringBuilder sb = new StringBuilder(); String s = String.valueOf(yyyy); int p = 4 - s.length(); while (p > 0) { sb.append('0'); p--; } sb.append(s).append('-'); s = String.valueOf(mm); p = 2 - s.length(); while (p > 0) { sb.append('0'); p--; } sb.append(s).append('-'); s = String.valueOf(dd); p = 2 - s.length(); while (p > 0) { sb.append('0'); p--; } sb.append(s); return sb.toString(); } /** * Doesn't change anything, just formats passed date as string according to widget's current settings. */ public String formatDate(Date d) { if (d == null) return null; JsDate jsd = JsDate.create(d.getTime()); return internFormat(input.getElement(), getActiveDateFormat(), jsd); } private static native String internFormat(Element elt, String fmt, JsDate d) /*-{ return $wnd.$(elt).datebox('callFormat', fmt, d); }-*/; private static native JsDate internGetDate(Element elt) /*-{ return $wnd.$(elt).datebox('getTheDate'); }-*/; private static native boolean internIsSelDateVisible(Element elt) /*-{ return $wnd.$(elt).datebox('dateVisible'); }-*/; /** * @return - true if the selected calendar date is visible. * <br> Only valid for calbox, otherwise it will always return true. */ public boolean isSelectedDateVisible() { return internIsSelDateVisible(input.getElement()); } private static native void internalSetDate(Element elt, double d) /*-{ $wnd.$(elt).datebox('setTheDate', new $wnd.Date(d)); }-*/; private static native void internalSetDate(Element elt, int year, int month, int dayOfMonth) /*-{ $wnd.$(elt).datebox('setTheDate', new $wnd.Date(year, month, dayOfMonth)); }-*/; private void internSetDate(Date d) { final double v = d == null ? NULL_DATE : d.getTime(); final Element elt = input.getElement(); JsDate jsd = internGetDate(elt); if (jsd != null) { double t = jsd.getTime(); if (t == v) return; // setTheDate() is expensive! } isInternSetDate = true; internDateToSet = d; try { // ValueChange may occur! internalSetDate(elt, v); } finally { isInternSetDate = false; } } // datebox('option') gives a full options list, it's inherited from the jquery-ui widget library // and working because jquery.ui.widget.js has the following declaration: // option: function( key, value ) { ... }, see https://github.com/jquery/jquery-ui/blob/master/ui/widget.js // // If you need to retrieve a single option, you can also do this (see https://github.com/jtsage/jquery-mobile-datebox/issues/340#issuecomment-61712507): // var myOption = $wnd.$(elt).datebox('getOption', 'dateFormat'); // 'getOption' is jtsage-datebox function with i18n knowledge, 'option' is jquery-ui function without i18n knowledge. // // See also: http://stackoverflow.com/a/8217857 // and http://dev.jtsage.com/DateBox/api/cat-event/ // and http://stackoverflow.com/a/15337587 // private static native JavaScriptObject internGetLangOption(Element elt, String val) /*-{ // var o = $wnd.$(elt).datebox('option'); return $wnd.$(elt).datebox('getOption', val); }-*/; private static native String internGetLangOptionStr(Element elt, String val) /*-{ // var o = $wnd.$(elt).datebox('option'); return $wnd.$(elt).datebox('getOption', val); }-*/; /** Gives "Global"/JsPrototype language option. * <br> From jtsage-datebox.js partial copy of: __: function(val) { ... } * <br> See for example: jtsage-datebox.i18n.ru.utf8.min.js **/ private static native JavaScriptObject internGetProtoLangOption(String val) /*-{ var o = $wnd.jQuery.jtsage.datebox.prototype.options; var lang = o.lang[o.useLang]; if (typeof lang !== 'undefined' && typeof lang[val] !== 'undefined') { return lang[val]; } return o.lang['default'][val]; }-*/; /** * @param mm - month 0-11, Jan = 0 .. Dec = 11 * @param dd - day 1-31 */ private String formatGridDate(int yyyy, int mm, int dd, String iso8601, boolean selectedDateVisible) { if (gridDateFormatter == null) { return String.valueOf(dd); } else { return gridDateFormatter.format(yyyy, mm, dd, iso8601, selectedDateVisible); } } private void formatGridDateEx(int yyyy, int mm, int dd, String iso8601, boolean selectedDateVisible, JavaScriptObject result) { if (!(gridDateFormatter instanceof GridDateFormatterEx)) { JsUtils.setObjValue(result, "text", String.valueOf(dd)); JsUtils.setObjValue(result, "class", ""); } else { String text = gridDateFormatter.format(yyyy, mm, dd, iso8601, selectedDateVisible); String cls = ((GridDateFormatterEx) gridDateFormatter).getStyleNames( yyyy, mm, dd, iso8601, selectedDateVisible); JsUtils.setObjValue(result, "text", text); JsUtils.setObjValue(result, "class", cls); } } private int getGridDateFormatterType() { if (gridDateFormatter == null) return 0; if (gridDateFormatter instanceof GridDateFormatterEx) return 2; return 1; } private static native void initGridDateFormatter(Element elt, JQMCalBox ctrl) /*-{ if (ctrl === null) { var v = $wnd.$(elt).datebox( 'getOption', 'calFormatter' ); if (v !== false) $wnd.$(elt).datebox( { 'calFormatter': false } ); } else { $wnd.$(elt).datebox( { 'calFormatter': function( date ) { var t = ctrl.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::getGridDateFormatterType()(); if (t === 0) return date.Date; else if (t === 1) { var s = ctrl.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::formatGridDate( IIILjava/lang/String;Z) (date.Year, date.Month, date.Date, date.ISO, date.dateVisible); return s; } else { var rslt = {}; ctrl.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::formatGridDateEx( IIILjava/lang/String;ZLcom/google/gwt/core/client/JavaScriptObject;) (date.Year, date.Month, date.Date, date.ISO, date.dateVisible, rslt); return rslt; } }}); } }-*/; private void gridDateBoxBeforeAppend(Element box, JavaScriptObject data) { if (gridDateBox != null) { JsDateBoxData d = data.cast(); gridDateBox.beforeAppend(box, d); } } // See http://dev.jtsage.com/DateBox/api/calBeforeAppendFunc/ private static native void initGridDateBoxBeforeAppend(Element elt, JQMCalBox ctrl) /*-{ if (ctrl === null) { $wnd.$(elt).datebox({ 'calBeforeAppendFunc': function(t) { return t; } }); } else { $wnd.$(elt).datebox({ 'calBeforeAppendFunc': function(t) { if (t) { ctrl.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::gridDateBoxBeforeAppend( Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/core/client/JavaScriptObject;) (t[0], t.data()); } return t; }}); } }-*/; private void initGridDateFormatter() { if (!isReady()) return; initGridDateFormatter(input.getElement(), gridDateFormatter != null ? this : null); } private void initGridDateBoxBeforeAppend() { if (!isReady()) return; initGridDateBoxBeforeAppend(input.getElement(), gridDateBox != null ? this : null); } public GridDateFormatter getGridDateFormatter() { return gridDateFormatter; } /** Additional information can be added to days (1..31) buttons. */ public void setGridDateFormatter(GridDateFormatter gridDateFormatter) { this.gridDateFormatter = gridDateFormatter; initGridDateFormatter(); } public GridDateBox getGridDateBoxBeforeAppend() { return gridDateBox; } /** This option allows you to define a custom function that is called on the generated * calbox grid box of each date. * <br> See <a href="http://dev.jtsage.com/DateBox/api/calBeforeAppendFunc/">calBeforeAppendFunc</a> **/ public void setGridDateBoxBeforeAppend(GridDateBox value) { this.gridDateBox = value; initGridDateBoxBeforeAppend(); } public boolean isIconNoDisc() { return JQMCommon.isIconNoDisc(this); } public void setIconNoDisc(boolean value) { JQMCommon.setIconNoDisc(this, value); } public boolean isIconAlt() { return JQMCommon.isIconAlt(this); } /** * @param value - if true "white vs. black" icon style will be used */ public void setIconAlt(boolean value) { JQMCommon.setIconAlt(this, value); } public HandlerRegistration addCalBoxHandler(JQMCalBoxEvent.Handler handler) { if (handler == null) return null; HandlerRegistration rslt = addHandler(handler, JQMCalBoxEvent.getType()); if (!calBoxHandlerAdded) { calBoxHandlerAdded = true; initDisplayChange(); initOffset(); } return rslt; } private static native void initDisplayChange(Element elt, JQMCalBox ctrl) /*-{ if (ctrl == null) { $wnd.$(elt).off('datebox.displayChange'); } else { $wnd.$(elt).on('datebox.displayChange', function (e, p) { if ( p.method === 'displayChange' ) { var changeAmount = p.thisChangeAmount == null ? 0 : p.thisChangeAmount; ctrl.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::fireDisplayChange( Lcom/google/gwt/core/client/JsDate;Lcom/google/gwt/core/client/JsDate;Ljava/lang/String;I) (p.shownDate, p.selectedDate, p.thisChange, changeAmount); } }); } }-*/; private static native void initOffset(Element elt, JQMCalBox ctrl) /*-{ if (ctrl == null) { $wnd.$(elt).off('datebox.offset'); } else { $wnd.$(elt).on('datebox.offset', function (e, p) { if ( p.method === 'offset' ) { var changeAmount = p.amount == null ? 0 : p.amount; ctrl.@com.sksamuel.jqm4gwt.plugins.datebox.JQMCalBox::fireOffset( Lcom/google/gwt/core/client/JsDate;Ljava/lang/String;I) (p.newDate, p.type, changeAmount); } }); } }-*/; private void initDisplayChange() { if (!calBoxHandlerAdded || !isReady()) return; initDisplayChange(input.getElement(), this); } private void initOffset() { if (!calBoxHandlerAdded || !isReady()) return; initOffset(input.getElement(), this); } private void fireDisplayChange(JsDate shownDate, JsDate selectedDate, String thisChange, int thisChangeAmount) { JQMCalBoxEvent.fire(this, new DisplayChangeData(JQMContext.jsDateToDate(shownDate), JQMContext.jsDateToDate(selectedDate), thisChange, thisChangeAmount)); } private void fireOffset(JsDate newDate, String changeType, int changeAmount) { JQMCalBoxEvent.fire(this, new OffsetData(JQMContext.jsDateToDate(newDate), changeType, changeAmount)); } }