/**
* License Agreement.
*
* JBoss RichFaces - Ajax4jsf Component Library
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.richfaces.renderkit;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.text.DateFormatSymbols;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TimeZone;
import javax.el.ValueExpression;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.DateTimeConverter;
import javax.faces.event.PhaseId;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSFunctionDefinition;
import org.ajax4jsf.javascript.JSLiteral;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.component.UICalendar;
import org.richfaces.component.util.ComponentUtil;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.context.RequestContext;
import org.richfaces.event.CurrentDateChangeEvent;
/**
* @author Nick Belaevski - mailto:nbelaevski@exadel.com created 08.06.2007
*
*/
public class CalendarRendererBase extends TemplateEncoderRendererBase {
/**
*
*/
private static final String MARKUP_PARAMETER_SUFFIX = "Markup";
protected static final String MONTH_LABELS_SHORT = "monthLabelsShort";
protected static final String MONTH_LABELS = "monthLabels";
protected static final String WEEK_DAY_LABELS_SHORT = "weekDayLabelsShort";
protected static final String WEEK_DAY_LABELS = "weekDayLabels";
/**
* Constant "hours"
*/
private static String HOURS_VALUE = "hours";
/**
* Constant "minutes"
*/
private static String MINUTES_VALUE = "minutes";
/**
* The constant used to resolve id of hidden input placed on the page
* for storing current date in "MM/yyyy" format.
* Actual id of hidden input used on the page is #{clientId}InputCurrentDate
*/
public static final String CURRENT_DATE_INPUT = "InputCurrentDate";
public static final String CURRENT_DATE_PRELOAD = "PreloadCurrentDate";
public static final String CALENDAR_BUNDLE = "org.richfaces.renderkit.calendar";
private static final String LOCALES_KEY = "rich:locales";
private final static Log log = LogFactory
.getLog(CalendarRendererBase.class);
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.framework.renderer.RendererBase#getComponentClass()
*/
protected Class<? extends UIComponent> getComponentClass() {
return UICalendar.class;
}
public void addPopupToAjaxRendered(FacesContext context,
UICalendar component) {
AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
Set<String> ajaxRenderedAreas = ajaxContext.getAjaxRenderedAreas();
String clientId = component.getClientId(context);
if (ajaxContext.isAjaxRequest() && ajaxRenderedAreas.contains(clientId)) {
ajaxRenderedAreas.add(clientId + "Popup");
ajaxRenderedAreas.add(clientId + "IFrame");
ajaxRenderedAreas.add(clientId + "Script");
}
}
@Override
public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
throws ConverterException {
if ((context == null) || (component == null)) {
throw new NullPointerException();
}
// skip conversion of already converted date
if (submittedValue instanceof Date) {
return (Date) submittedValue;
}
// Store submitted value in the local variable as a string
String newValue = (String) submittedValue;
// if we have no local value, try to get the valueExpression.
ValueExpression valueExpression = component.getValueExpression("value");
Converter converter = null;
UICalendar calendar = (UICalendar) component;
converter = calendar.getConverter();
if ((converter == null) && (valueExpression != null)) {
Class<? extends Object> converterType = valueExpression.getType(context.getELContext());
if((converterType != null) && (converterType != Object.class)) {
// if getType returns a type for which we support a default
// conversion, acquire an appropriate converter instance.
converter = getConverterForClass(converterType, context);
}
}
// in case the converter hasn't been set, try to use default DateTimeConverter
if (converter == null) {
converter = createDefaultConverter();
}
setupDefaultConverter(converter, calendar);
return converter.getAsObject(context, component, newValue);
}
private Converter getConverterForClass(Class <? extends Object> converterClass, FacesContext context) {
if (converterClass == null) {
return null;
}
try {
Application application = context.getApplication();
return (application.createConverter(converterClass));
} catch (Exception e) {
log.warn(e.getLocalizedMessage(), e);
return null;
}
}
/**
* Returns hours and minutes from "defaultTime" attribute as a String with
* special format: hours:"value_hours",minutes:"value_minutes"
*
* @param component - UICalendar
*
* @return hours and minutes from "defaultTime" attribute
*/
public Map<String, Object> getPreparedDefaultTime(UICalendar component) {
Date date = component.getFormattedDefaultTime();
Map<String, Object> result = new HashMap<String, Object>();
if (date != null) {
Calendar calendar = component.getCalendar();
calendar.setTime(date);
int hours = calendar.get(Calendar.HOUR_OF_DAY);
int minutes = calendar.get(Calendar.MINUTE);
if (hours != 12 || minutes != 0) {
result.put(HOURS_VALUE, hours);
result.put(MINUTES_VALUE, minutes);
}
}
return result;
}
/**
* Overloads getFormattedValue to take a advantage of a previously
* obtained converter.
* @param context the FacesContext for the current request
* @param component UIComponent of interest
* @param currentValue the current value of <code>component</code>
* @param converter the component's converter
* @return the currentValue after any associated Converter has been
* applied
*
* @throws ConverterException if the value cannot be converted
*/
protected String getFormattedValue(FacesContext context, UIComponent component, Object currentValue,
Converter converter) throws ConverterException {
// formatting is supported only for components that support
// converting value attributes.
if (!(component instanceof UICalendar)) {
if (currentValue != null) {
return currentValue.toString();
}
return null;
}
UICalendar calendar = (UICalendar) component;
// if value is null and no converter attribute is specified, then
// return a zero length String.
if(currentValue == null) {
return "";
}
if (converter == null) {
// If there is a converter attribute, use it to to ask application
// instance for a converter with this identifier.
converter = calendar.getConverter();
}
if (converter == null) {
// Do not look for "by-type" converters for Strings
if (currentValue instanceof String) {
return (String) currentValue;
}
// if converter attribute set, try to acquire a converter
// using its class type.
Class<? extends Object> converterType = currentValue.getClass();
converter = getConverterForClass(converterType, context);
// if there is no default converter available for this identifier,
// assume the model type to be String.
if (converter == null) {
// in case the converter hasn't been set, try to use default DateTimeConverter
converter = createDefaultConverter();
}
}
setupDefaultConverter(converter, calendar);
return converter.getAsString(context, calendar, currentValue);
}
/**
* @param context the FacesContext for the current request
* @param component UIComponent of interest
* @param currentValue the current value of <code>component</code>
*
* @return the currentValue after any associated Converter has been
* applied
*
* @throws ConverterException if the value cannot be converted
*/
protected String getFormattedValue(FacesContext context, UIComponent component, Object currentValue)
throws ConverterException {
return getFormattedValue(context, component, currentValue, null);
}
/**
* Creates default <code>DateTimeConverter</code> for the calendar
*
* @return created converter
*/
protected static Converter createDefaultConverter() {
return new DateTimeConverter();
}
/**
* Setup the default converter provided by JSF API
* (<code>DateTimeConverter</code>) with the component settings
* @param converter
* @param calendar
* @return
*/
protected static Converter setupDefaultConverter(Converter converter, UICalendar calendar) {
// skip id converter is null
if(converter == null) {
return null;
}
if(converter instanceof DateTimeConverter) {
DateTimeConverter defaultConverter = (DateTimeConverter) converter;
defaultConverter.setPattern(calendar.getDatePattern());
defaultConverter.setLocale(calendar.getAsLocale(calendar.getLocale()));
defaultConverter.setTimeZone(calendar.getTimeZone());
}
return converter;
}
@Override
protected void doDecode(FacesContext context, UIComponent component) {
// TODO Auto-generated method stub
if (((UICalendar)component).isDisabled()){
return;
}
super.doDecode(context, component);
String clientId = component.getClientId(context);
Map<String, String> requestParameterMap = context.getExternalContext()
.getRequestParameterMap();
String currentDateString = (String) requestParameterMap.get(clientId + CURRENT_DATE_INPUT);
if (currentDateString != null) {
CurrentDateChangeEvent ev = new CurrentDateChangeEvent(component,
currentDateString);
ev.setPhaseId(PhaseId.PROCESS_VALIDATIONS);
ev.queue();
}
if (requestParameterMap.get(clientId + CURRENT_DATE_PRELOAD) != null) {
// TODO nick - nick - queue this event when ValueChangeEvent is
// queued?
new AjaxEvent(component).queue();
AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
if (ajaxContext.isAjaxRequest(context)) {
ajaxContext.addAreasToProcessFromComponent(context, component);
}
}
String selectedDateString = (String) requestParameterMap.get(clientId
+ "InputDate");
if (selectedDateString != null) {
((UICalendar) component).setSubmittedValue(selectedDateString);
}
}
@Override
public void encodeChildren(FacesContext context, UIComponent calendar)
throws IOException {
}
public JSReference getIsDayEnabled(FacesContext context, UIComponent component) {
UICalendar calendar = (UICalendar) component;
String isDayEnabled = (String) calendar.getAttributes().get(
"isDayEnabled");
if (isDayEnabled != null && isDayEnabled.length() != 0) {
return new JSReference(isDayEnabled); //new JSFunction(isDayEnabled);
}
return null;
}
public JSReference getDayStyleClass(FacesContext context, UIComponent component) {
UICalendar calendar = (UICalendar) component;
String dayStyleClass = (String) calendar.getAttributes().get(
"dayStyleClass");
if (dayStyleClass != null && dayStyleClass.length() != 0) {
return new JSReference(dayStyleClass);
}
return null;
}
public String getMarkupScriptBody(FacesContext context, UIComponent component, boolean children)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
Writer dumpingWriter = new StringWriter();
ResponseWriter clonedWriter = writer.cloneWithWriter(dumpingWriter);
context.setResponseWriter(clonedWriter);
writeScriptBody(context, component, children);
clonedWriter.flush();
context.setResponseWriter(writer);
return dumpingWriter.toString();
}
public String getOptionalFacetMarkupScriptBody(FacesContext context,
UIComponent component, String facetName) throws IOException {
UIComponent facet = component.getFacet(facetName);
if (facet != null && facet.isRendered()) {
return getMarkupScriptBody(context, facet, false);
}
return null;
}
public void dayCellClass(FacesContext context, UIComponent component)
throws IOException {
// if cellWidth/Height is set send dayCellClass to script
String cellwidth = (String) component.getAttributes().get("cellWidth");
String cellheight = (String) component.getAttributes()
.get("cellHeight");
ResponseWriter writer = context.getResponseWriter();
String clientId = component.getClientId(context);
String divStyle = "";
if (cellwidth != null && cellwidth.length() != 0) {
if (cellwidth.contains("px") || cellwidth.contains("%")) {
divStyle = divStyle + "width:" + cellwidth + ";";
} else {
divStyle = divStyle + "width:" + cellwidth + "px;";
}
}
if (cellheight != null && cellheight.length() != 0) {
if (cellheight.contains("px") || cellheight.contains("%")) {
divStyle = divStyle + "height:" + cellheight.toString() + ";";
} else {
divStyle = divStyle + "height:" + cellheight.toString() + "px;";
}
}
if (divStyle.length() != 0) {
writer.startElement("style", component);
getUtils().writeAttribute(writer, "type", "text/css");
writer.writeText("." + clientId.replace(':', '_') + "DayCell{"
+ divStyle + "}", null);
writer.endElement("style");
}
}
public String getDayCellClass(FacesContext context, UIComponent component) {
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 void writeFacetMarkup(FacesContext context, UIComponent component) throws IOException {
Map<String, JSLiteral> jsonMap = new HashMap<String, JSLiteral>();
if (component.getChildCount() != 0) {
String markupScriptBody = getMarkupScriptBody(context, component, true);
jsonMap.put("dayList" + MARKUP_PARAMETER_SUFFIX, new JSLiteral(markupScriptBody));
}
addFacetMarkupScriptBody(context, component, jsonMap, "optionalHeader");
addFacetMarkupScriptBody(context, component, jsonMap, "optionalFooter");
addFacetMarkupScriptBody(context, component, jsonMap, "weekDay");
addFacetMarkupScriptBody(context, component, jsonMap, "weekNumber");
addFacetMarkupScriptBody(context, component, jsonMap, "header");
addFacetMarkupScriptBody(context, component, jsonMap, "footer");
context.getResponseWriter().writeText(ScriptUtils.toScript(jsonMap), null);
}
private void addFacetMarkupScriptBody(FacesContext context, UIComponent component, Map<String, JSLiteral> jsonMap, String facetName) throws IOException {
String res = getOptionalFacetMarkupScriptBody(context, component, facetName);
if (res != null) {
jsonMap.put(facetName + MARKUP_PARAMETER_SUFFIX, new JSLiteral(res));
}
}
public void writePreloadBody(FacesContext context, UICalendar calendar)
throws IOException {
Object preload = calendar.getPreload();
if (preload != null) {
ResponseWriter writer = context.getResponseWriter();
writer.writeText(ScriptUtils.toScript(preload), null);
}
}
public Object getSubmitFunction(FacesContext context, UICalendar calendar)
throws IOException {
if (!UICalendar.AJAX_MODE.equals(calendar.getAttributes().get("mode"))) return null;
JSFunction ajaxFunction = AjaxRendererUtils.buildAjaxFunction(calendar,
context, AjaxRendererUtils.AJAX_FUNCTION_NAME);
ajaxFunction.addParameter(JSReference.NULL);
HashMap<String, Object> params = new HashMap<String, Object>();
params.put(calendar.getClientId(context) + CURRENT_DATE_PRELOAD, Boolean.TRUE);
Map<String, Object> options = AjaxRendererUtils.buildEventOptions(context, calendar, params, true);
options.put("calendar", JSReference.THIS);
String oncomplete = AjaxRendererUtils.getAjaxOncomplete(calendar);
JSFunctionDefinition oncompleteDefinition = new JSFunctionDefinition();
oncompleteDefinition.addParameter("request");
oncompleteDefinition.addParameter("event");
oncompleteDefinition.addParameter("data");
oncompleteDefinition.addToBody("this.calendar.load(data, true);");
if (oncomplete != null) {
oncompleteDefinition.addToBody(oncomplete);
}
options.put("oncomplete", oncompleteDefinition);
JSReference requestValue = new JSReference("requestValue");
ajaxFunction.addParameter(options);
JSFunctionDefinition definition = new JSFunctionDefinition();
definition.addParameter(requestValue);
definition.addToBody(ajaxFunction);
return definition;
}
public String getInputValue(FacesContext context, UIComponent component) {
UICalendar calendar = (UICalendar) component;
// Fix for myFaces 1.1.x RF-997
String returnValue = null;
Object value = calendar.getSubmittedValue();
if (value != null) {
try {
returnValue = getFormattedValue(context, calendar, value);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(" InputValue: " + e.toString(), e);
}
returnValue = (String)value;
}
} else {
returnValue = getFormattedValue(context, calendar, calendar.getValue());
}
return returnValue;
}
public void writeDefaultSymbols(FacesContext facesContext, UICalendar calendar)
throws IOException {
RequestContext context = RequestContext.getInstance(facesContext);
Set<String> locales = (Set<String>)context.getAttribute(LOCALES_KEY);
if (locales == null) {
locales = new HashSet<String>(1);
context.setAttribute(LOCALES_KEY, locales);
}
String locale = calendar.getAsLocale().toString();
if (!locales.contains(locale)) {
ResponseWriter writer = facesContext.getResponseWriter();
writer.writeText("Richfaces.Calendar.addLocale('" + locale + "', ", null);
writer.writeText(ScriptUtils.toScript(getDefaultSymbolsMap(calendar)), null);
writer.writeText(");\n", null);
locales.add(locale);
}
}
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> getSymbolsMap(FacesContext facesContext, UICalendar calendar) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
RendererUtils utils = getUtils();
utils.addToScriptHash(map, WEEK_DAY_LABELS, ComponentUtil.asArray(calendar.getWeekDayLabels()));
utils.addToScriptHash(map, WEEK_DAY_LABELS_SHORT, ComponentUtil.asArray(calendar.getWeekDayLabelsShort()));
utils.addToScriptHash(map, MONTH_LABELS, ComponentUtil.asArray(calendar.getMonthLabels()));
utils.addToScriptHash(map, MONTH_LABELS_SHORT, ComponentUtil.asArray(calendar.getMonthLabelsShort()));
int day = calendar.getFirstWeekDay();
if (0 <= day && day <= 6) {
utils.addToScriptHash(map, "firstWeekDay", day);
} else if (day != Integer.MIN_VALUE) {
facesContext.getExternalContext()
.log(day + " value of firstWeekDay attribute is not a legal one for component: "
+ MessageUtil.getLabel(facesContext, calendar) + ". Default value was applied.");
}
return map;
}
protected Map<String, Object> getDefaultSymbolsMap(UICalendar calendar) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
Locale locale = calendar.getAsLocale();
Calendar cal = calendar.getCalendar();
int maximum = cal.getActualMaximum(Calendar.DAY_OF_WEEK);
int minimum = cal.getActualMinimum(Calendar.DAY_OF_WEEK);
int monthMax = cal.getActualMaximum(Calendar.MONTH);
int monthMin = cal.getActualMinimum(Calendar.MONTH);
DateFormatSymbols symbols = new DateFormatSymbols(locale);
String[] weekDayLabels = symbols.getWeekdays();
weekDayLabels = shiftDates(minimum, maximum, weekDayLabels);
String[] weekDayLabelsShort = symbols.getShortWeekdays();
weekDayLabelsShort = shiftDates(minimum, maximum, weekDayLabelsShort);
String[] monthLabels = symbols.getMonths();
monthLabels = shiftDates(monthMin, monthMax, monthLabels);
String[] monthLabelsShort = symbols.getShortMonths();
monthLabelsShort = shiftDates(monthMin, monthMax, monthLabelsShort);
map.put(WEEK_DAY_LABELS, weekDayLabels);
map.put(WEEK_DAY_LABELS_SHORT, weekDayLabelsShort);
map.put(MONTH_LABELS, monthLabels);
map.put(MONTH_LABELS_SHORT, monthLabelsShort);
map.put("minDaysInFirstWeek", cal.getMinimalDaysInFirstWeek());
map.put("firstWeekDay", cal.getFirstDayOfWeek() - cal.getActualMinimum(Calendar.DAY_OF_WEEK));
return map;
}
public String getCurrentDateAsString(FacesContext context,
UICalendar calendar, Date date) throws IOException {
Format formatter = new SimpleDateFormat("MM/yyyy");
return formatter.format(date);
}
public Object getCurrentDate(FacesContext context, UICalendar calendar,
Date date) throws IOException {
return formatDate(date);
}
public Object getSelectedDate(FacesContext context, UICalendar calendar)
throws IOException {
Object returnValue = null;
if(calendar.isValid()) {
Date date;
Object value = calendar.getValue();
date = calendar.getAsDate(value);
if(date != null) {
returnValue = formatSelectedDate(calendar.getTimeZone(), date);
}
}
return returnValue;
}
public static Object formatDate(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
JSFunction result = new JSFunction("new Date");
result.addParameter(Integer.valueOf(calendar.get(Calendar.YEAR)));
result.addParameter(Integer.valueOf(calendar.get(Calendar.MONTH)));
result.addParameter(Integer.valueOf(calendar.get(Calendar.DATE)));
return result;
}
public static Object formatSelectedDate(TimeZone timeZone, Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(timeZone);
calendar.setTime(date);
JSFunction result = new JSFunction("new Date");
result.addParameter(Integer.valueOf(calendar.get(Calendar.YEAR)));
result.addParameter(Integer.valueOf(calendar.get(Calendar.MONTH)));
result.addParameter(Integer.valueOf(calendar.get(Calendar.DATE)));
result
.addParameter(Integer.valueOf(calendar
.get(Calendar.HOUR_OF_DAY)));
result.addParameter(Integer.valueOf(calendar.get(Calendar.MINUTE)));
result.addParameter(new Integer(0));
return result;
}
public Map<String, Object> getLabels(FacesContext context, UICalendar calendar) {
ResourceBundle bundle1 = null;
ResourceBundle bundle2 = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String messageBundle = context.getApplication().getMessageBundle();
Object locale = calendar.getLocale();
if (null != messageBundle) {
bundle1 = ResourceBundle.getBundle(messageBundle, calendar.getAsLocale(locale), loader);
}
try {
bundle2 = ResourceBundle.getBundle(CALENDAR_BUNDLE, calendar.getAsLocale(locale), loader);
} catch (MissingResourceException e) {
//No external bundle was found, ignore this exception.
}
Map<String, Object> labels = new HashMap<String, Object>();
if (null != bundle1 || null != bundle2) {
// TODO: make one function call
String[] names = {"apply", "today", "clean", "cancel", "ok", "close"};
RendererUtils utils= getUtils();
for (String name : names) {
String label = null;
String bundleKey = "RICH_CALENDAR_" + name.toUpperCase() + "_LABEL";
if (bundle1 != null) {
try {
label = bundle1.getString(bundleKey);
} catch (MissingResourceException mre) {
// Current key was not found, ignore this exception;
}
}
// Current key wasn't found in application bundle, use CALENDAR_BUNDLE,
// if it is not null
if((label == null) && (bundle2 != null)) {
try {
label = bundle2.getString(bundleKey);
} catch (MissingResourceException mre) {
// Current key was not found, ignore this exception;
}
}
utils.addToScriptHash(labels, name, label);
}
}
return labels;
}
}