/**
* 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.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.Iterator;
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.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
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.event.CurrentDateChangeEvent;
/**
* @author Nick Belaevski - mailto:nbelaevski@exadel.com created 08.06.2007
*
*/
public class CalendarRendererBase extends TemplateEncoderRendererBase {
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";
/**
* 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";
protected static final String MARKUP_SUFFIX = "Markup";
public static final String CALENDAR_BUNDLE = "org.richfaces.renderkit.calendar";
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 calendar - UICalendar
*
* @return hours and minutes from "defaultTime" attribute
*/
public String getPreparedDefaultTime(UICalendar calendar) {
return calendar.getPreparedDefaultTime();
}
/**
* 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
* @param calendar - calendar component
*
* @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;
}
protected void doDecode(FacesContext context, UIComponent component) {
// TODO Auto-generated method stub
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);
}
}
public void encodeChildren(FacesContext context, UIComponent calendar)
throws IOException {
}
public void writeClass(FacesContext context, UIComponent component)
throws IOException {
UICalendar calendar = (UICalendar) component;
String styleClass = (String) calendar.getAttributes().get("styleClass");
if (styleClass != null && styleClass.length() != 0) {
ResponseWriter writer = context.getResponseWriter();
writer.writeText(",\n className: '" + styleClass + "'", null);
}
}
public void writeDayStyleClass(FacesContext context, UIComponent component)
throws IOException {
UICalendar calendar = (UICalendar) component;
String dayStyleClass = (String) calendar.getAttributes().get(
"dayStyleClass");
if (dayStyleClass != null && dayStyleClass.length() != 0) {
ResponseWriter writer = context.getResponseWriter();
writer.writeText(",\n dayStyleClass: " + dayStyleClass, null);
}
}
public void writeIsDayEnabled(FacesContext context, UIComponent component)
throws IOException {
UICalendar calendar = (UICalendar) component;
String isDayEnabled = (String) calendar.getAttributes().get(
"isDayEnabled");
if (isDayEnabled != null && isDayEnabled.length() != 0) {
ResponseWriter writer = context.getResponseWriter();
writer.writeText(",\n isDayEnabled: " + isDayEnabled, null);
}
}
public void writeMarkupScriptBody(FacesContext context,
UIComponent component, boolean children) throws IOException {
writeScriptBody(context, component, children);
}
public void writeOptionalFacetMarkupScriptBody(FacesContext context,
UIComponent component, String facetName) throws IOException {
UIComponent facet = component.getFacet(facetName);
if (facet != null && facet.isRendered()) {
ResponseWriter writer = context.getResponseWriter();
writer.writeText(",\n " + facetName + MARKUP_SUFFIX + ": ", null);
writeMarkupScriptBody(context, facet, false);
}
}
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 void writeDayCellClass(FacesContext context, UIComponent component)
throws IOException {
String cellwidth = (String) component.getAttributes().get("cellWidth");
String cellheight = (String) component.getAttributes()
.get("cellHeight");
ResponseWriter writer = context.getResponseWriter();
if (cellwidth != null && cellwidth.length() != 0 || cellheight != null
&& cellheight.length() != 0) {
String clientId = component.getClientId(context);
writer.writeText(",\n dayCellClass: '" + clientId.replace(':', '_')
+ "DayCell'", null);
}
}
public void writeFacetMarkupScriptBody(FacesContext context,
UIComponent component, String facetName) throws IOException {
UIComponent facet = component.getFacet(facetName);
if (facet != null && facet.isRendered()) {
ResponseWriter writer = context.getResponseWriter();
writer.writeText(",\n " + facetName + MARKUP_SUFFIX + ": ", null);
writeMarkupScriptBody(context, facet, false);
}
}
public void writePreloadBody(FacesContext context, UICalendar calendar)
throws IOException {
Object preload = calendar.getPreload();
if (preload != null) {
ResponseWriter writer = context.getResponseWriter();
writer.write(ScriptUtils.toScript(preload));
}
}
public void writeSubmitFunction(FacesContext context, UICalendar calendar)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
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);
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);
writer.write(definition.toScript());
}
public void writeEventHandlerFunction(FacesContext context,
UIComponent component, String eventName) throws IOException {
ResponseWriter writer = context.getResponseWriter();
Object script = component.getAttributes().get(eventName);
if (script != null && !script.equals("")) {
JSFunctionDefinition onEventDefinition = new JSFunctionDefinition();
onEventDefinition.addParameter("event");
onEventDefinition.addToBody(script);
writer.writeText(",\n" + eventName + ": "
+ onEventDefinition.toScript(), null);
}
}
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 writeSymbols(FacesContext facesContext, UICalendar calendar)
throws IOException {
ResponseWriter writer = facesContext.getResponseWriter();
Map<String, String[]> symbolsMap = getSymbolsMap(facesContext, calendar);
Iterator<Map.Entry<String, String[]>> entryIterator = symbolsMap.entrySet().iterator();
writer.writeText(", \n", null);
while (entryIterator.hasNext()) {
Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) entryIterator.next();
writer.writeText(ScriptUtils.toScript(entry.getKey()), null);
writer.writeText(": ", null);
writer.writeText(ScriptUtils.toScript(entry.getValue()), null);
if (entryIterator.hasNext()) {
writer.writeText(", \n", 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, String[]> getSymbolsMap(FacesContext facesContext, UICalendar calendar) {
Map<String, String[]> map = new HashMap<String, String[]>();
Locale locale = calendar.getAsLocale(calendar.getLocale());
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 = ComponentUtil.asArray(calendar
.getWeekDayLabels());
if (weekDayLabels == null) {
weekDayLabels = symbols.getWeekdays();
weekDayLabels = shiftDates(minimum, maximum, weekDayLabels);
}
String[] weekDayLabelsShort = ComponentUtil.asArray(calendar
.getWeekDayLabelsShort());
if (weekDayLabelsShort == null) {
weekDayLabelsShort = symbols.getShortWeekdays();
weekDayLabelsShort = shiftDates(minimum, maximum,
weekDayLabelsShort);
}
String[] monthLabels = ComponentUtil.asArray(calendar.getMonthLabels());
if (monthLabels == null) {
monthLabels = symbols.getMonths();
monthLabels = shiftDates(monthMin, monthMax, monthLabels);
}
String[] monthLabelsShort = ComponentUtil.asArray(calendar
.getMonthLabelsShort());
if (monthLabelsShort == null) {
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);
return map;
}
public String getFirstWeekDay(FacesContext context, UICalendar calendar)
throws IOException {
return String.valueOf(calendar.getFirstWeekDay());
}
public String getMinDaysInFirstWeek(FacesContext context,
UICalendar calendar) throws IOException {
return String.valueOf(calendar.getMinDaysInFirstWeek());
}
public String getCurrentDateAsString(FacesContext context,
UICalendar calendar, Date date) throws IOException {
Format formatter = new SimpleDateFormat("MM/yyyy");
return formatter.format(date);
}
public String getCurrentDate(FacesContext context, UICalendar calendar,
Date date) throws IOException {
return ScriptUtils.toScript(formatDate(date));
}
public String 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 ScriptUtils.toScript(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;
}
/**
* Write labels used in the Calendar component, taken from message bundles.
* Try to use bundle1 at first. If the 1st bundle is null or it doesn't
* contain requested message key, use the bundle2.
* @param bundle1 - 1st bundle to be used as a source for messages
* @param bundle2 - 2nd bundle to be used as a source for messages
* @param name - name of the requested label
* @param writer - response writer
* @throws IOException
*/
public void writeStringsFromBundle(ResourceBundle bundle1, ResourceBundle bundle2, String name,
ResponseWriter writer) throws IOException {
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;
}
}
writeStringFoundInBundle(name, label, writer);
}
public void writeStringFoundInBundle(String name, String value, ResponseWriter writer) throws IOException {
if(null!=value){
if (!("close").equals(name.toLowerCase())) {
writer.writeText(name.toLowerCase() + ":'" + value + "', ",null);
} else {
writer.writeText("close:'" + value + "'", null);
}
}else{
if (!("close").equals(name.toLowerCase())) {
writer.writeText(name.toLowerCase() + ":'" + name + "', ",null);
} else {
writer.writeText("close:'x'", null);
}
}
}
public void writeLabels(FacesContext context, UICalendar calendar)
throws IOException {
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.
}
ResponseWriter writer = context.getResponseWriter();
writer.writeText(",\n labels:{", null);
if (null != bundle1 || null != bundle2) {
writeStringsFromBundle(bundle1, bundle2, "Apply", writer);
writeStringsFromBundle(bundle1, bundle2, "Today", writer);
writeStringsFromBundle(bundle1, bundle2, "Clean", writer);
writeStringsFromBundle(bundle1, bundle2, "Cancel", writer);
writeStringsFromBundle(bundle1, bundle2, "OK", writer);
writeStringsFromBundle(bundle1, bundle2, "Close", writer);
}else{
// No bundles were found, use default labels.
writer.writeText("apply:'Apply', today:'Today', clean:'Clean', ok:'OK', cancel:'Cancel', close:'x'", null);
}
writer.writeText("}", null);
}
}