/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.renderkit;
import java.io.IOException;
import java.text.DateFormatSymbols;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.TimeZone;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.DateTimeConverter;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSReference;
import org.richfaces.component.AbstractCalendar;
import org.richfaces.component.MetaComponentResolver;
import org.richfaces.component.Positioning;
import org.richfaces.component.util.HtmlUtil;
import org.richfaces.component.util.InputUtils;
import org.richfaces.component.util.InputUtils.ConverterLookupStrategy;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.context.ExtendedPartialViewContext;
import org.richfaces.event.CurrentDateChangeEvent;
import org.richfaces.utils.CalendarHelper;
/**
* @author amarkhel
*
*/
@ResourceDependencies({ @ResourceDependency(library = "javax.faces", name = "jsf.js"),
@ResourceDependency(library = "org.richfaces", name = "jquery.js"),
@ResourceDependency(library = "org.richfaces", name = "richfaces.js"),
@ResourceDependency(library = "org.richfaces", name = "richfaces-queue.reslib"),
@ResourceDependency(library = "org.richfaces", name = "richfaces-base-component.js"), @ResourceDependency(library = "org.richfaces", name = "jquery.position.js"),
@ResourceDependency(library = "org.richfaces", name = "richfaces-event.js"), @ResourceDependency(library = "org.richfaces", name = "json-dom.js"),
@ResourceDependency(library = "org.richfaces", name = "jquery.effects.core.js"),
@ResourceDependency(library = "org.richfaces", name = "jquery.effects.highlight.js"),
@ResourceDependency(library = "org.richfaces", name = "JQuerySpinBtn.js"),
@ResourceDependency(library = "org.richfaces", name = "calendar-utils.js"),
@ResourceDependency(library = "org.richfaces", name = "calendar.js"),
@ResourceDependency(library = "org.richfaces", name = "calendar.ecss") })
public class CalendarRendererBase extends InputRendererBase implements MetaComponentRenderer {
public static final String CALENDAR_BUNDLE = "org.richfaces.renderkit.calendar";
public static final String OPTION_DISABLED = "disabled";
public static final String OPTION_MIN_DAYS_IN_FIRST_WEEK = "minDaysInFirstWeek";
public static final String MONTH_LABELS_SHORT = "monthLabelsShort";
public static final String MONTH_LABELS = "monthLabels";
public static final String WEEK_DAY_LABELS_SHORT = "weekDayLabelsShort";
public static final String WEEK_DAY_LABELS = "weekDayLabels";
public static final String FIRST_DAY_WEEK = "firstWeekDay";
public static final String MIN_DAYS_IN_FIRST_WEEK = "minDaysInFirstWeek";
public static final String CALENDAR_ICON_RESOURCE_NAME = "calendarIcon.png";
public static final String CALENDAR_DISABLE_ICON_RESOURCE_NAME = "disabledCalendarIcon.png";
public static final String CURRENT_DATE_INPUT = "InputCurrentDate";
protected static final Map<String, ComponentAttribute> CALENDAR_INPUT_HANDLER_ATTRIBUTES = Collections
.unmodifiableMap(ComponentAttribute.createMap(
new ComponentAttribute(HtmlConstants.ONCLICK_ATTRIBUTE).setEventNames("inputclick").setComponentAttributeName(
"oninputclick"),
new ComponentAttribute(HtmlConstants.ONDBLCLICK_ATTRIBUTE).setEventNames("inputdblclick")
.setComponentAttributeName("oninputdblclick"),
new ComponentAttribute(HtmlConstants.ONMOUSEDOWN_ATTRIBUTE).setEventNames("inputmousedown")
.setComponentAttributeName("oninputmousedown"),
new ComponentAttribute(HtmlConstants.ONMOUSEUP_ATTRIBUTE).setEventNames("inputmouseup").setComponentAttributeName(
"oninputmouseup"),
new ComponentAttribute(HtmlConstants.ONMOUSEOVER_ATTRIBUTE).setEventNames("inputmouseover")
.setComponentAttributeName("oninputmouseover"),
new ComponentAttribute(HtmlConstants.ONMOUSEMOVE_ATTRIBUTE).setEventNames("inputmousemove")
.setComponentAttributeName("oninputmousemove"),
new ComponentAttribute(HtmlConstants.ONMOUSEOUT_ATTRIBUTE).setEventNames("inputmouseout")
.setComponentAttributeName("oninputmouseout"),
new ComponentAttribute(HtmlConstants.ONKEYPRESS_ATTRIBUTE).setEventNames("inputkeypress")
.setComponentAttributeName("oninputkeypress"),
new ComponentAttribute(HtmlConstants.ONKEYDOWN_ATTRIBUTE).setEventNames("inputkeydown").setComponentAttributeName(
"oninputkeydown"),
new ComponentAttribute(HtmlConstants.ONKEYUP_ATTRIBUTE).setEventNames("inputkeyup").setComponentAttributeName(
"oninputkeyup"),
new ComponentAttribute(HtmlConstants.ONBLUR_ATTRIBUTE).setEventNames("inputblur").setComponentAttributeName(
"oninputblur"),
new ComponentAttribute(HtmlConstants.ONFOCUS_ATTRIBUTE).setEventNames("inputfocus").setComponentAttributeName(
"oninputfocus"),
new ComponentAttribute(HtmlConstants.ONCHANGE_ATTRIBUTE).setEventNames("inputchange").setComponentAttributeName(
"oninputchange"),
new ComponentAttribute(HtmlConstants.ONSELECT_ATTRIBUTE).setEventNames("inputselect").setComponentAttributeName(
"oninputselect")));
private static final String HOURS_VALUE = "hours";
private static final String MINUTES_VALUE = "minutes";
private static final String SECONDS_VALUE = "seconds";
protected final ConverterLookupStrategy calendarConverterLookupStrategy = new ConverterLookupStrategy() {
public Converter getConverterByValue(FacesContext context, UIComponent component, Object value)
throws ConverterException {
AbstractCalendar calendar = (AbstractCalendar) component;
Converter converter = calendar.getConverter();
if (converter == null && value != null) {
converter = InputUtils.getConverterForType(context, value.getClass());
}
// in case the converter hasn't been set, try to use default
// DateTimeConverter
if (converter == null) {
converter = createDefaultConverter(context);
}
setupConverter(context, converter, (AbstractCalendar) component);
return converter;
}
public Converter getConverterByProperty(FacesContext context, UIComponent component) throws ConverterException {
AbstractCalendar calendar = (AbstractCalendar) component;
Converter converter = InputUtils.findConverter(context, calendar, "value");
// in case the converter hasn't been set, try to use default
// DateTimeConverter
if (converter == null) {
converter = createDefaultConverter(context);
}
setupConverter(context, converter, calendar);
return converter;
}
};
protected void doDecode(FacesContext context, UIComponent component) {
if (!(component instanceof AbstractCalendar)) {
return;
}
AbstractCalendar calendar = (AbstractCalendar) component;
if (calendar.isDisabled()) {
return;
}
Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
String clientId = calendar.getClientId(context);
String currentDateString = (String) requestParameterMap.get(clientId + CURRENT_DATE_INPUT);
if (currentDateString != null) {
calendar.queueEvent(new CurrentDateChangeEvent(calendar, currentDateString));
}
String selectedDateString = requestParameterMap.get(clientId + "InputDate");
if (selectedDateString != null) {
calendar.setSubmittedValue(selectedDateString);
}
if (requestParameterMap.get(component.getClientId(context) + ".ajax") != null) {
PartialViewContext pvc = context.getPartialViewContext();
pvc.getRenderIds().add(
component.getClientId(context) + MetaComponentResolver.META_COMPONENT_SEPARATOR_CHAR
+ AbstractCalendar.DAYSDATA_META_COMPONENT_ID);
context.renderResponse();
}
}
public void renderInputHandlers(FacesContext facesContext, UIComponent component) throws IOException {
RenderKitUtils.renderPassThroughAttributesOptimized(facesContext, component, CALENDAR_INPUT_HANDLER_ATTRIBUTES);
}
@Override
public Object getConvertedValue(FacesContext facesContext, UIComponent component, Object submittedValue)
throws ConverterException {
if ((facesContext == null) || (component == null)) {
throw new NullPointerException();
}
// skip conversion of already converted date
if (submittedValue instanceof Date) {
return submittedValue;
}
return InputUtils.getConvertedValue(facesContext, component, calendarConverterLookupStrategy, submittedValue);
}
@Override
public String getInputValue(FacesContext facesContext, UIComponent component) {
if (!(component instanceof AbstractCalendar)) {
return null;
}
return InputUtils.getInputValue(facesContext, component, calendarConverterLookupStrategy);
}
public String getButtonIcon(FacesContext facesContext, UIComponent component) {
boolean disable = (Boolean) component.getAttributes().get(OPTION_DISABLED);
String buttonIcon = (String) component.getAttributes().get("buttonIcon");
if (disable) {
buttonIcon = (String) component.getAttributes().get("buttonDisabledIcon");
}
if (buttonIcon != null && buttonIcon.trim().length() != 0) {
buttonIcon = RenderKitUtils.getResourceURL(buttonIcon, facesContext);
} else {
buttonIcon = disable ? CALENDAR_DISABLE_ICON_RESOURCE_NAME : CALENDAR_ICON_RESOURCE_NAME;
buttonIcon = RenderKitUtils.getResourcePath(facesContext, "org.richfaces.images", buttonIcon);
}
return buttonIcon;
}
public Object getSelectedDate(FacesContext facesContext, UIComponent component) throws IOException {
Object returnValue = null;
AbstractCalendar calendar = (AbstractCalendar) component;
if (calendar.isValid()) {
Date date;
Object value = calendar.getValue();
date = CalendarHelper.getAsDate(facesContext, calendar, value);
if (date != null) {
returnValue = formatSelectedDate(CalendarHelper.getTimeZoneOrDefault(calendar), date);
}
}
return returnValue;
}
public static Object formatSelectedDate(TimeZone timeZone, Date date) {
FacesContext facesContext = FacesContext.getCurrentInstance();
AbstractCalendar calendarInstance = (AbstractCalendar) AbstractCalendar.getCurrentComponent(facesContext);
Calendar calendar = CalendarHelper.getCalendar(facesContext, calendarInstance);
calendar.setTimeZone(timeZone);
calendar.setTime(date);
JSFunction result = new JSFunction("new Date");
result.addParameter(calendar.get(Calendar.YEAR));
result.addParameter(calendar.get(Calendar.MONTH));
result.addParameter(calendar.get(Calendar.DATE));
result.addParameter(calendar.get(Calendar.HOUR_OF_DAY));
result.addParameter(calendar.get(Calendar.MINUTE));
result.addParameter(0);
return result;
}
public Object getCurrentDate(FacesContext facesContext, UIComponent component) throws IOException {
AbstractCalendar calendar = (AbstractCalendar) component;
Date date = CalendarHelper.getCurrentDateOrDefault(facesContext, calendar);
return formatDate(date);
}
public String getCurrentDateAsString(FacesContext facesContext, UIComponent component) throws IOException {
AbstractCalendar calendar = (AbstractCalendar) component;
Format formatter = new SimpleDateFormat("MM/yyyy");
Date currentDate = CalendarHelper.getCurrentDateOrDefault(facesContext, calendar);
return formatter.format(currentDate);
}
public static Object formatDate(Date date) {
FacesContext facesContext = FacesContext.getCurrentInstance();
AbstractCalendar calendarInstance = (AbstractCalendar) AbstractCalendar.getCurrentComponent(facesContext);
Calendar calendar = CalendarHelper.getCalendar(facesContext, calendarInstance);
calendar.setTime(date);
JSFunction result = new JSFunction("new Date");
result.addParameter(calendar.get(Calendar.YEAR));
result.addParameter(calendar.get(Calendar.MONTH));
result.addParameter(calendar.get(Calendar.DATE));
return result;
}
public String getDayCellClass(FacesContext facesContext, UIComponent component) {
// TODO: refactor this
/*
* String cellwidth = (String) component.getAttributes().get("cellWidth"); String cellheight = (String)
* component.getAttributes().get("cellHeight"); if (cellwidth != null && cellwidth.length() != 0 || cellheight != null
* && cellheight.length() != 0) { String clientId = component.getClientId(context); String value = clientId.replace(':',
* '_') + "DayCell"; return value; }
*/
return null;
}
public JSReference getDayEnabled(FacesContext facesContext, UIComponent component) {
AbstractCalendar calendar = (AbstractCalendar) component;
String dayEnabled = calendar.getDayDisableFunction();
return ((dayEnabled != null && dayEnabled.trim().length() != 0)) ? new JSReference(dayEnabled) : null;
}
public JSReference getDayStyleClass(FacesContext context, UIComponent component) {
AbstractCalendar calendar = (AbstractCalendar) component;
String dayStyleClass = calendar.getDayClassFunction();
return ((dayStyleClass != null && dayStyleClass.trim().length() != 0)) ? new JSReference(dayStyleClass) : null;
}
public Map<String, Object> getLabels(FacesContext facesContext, UIComponent component) {
AbstractCalendar calendar = (AbstractCalendar) component;
ResourceBundle bundle1 = null;
ResourceBundle bundle2 = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String messageBundle = facesContext.getApplication().getMessageBundle();
Locale locale = CalendarHelper.getAsLocale(facesContext, calendar);
if (null != messageBundle) {
bundle1 = ResourceBundle.getBundle(messageBundle, locale, loader);
}
try {
bundle2 = ResourceBundle.getBundle(CALENDAR_BUNDLE, locale, loader);
} catch (MissingResourceException e) {
// No external bundle was found, ignore this exception.
}
ResourceBundle[] bundles = { bundle1, bundle2 };
String[] names = { "apply", "today", "clean", "cancel", "ok", "close" };
return getCollectedLabels(bundles, names);
}
protected Map<String, Object> getCollectedLabels(ResourceBundle[] bundles, String[] names) {
Map<String, Object> labels = new HashMap<String, Object>();
if (bundles != null && names != null) {
for (String name : names) {
String label = null;
String bundleKey = "RICH_CALENDAR_" + name.toUpperCase() + "_LABEL";
for (ResourceBundle bundle : bundles) {
if (bundle != null) {
try {
label = bundle.getString(bundleKey);
} catch (MissingResourceException mre) {
// Current key was not found, ignore this exception;
}
}
if (label != null) {
break;
}
}
RenderKitUtils.addToScriptHash(labels, name, label);
}
}
return labels;
}
public Map<String, Object> getPreparedDefaultTime(FacesContext facesContext, UIComponent component) {
AbstractCalendar abstractCalendar = (AbstractCalendar) component;
Date date = CalendarHelper.getFormattedDefaultTime(abstractCalendar);
Map<String, Object> result = new HashMap<String, Object>();
if (date != null) {
Calendar calendar = CalendarHelper.getCalendar(null, null);
calendar.setTime(date);
int hours = calendar.get(Calendar.HOUR_OF_DAY);
int minutes = calendar.get(Calendar.MINUTE);
int seconds = calendar.get(Calendar.SECOND);
if (hours != 12 || minutes != 0 || seconds != 0) {
result.put(HOURS_VALUE, hours);
result.put(MINUTES_VALUE, minutes);
result.put(SECONDS_VALUE, seconds);
}
}
if (result.size() > 0) {
return result;
} else {
return null;
}
}
private static String[] shiftDates(int minimum, int maximum, String[] labels) {
if (minimum == 0 && (maximum - minimum == labels.length - 1)) {
return labels;
}
String[] shiftedLabels = new String[maximum - minimum + 1];
System.arraycopy(labels, minimum, shiftedLabels, 0, maximum - minimum + 1);
return shiftedLabels;
}
protected Map<String, Object> getLocaleOptions(FacesContext facesContext, UIComponent component) {
AbstractCalendar calendarComponent = (AbstractCalendar) component;
Map<String, Object> map = new HashMap<String, Object>();
Locale locale = CalendarHelper.getAsLocale(facesContext, calendarComponent);
DateFormatSymbols dateFormat = new DateFormatSymbols(locale);
Calendar calendar = CalendarHelper.getCalendar(facesContext, calendarComponent);
int maximum = calendar.getActualMaximum(Calendar.DAY_OF_WEEK);
int minimum = calendar.getActualMinimum(Calendar.DAY_OF_WEEK);
int monthMax = calendar.getActualMaximum(Calendar.MONTH);
int monthMin = calendar.getActualMinimum(Calendar.MONTH);
String[] weekDayLabels = RenderKitUtils.asArray(calendarComponent.getWeekDayLabels());
if (isEmptyArray(weekDayLabels)) {
weekDayLabels = dateFormat.getWeekdays();
weekDayLabels = shiftDates(minimum, maximum, weekDayLabels);
}
RenderKitUtils.addToScriptHash(map, WEEK_DAY_LABELS, weekDayLabels);
String[] weekDayLabelsShort = RenderKitUtils.asArray(calendarComponent.getWeekDayLabelsShort());
if (isEmptyArray(weekDayLabelsShort)) {
weekDayLabelsShort = dateFormat.getShortWeekdays();
weekDayLabelsShort = shiftDates(minimum, maximum, weekDayLabelsShort);
}
RenderKitUtils.addToScriptHash(map, WEEK_DAY_LABELS_SHORT, weekDayLabelsShort);
String[] monthLabels = RenderKitUtils.asArray(calendarComponent.getMonthLabels());
if (isEmptyArray(monthLabels)) {
monthLabels = dateFormat.getMonths();
monthLabels = shiftDates(monthMin, monthMax, monthLabels);
}
RenderKitUtils.addToScriptHash(map, MONTH_LABELS, monthLabels);
String[] monthLabelsShort = RenderKitUtils.asArray(calendarComponent.getMonthLabelsShort());
if (isEmptyArray(monthLabelsShort)) {
monthLabelsShort = dateFormat.getShortMonths();
monthLabelsShort = shiftDates(monthMin, monthMax, monthLabelsShort);
}
RenderKitUtils.addToScriptHash(map, MONTH_LABELS_SHORT, monthLabelsShort);
int minDaysInFirstWeek = calendarComponent.getMinDaysInFirstWeek();
if (1 > minDaysInFirstWeek || minDaysInFirstWeek > 7) {
minDaysInFirstWeek = calendar.getMinimalDaysInFirstWeek();
}
if (0 <= minDaysInFirstWeek && minDaysInFirstWeek <= 7) {
RenderKitUtils.addToScriptHash(map, MIN_DAYS_IN_FIRST_WEEK, minDaysInFirstWeek);
}
int day = calendarComponent.getFirstWeekDay();
if (day < 0 || 6 < day) {
day = calendar.getFirstDayOfWeek() - calendar.getActualMinimum(Calendar.DAY_OF_WEEK);
}
if (0 <= day && day <= 6) {
RenderKitUtils.addToScriptHash(map, FIRST_DAY_WEEK, day);
} else {
throw new IllegalArgumentException(day + " value of firstWeekDay attribute is not a legal one for component: "
+ MessageUtil.getLabel(facesContext, calendarComponent));
}
return map;
}
private boolean isEmptyArray(String[] array) {
if (array != null) {
for (String str : array) {
if (str.trim().length() > 0) {
return false;
}
}
}
return true;
}
public String getPopupStyle(FacesContext facesContext, UIComponent component) {
AbstractCalendar calendar = (AbstractCalendar) component;
int zindex = calendar.getZindex();
if (zindex < 0) {
zindex = 3;
}
return HtmlUtil.concatStyles("z-index: " + zindex, calendar.getPopupStyle());
}
public Locale getAsLocale(FacesContext facesContext, UIComponent component) {
return CalendarHelper.getAsLocale(facesContext, component);
}
public String writePreloadBody(FacesContext context, UIComponent component) throws IOException {
AbstractCalendar calendar = (AbstractCalendar) component;
Object preload = calendar.getPreload();
if (preload != null) {
return RenderKitUtils.toScriptArgs(preload);
} else {
return null;
}
}
public boolean isUseIcons(FacesContext facesContext, UIComponent component) {
Object label = component.getAttributes().get("buttonLabel");
return (label == null || ((String) label).trim().length() == 0);
}
protected Converter createDefaultConverter(FacesContext facesContext) {
if (facesContext == null) {
return null;
}
return facesContext.getApplication().createConverter(DateTimeConverter.CONVERTER_ID);
}
protected Converter setupConverter(FacesContext facesContext, Converter converter, AbstractCalendar calendar) {
if (converter == null || calendar == null) {
return null;
}
if (converter instanceof DateTimeConverter) {
DateTimeConverter defaultConverter = (DateTimeConverter) converter;
defaultConverter.setPattern(CalendarHelper.getDatePatternOrDefault(calendar));
defaultConverter.setLocale(CalendarHelper.getAsLocale(facesContext, calendar));
defaultConverter.setTimeZone(CalendarHelper.getTimeZoneOrDefault(calendar));
}
return converter;
}
public void encodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) throws IOException {
if (AbstractCalendar.DAYSDATA_META_COMPONENT_ID.equals(metaComponentId)) {
Object preload = ((AbstractCalendar) component).getPreload();
Map<String, Object> dataMap = ExtendedPartialViewContext.getInstance(context).getResponseComponentDataMap();
dataMap.put(component.getClientId(context), preload);
} else {
throw new IllegalArgumentException(metaComponentId);
}
}
public void decodeMetaComponent(FacesContext context, UIComponent component, String metaComponentId) {
throw new UnsupportedOperationException();
}
protected String getTodayControlModeOrDefault(UIComponent component) {
String value = "";
if (component instanceof AbstractCalendar) {
value = ((AbstractCalendar) component).getTodayControlMode();
if (value == null || value.length() == 0) {
value = "select";
}
}
return value;
}
protected String getJointPoint(UIComponent component) {
if (component instanceof AbstractCalendar) {
Positioning jointPoint = ((AbstractCalendar) component).getJointPoint();
if (jointPoint != null) {
return jointPoint.getValue();
}
}
// return null to use default value on client
return null;
}
protected String getDirection(UIComponent component) {
if (component instanceof AbstractCalendar) {
Positioning direction = ((AbstractCalendar) component).getDirection();
if (direction != null) {
return direction.getValue();
}
}
// return null to use default value on client
return null;
}
protected String getBoundaryDatesModeOrDefault(UIComponent component) {
String value = "";
if (component instanceof AbstractCalendar) {
value = ((AbstractCalendar) component).getBoundaryDatesMode();
if (value == null || value.length() == 0) {
value = "inactive";
}
}
return value;
}
protected AbstractCalendar.Mode getModeOrDefault(UIComponent component) {
AbstractCalendar.Mode value = ((AbstractCalendar) component).getMode();
if (value == null) {
value = AbstractCalendar.Mode.client;
}
return value;
}
}