package de.flower.rmt.ui.markup.html.calendar;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import de.flower.common.ui.panel.BasePanel;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.string.StringValue;
import org.joda.time.DateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Calendar widget based on wonderful http://arshaw.com/fullcalendar/.
*
* @author flowerrrr
*/
public abstract class FullCalendarPanel extends BasePanel {
// TODO (flowerrrr - 23.06.12) let wicket inject url
private final static String fullCalendarCssUrl = "static/js/fullcalendar-1.5.3/fullcalendar.css";
private final static String fullCalendarJsUrl = "static/js/fullcalendar-1.5.3/fullcalendar.min.js";
private AbstractAjaxBehavior jsonEventSourceBehavior;
private EventClickCallbackBehavior eventClickCallbackBehavior;
private SelectCallbackBehavior selectCallbackBehavior;
/**
* tracks current date of calendar so that ajax-refresh of this panel stays on previous selected date.
*/
private DateTime currentDate;
public FullCalendarPanel() {
add(jsonEventSourceBehavior = new JSONEventSourceBehavior() {
@Override
protected List<CalEvent> loadCalEvents(final DateTime start, final DateTime end) {
currentDate = getCurrentDate(start, end);
return FullCalendarPanel.this.loadCalEvents(start, end);
}
});
add(eventClickCallbackBehavior = new EventClickCallbackBehavior() {
@Override
protected void onEdit(final AjaxRequestTarget target, final CalEvent calEvent) {
FullCalendarPanel.this.onEventClick(target, calEvent);
}
});
add(selectCallbackBehavior = new SelectCallbackBehavior() {
@Override
protected void onEdit(final AjaxRequestTarget target, final CalEvent calEvent) {
FullCalendarPanel.this.onEventClick(target, calEvent);
}
});
}
@Override
protected String getPanelMarkup() {
return "";
}
/**
* Need to save currently displayed month.
* start could be before current month, and end most likely is currentMonth + 1.
* so, find date in the middle and use its month.
*/
private DateTime getCurrentDate(DateTime start, DateTime end) {
return new DateTime((start.getMillis() + end.getMillis()) / 2);
}
@Override
public void renderHead(final IHeaderResponse response) {
response.renderCSSReference(fullCalendarCssUrl);
response.renderJavaScriptReference(fullCalendarJsUrl);
super.renderHead(response);
String initScript = "$('#" + getMarkupId() + "').fullCalendar(" + getJsonOptions() + ");";
response.renderOnDomReadyJavaScript(initScript);
}
private String getJsonOptions() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String options = gson.toJson(getOptions());
// replace placeholder with function literal (gson does not allow serializing function literals without quotes)
options = options.replace("\"_eventClick_\"", eventClickCallbackBehavior.getCallbackFunction());
options = options.replace("\"_select_\"", selectCallbackBehavior.getCallbackFunction());
String errorFunction = "function(jqXHR, textStatus, errorThrown) { ";
for (Entry<Integer, String> entry : getErrorRedirectMap().entrySet()) {
Integer status = entry.getKey();
String url = entry.getValue();
errorFunction += "if (jqXHR.status == " + status + ") window.location.href = '" + url + "'; ";
}
errorFunction += "}";
options = options.replace("\"_error_\"", errorFunction);
return options;
}
/**
* Return map of http status code and the page url to be redirected to when an ajax-error occurs.
* @return
*/
protected Map<Integer, String> getErrorRedirectMap() {
return Maps.newHashMap();
}
private Map<String, Object> getOptions() {
Map<String, Object> options = new HashMap<>();
Map<String, Object> eventSource = new HashMap<>();
eventSource.put("url", jsonEventSourceBehavior.getCallbackUrl().toString());
eventSource.put("error", "_error_");
options.put("eventSources", Lists.newArrayList(eventSource));
options.put("eventClick", "_eventClick_"); // will be substituted after converting to json
options.put("selectable", true);
options.put("select", "_select_");
options.put("firstDay", 1); // start week on monday
options.put("weekMode", "liquid");
options.put("timeFormat", "H:mm");
options.put("monthNames", StringUtils.split(getResourceString("monthNames"), ","));
options.put("monthNamesShort", StringUtils.split(getResourceString("monthNamesShort"), ","));
options.put("dayNames", StringUtils.split(getResourceString("dayNames"), ","));
options.put("dayNamesShort", StringUtils.split(getResourceString("dayNamesShort"), ","));
if (currentDate != null) {
options.put("year", currentDate.getYear());
options.put("month", currentDate.getMonthOfYear() - 1);
}
Map<String, Object> buttonText = new HashMap<>();
buttonText.put("today", getResourceString("today"));
options.put("buttonText", buttonText);
return options;
}
private String getResourceString(String property) {
return new ResourceModel(property).wrapOnAssignment(this).getObject();
}
protected abstract List<CalEvent> loadCalEvents(final DateTime start, final DateTime end);
protected abstract void onEventClick(AjaxRequestTarget target, CalEvent calEvent);
/**
* Providing json feed of calendar events.
*/
public abstract static class JSONEventSourceBehavior extends AbstractAjaxBehavior {
@Override
public void onRequest() {
RequestCycle requestCycle = RequestCycle.get();
StringValue start = requestCycle.getRequest().getRequestParameters().getParameterValue("start");
StringValue end = requestCycle.getRequest().getRequestParameters().getParameterValue("end");
List<CalEvent> events = loadCalEvents(new DateTime(start.toLong() * 1000), new DateTime(end.toLong() * 1000));
Gson gson = new Gson();
String json = gson.toJson(events);
requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "UTF-8", json));
}
protected abstract List<CalEvent> loadCalEvents(final DateTime start, final DateTime end);
}
}