/**
* Copyright (c) 2014 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.frontend.taglibs;
import com.redhat.rhn.common.util.DatePicker;
import com.redhat.rhn.frontend.html.HtmlTag;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* <rhn:datepicker data="${picker}"/>
*
* Where picker is a com.redhat.rhn.common.util.DatePicker
*
* The date-picker.jsp fragment is kept for backwards compatibility
*
* It generates backward compatibility input tags with date_hour,
* date_minutes, date_am_pm...,
* using Javascript to be backwards compatible with the old tag,
* so it should work in all pages.
*
* The date is displayed in a localized format when the calendar is not open.
* The calendar is localized using the month names from the DatePicker class
* and related classes.
*
*/
public class DateTimePickerTag extends TagSupport {
private static final String JS_INCLUDE_GUARD_ATTR = "__spacewalk_datepicker_included";
private DatePicker data;
/**
* @return the date picker object for this tag
* @see com.redhat.rhn.common.util.DatePicker
*/
public DatePicker getData() {
return data;
}
/**
* Sets the date picker for this tag
* @param pData the date picker object
*/
public void setData(DatePicker pData) {
this.data = pData;
}
/**
* {@inheritDoc}
*/
@Override
public void release() {
this.data = null;
super.release();
}
/**
* {@inheritDoc}
*/
@Override
public int doEndTag() throws JspException {
try {
writePickerHtml(pageContext.getOut());
writePickerJavascript(pageContext.getOut());
}
catch (IOException e) {
throw new JspException(e);
}
return super.doEndTag();
}
/**
* {@inheritDoc}
*/
@Override
public int doStartTag() throws JspException {
return super.doStartTag();
}
private HtmlTag createInputAddonTag(String type, String icon) {
HtmlTag dateAddon = new HtmlTag("span");
dateAddon.setAttribute("class", "input-group-addon text");
dateAddon.setAttribute("id", data.getName() + "_" +
type + "picker_widget_input_addon");
dateAddon.setAttribute("data-picker-name", data.getName());
dateAddon.setAttribute("data-picker-type", type);
IconTag dateAddonIcon = new IconTag(icon);
dateAddon.addBody(" ");
dateAddon.addBody(dateAddonIcon.render());
return dateAddon;
}
/**
* The date picker uses a strange date format.
* The is a bug open about that:
* https://github.com/eternicode/bootstrap-datepicker/issues/182
*
* @param format a standard format like the one described in
* http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
* @return a format like the one described in
* http://bootstrap-datepicker.readthedocs.org/en/latest/options.html
*/
private String toDatepickerFormat(String format) {
return format
.replaceAll("(^|[^M])MM([^M]|$)", "$1mm$2")
.replaceAll("(^|[^M])M([^M]|$)", "$1m$2")
.replaceAll("MMMM+", "MM")
.replaceAll("MMM", "M")
.replaceAll("DD+", "dd")
.replaceAll("D", "d")
.replaceAll("EEEE+", "DD")
.replaceAll("E+", "D")
.replaceAll("(^|[^y])y{1,3}([^y]|$)", "$1yy$2")
.replaceAll("yyyy+", "yyyy");
}
/**
* The time picker uses the PHP time format
*
* @param format a standard format like the one described in
* http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
* @return a format like the one described in
* http://php.net/manual/en/function.date.php
*
*/
private String toPhpTimeFormat(String format) {
return format
.replaceAll("a+", "a")
.replaceAll("(^|[^H])H([^H]|$)", "$1G$2")
.replaceAll("HH+", "H")
.replaceAll("(^|[^h])h([^h]|$)", "$1g$2")
.replaceAll("hh+", "h")
// k (1-24) not supported, convert to the 0-23 format
.replaceAll("kk+", "H")
.replaceAll("k", "G")
// K (0-11) not supported, convert to the 1-12 format
.replaceAll("KK+", "h")
.replaceAll("K", "g")
.replaceAll("m+", "i")
.replaceAll("s+", "s")
// ignore others
.replaceAll("z+", "")
.replaceAll("Z+", "")
.replaceAll("X+", "");
}
/**
* Convert day java.util.Calendar constants
* to an index usable by the javascript picker.
*
* @return the equivalent index for the javascript picker
*/
private String getJavascriptPickerDayIndex(int calIndex) {
return String.valueOf(calIndex - 1);
}
private void writePickerHtml(Writer out) throws IOException {
HtmlTag group = new HtmlTag("div");
group.setAttribute("class", "input-group");
group.setAttribute("id", data.getName() + "_datepicker_widget");
if (!data.getDisableDate()) {
HtmlTag dateAddon = createInputAddonTag("date", "header-calendar");
group.addBody(dateAddon);
SimpleDateFormat dateFmt = (SimpleDateFormat)
DateFormat.getDateInstance(DateFormat.SHORT, data.getLocale());
HtmlTag dateInput = new HtmlTag("input");
dateInput.setAttribute("id", data.getName() + "_datepicker_widget_input");
dateInput.setAttribute("data-provide", "date-picker");
dateInput.setAttribute("data-date-today-highlight", "true");
dateInput.setAttribute("data-date-orientation", "top auto");
dateInput.setAttribute("data-date-autoclose", "true");
dateInput.setAttribute("data-date-language", data.getLocale().toString());
dateInput.setAttribute("data-date-format",
toDatepickerFormat(dateFmt.toPattern()));
dateInput.setAttribute("type", "text");
dateInput.setAttribute("class", "form-control");
dateInput.setAttribute("id", data.getName() + "_datepicker_widget_input");
dateInput.setAttribute("size", "15");
dateInput.setAttribute("data-picker-name", data.getName());
dateInput.setAttribute("data-initial-year", String.valueOf(data.getYear()));
dateInput.setAttribute("data-initial-month", String.valueOf(data.getMonth()));
dateInput.setAttribute("data-initial-day", String.valueOf(data.getDay()));
String firstDay = getJavascriptPickerDayIndex(
data.getCalendar().getFirstDayOfWeek());
dateInput.setAttribute("data-date-week-start", firstDay);
group.addBody(dateInput);
}
if (!data.getDisableTime()) {
HtmlTag timeAddon = createInputAddonTag("time", "header-clock");
group.addBody(timeAddon);
SimpleDateFormat timeFmt = (SimpleDateFormat)
DateFormat.getTimeInstance(DateFormat.SHORT, data.getLocale());
HtmlTag timeInput = new HtmlTag("input");
timeInput.setAttribute("type", "text");
timeInput.setAttribute("data-provide", "time-picker");
timeInput.setAttribute("class", "form-control");
timeInput.setAttribute("data-time-format",
toPhpTimeFormat(timeFmt.toPattern()));
timeInput.setAttribute("id", data.getName() + "_timepicker_widget_input");
timeInput.setAttribute("size", "10");
timeInput.setAttribute("data-picker-name", data.getName());
timeInput.setAttribute("data-initial-hour",
String.valueOf(data.getHourOfDay()));
timeInput.setAttribute("data-initial-minute", String.valueOf(data.getMinute()));
group.addBody(timeInput);
}
HtmlTag tzAddon = new HtmlTag("span");
tzAddon.setAttribute("id", data.getName() + "_tz_input_addon");
tzAddon.setAttribute("data-picker-name", data.getName());
tzAddon.setAttribute("class", "input-group-addon text tz_input_addon");
DateFormat tzFmt = new SimpleDateFormat("z", data.getLocale());
tzFmt.setTimeZone(data.getCalendar().getTimeZone());
tzAddon.addBody(tzFmt.format(data.getDate()));
group.addBody(tzAddon);
out.append(group.render());
// compatibility with the old struts form
// these values are updated when the picker changes using javascript
//
// if you are tempted to not write out these fields in case
// date or time are disabled for the picker, mind that
// DatePicker::readMap resets the date to now() if not all fields
// are present.
out.append(createHiddenInput("day", String.valueOf(data.getDay())).render());
out.append(createHiddenInput("month", String.valueOf(data.getMonth())).render());
out.append(createHiddenInput("year", String.valueOf(data.getYear())).render());
out.append(createHiddenInput("hour", String.valueOf(data.getHour())).render());
out.append(createHiddenInput("minute", String.valueOf(data.getMinute())).render());
if (data.isLatin()) {
out.append(createHiddenInput("am_pm",
String.valueOf((data.getHour() > 12) ? 1 : 0)).render());
}
}
private HtmlTag createHiddenInput(String type, String value) {
HtmlTag input = new HtmlTag("input");
input.setAttribute("id", data.getName() + "_" + type);
input.setAttribute("name", data.getName() + "_" + type);
input.setAttribute("type", "hidden");
input.setAttribute("value", value);
return input;
}
private void writePickerJavascript(Writer out) throws IOException {
if (pageContext.getRequest().getAttribute(JS_INCLUDE_GUARD_ATTR) == null) {
writeJavascriptIncludes(out);
out.append("<script type='text/javascript'>\n");
writeI18NMap(out);
pageContext.getRequest().setAttribute(JS_INCLUDE_GUARD_ATTR, true);
out.append("</script>\n");
}
}
private void writeJavascriptIncludes(Writer out) throws IOException {
out.append("<script type='text/javascript' " +
"src='/javascript/spacewalk-datetimepicker.js'></script>\n");
}
private void writeI18NMap(Writer out) throws IOException {
// generate i18n for the picker here
DateFormatSymbols syms = data.getDateFormatSymbols();
out.append("$.fn.datepicker.dates['" + data.getLocale() + "'] = {\n");
Writer names = new StringWriter();
Writer shortNames = new StringWriter();
String[] nameStrings = syms.getWeekdays();
String[] shortNameStrings = syms.getShortWeekdays();
for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
names.append(String.format(" '%s',", nameStrings[i]));
shortNames.append(String.format(" '%s',", shortNameStrings[i]));
}
out.append("days: [" + names.toString() + "],\n");
out.append("daysShort: [" + shortNames.toString() + "],\n");
out.append("daysMin: [" + shortNames.toString() + "],\n");
names = new StringWriter();
shortNames = new StringWriter();
nameStrings = syms.getMonths();
shortNameStrings = syms.getShortMonths();
for (int i = Calendar.JANUARY; i <= Calendar.DECEMBER; i++) {
names.append(String.format(" '%s',", nameStrings[i]));
shortNames.append(String.format(" '%s',", shortNameStrings[i]));
}
out.append("months: [" + names.toString() + "],\n");
out.append("monthsShort: [" + shortNames.toString() + "],\n");
out.append("};\n");
}
}