org.projectforge.web.calendar.CalendarPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.projectforge.web.calendar.CalendarPanel.java

Source

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2013 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.calendar;

import java.sql.Timestamp;

import net.ftlines.wicket.fullcalendar.CalendarResponse;
import net.ftlines.wicket.fullcalendar.Event;
import net.ftlines.wicket.fullcalendar.EventProvider;
import net.ftlines.wicket.fullcalendar.EventSource;
import net.ftlines.wicket.fullcalendar.callback.CalendarDropMode;
import net.ftlines.wicket.fullcalendar.callback.ClickedEvent;
import net.ftlines.wicket.fullcalendar.callback.DroppedEvent;
import net.ftlines.wicket.fullcalendar.callback.ResizedEvent;
import net.ftlines.wicket.fullcalendar.callback.SelectedRange;
import net.ftlines.wicket.fullcalendar.callback.View;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.util.string.StringValue;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.projectforge.access.AccessChecker;
import org.projectforge.address.AddressDao;
import org.projectforge.common.DateHelper;
import org.projectforge.common.NumberHelper;
import org.projectforge.humanresources.HRPlanningDao;
import org.projectforge.timesheet.TimesheetDO;
import org.projectforge.timesheet.TimesheetDao;
import org.projectforge.user.PFUserContext;
import org.projectforge.user.PFUserDO;
import org.projectforge.user.ProjectForgeGroup;
import org.projectforge.web.address.AddressViewPage;
import org.projectforge.web.address.BirthdayEventsProvider;
import org.projectforge.web.humanresources.HRPlanningEventsProvider;
import org.projectforge.web.timesheet.TimesheetEditPage;
import org.projectforge.web.timesheet.TimesheetEventsProvider;
import org.projectforge.web.wicket.AbstractEditPage;
import org.projectforge.web.wicket.components.DatePickerUtils;
import org.projectforge.web.wicket.components.JodaDatePanel;

public class CalendarPanel extends Panel {
    private static final long serialVersionUID = -8491059902148238143L;

    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(CalendarPanel.class);

    @SpringBean(name = "accessChecker")
    private AccessChecker accessChecker;

    @SpringBean(name = "addressDao")
    private AddressDao addressDao;

    @SpringBean(name = "hrPlanningDao")
    private HRPlanningDao hrPlanningDao;

    @SpringBean(name = "timesheetDao")
    private TimesheetDao timesheetDao;

    private MyFullCalendar calendar;

    private BirthdayEventsProvider birthdayEventsProvider;

    private HolidayEventsProvider holidayEventsProvider;

    private HRPlanningEventsProvider hrPlanningEventsProvider;

    private TimesheetEventsProvider timesheetEventsProvider;

    protected ICalendarFilter filter;

    private boolean refresh;

    private final JodaDatePanel currentDatePanel;

    public CalendarPanel(final String id, final JodaDatePanel currentDatePanel) {
        super(id);
        this.currentDatePanel = currentDatePanel;
    }

    /**
     * At default the filter is equals to the customized filter. For the team plug-in the filter is a different filter.
     * @param filter
     * @param filter
     */
    @SuppressWarnings("serial")
    public void init(final ICalendarFilter filter) {
        this.filter = filter;
        final MyFullCalendarConfig config = new MyFullCalendarConfig(this);
        config.setSelectable(true);
        config.setSelectHelper(true);
        config.setLoading("function(bool) { if (bool) $(\"#loading\").show(); else $(\"#loading\").hide(); }");
        // config.setMinTime(new LocalTime(6, 30));
        // config.setMaxTime(new LocalTime(17, 30));
        config.setAllDaySlot(true);
        calendar = new MyFullCalendar("cal", config) {
            @Override
            protected void onDateRangeSelected(final SelectedRange range, final CalendarResponse response) {
                final String selectedCalendar = filter.getSelectedCalendar();
                if (selectedCalendar == null
                        || TimesheetEventsProvider.EVENT_CLASS_NAME.equals(selectedCalendar) == true) {
                    if (log.isDebugEnabled() == true) {
                        log.debug("Selected region: " + range.getStart() + " - " + range.getEnd() + " / allDay: "
                                + range.isAllDay());
                    }
                    if (accessChecker.isRestrictedUser() == true) {
                        return;
                    }
                    final TimesheetDO timesheet = new TimesheetDO();
                    if (range.isAllDay() == true) {
                        // Start with the first hour displayed (combo-box) or if any time sheets already exists for this date with a time sheet starts
                        // with the stop date of the last time sheet of the current day.
                        final TimesheetDO latest = timesheetEventsProvider.getLatestTimesheetOfDay(range.getEnd());
                        if (latest != null) {
                            timesheet.setStartDate(latest.getStopTime()).setStopTime(latest.getStopTime());
                        } else {
                            final Integer firstHourOfDay = filter.getFirstHour();
                            final DateTime start = range.getStart()
                                    .withHourOfDay(firstHourOfDay != null ? firstHourOfDay : 8);
                            final long millis = DateHelper.getDateTimeAsMillis(start);
                            timesheet.setStartDate(millis).setStopTime(millis);
                        }
                    } else {
                        timesheet.setStartDate(DateHelper.getDateTimeAsMillis(range.getStart()))//
                                .setStopTime(DateHelper.getDateTimeAsMillis(range.getEnd()));
                    }
                    if (filter.getTimesheetUserId() != null) {
                        timesheetDao.setUser(timesheet, filter.getTimesheetUserId());
                    }
                    final TimesheetEditPage timesheetEditPage = new TimesheetEditPage(timesheet);
                    timesheetEditPage.setReturnToPage((WebPage) getPage());
                    setResponsePage(timesheetEditPage);
                } else {
                    onDateRangeSelectedHook(filter.getSelectedCalendar(), range, response);
                }
            }

            /**
             * Event was moved, a new start time was chosen.
             * @see net.ftlines.wicket.fullcalendar.FullCalendar#onEventDropped(net.ftlines.wicket.fullcalendar.callback.DroppedEvent,
             *      net.ftlines.wicket.fullcalendar.CalendarResponse)
             */
            @Override
            protected boolean onEventDropped(final DroppedEvent event, final CalendarResponse response) {
                // default mode is move and edit
                CalendarDropMode dropMode = CalendarDropMode.MOVE_EDIT;
                final StringValue parameterValue = RequestCycle.get().getRequest().getQueryParameters()
                        .getParameterValue("which");
                if (parameterValue != null) {
                    try {
                        dropMode = CalendarDropMode.fromAjaxTarget(parameterValue.toString());
                    } catch (final Exception ex) {
                        log.warn(
                                "Unable to get calendar drop mode for given value, using default mode. Given mode: "
                                        + parameterValue.toString());
                    }
                }
                if (log.isDebugEnabled() == true) {
                    log.debug("Event drop. eventId: " + event.getEvent().getId() + " eventClass"
                            + event.getEvent().getClassName() + " sourceId: " + event.getSource().getUuid()
                            + " dayDelta: " + event.getDaysDelta() + " minuteDelta: " + event.getMinutesDelta()
                            + " allDay: " + event.isAllDay());
                    log.debug("Original start time: " + event.getEvent().getStart() + ", original end time: "
                            + event.getEvent().getEnd());
                    log.debug("New start time: " + event.getNewStartTime() + ", new end time: "
                            + event.getNewEndTime());
                }
                modifyEvent(event.getEvent(), event.getNewStartTime(), event.getNewEndTime(), dropMode, response);
                return false;
            }

            @Override
            protected boolean onEventResized(final ResizedEvent event, final CalendarResponse response) {
                if (log.isDebugEnabled() == true) {
                    log.debug("Event resized. eventId: " + event.getEvent().getId() + " eventClass"
                            + event.getEvent().getClassName() + " sourceId: " + event.getSource().getUuid()
                            + " dayDelta: " + event.getDaysDelta() + " minuteDelta: " + event.getMinutesDelta());
                }
                modifyEvent(event.getEvent(), null, event.getNewEndTime(), CalendarDropMode.MOVE_EDIT, response);
                return false;
            }

            @Override
            protected void onEventClicked(final ClickedEvent clickedEvent, final CalendarResponse response) {
                final Event event = clickedEvent.getEvent();
                final String eventId = event != null ? event.getId() : null;
                final String eventClassName = event != null ? event.getClassName() : null;
                if (log.isDebugEnabled() == true) {
                    log.debug("Event clicked. eventId: " + eventId + " eventClass" + event.getClassName()
                            + ", sourceId: " + clickedEvent.getSource().getUuid());
                }
                if (eventId != null && eventClassName != null) {
                    if (TimesheetEventsProvider.EVENT_CLASS_NAME.startsWith(eventClassName) == true) {
                        // User clicked on a time sheet, show the time sheet:
                        final Integer id = NumberHelper.parseInteger(eventId);
                        final PageParameters parameters = new PageParameters();
                        parameters.add(AbstractEditPage.PARAMETER_KEY_ID, id);
                        final TimesheetEditPage timesheetEditPage = new TimesheetEditPage(parameters);
                        timesheetEditPage.setReturnToPage((WebPage) getPage());
                        setResponsePage(timesheetEditPage);
                        return;
                    } else if (TimesheetEventsProvider.BREAK_EVENT_CLASS_NAME.startsWith(eventClassName) == true) {
                        // User clicked on a break (between time sheets), create new time sheet with times of the break:
                        final TimesheetDO breaksTimesheet = timesheetEventsProvider.getBreakTimesheet(eventId);
                        final TimesheetEditPage timesheetEditPage = new TimesheetEditPage(breaksTimesheet);
                        timesheetEditPage.setReturnToPage((WebPage) getPage());
                        setResponsePage(timesheetEditPage);
                        return;
                    } else if (BirthdayEventsProvider.EVENT_CLASS_NAME.startsWith(eventClassName) == true) {
                        // User clicked on birthday, show the address:
                        final Integer id = NumberHelper.parseInteger(eventId);
                        final PageParameters parameters = new PageParameters();
                        parameters.add(AbstractEditPage.PARAMETER_KEY_ID, id);
                        final AddressViewPage addressViewPage = new AddressViewPage(parameters);
                        setResponsePage(addressViewPage);
                        addressViewPage.setReturnToPage((WebPage) getPage());
                        return;
                    }
                    onEventClickedHook(clickedEvent, response, event, eventId, eventClassName);
                }
                response.refetchEvents();
            }

            @Override
            protected void onViewDisplayed(final View view, final CalendarResponse response) {
                if (log.isDebugEnabled() == true) {
                    log.debug("View displayed. viewType: " + view.getType().name() + ", start: " + view.getStart()
                            + ", end: " + view.getEnd());
                }
                response.refetchEvents();
                setStartDate(view.getStart());
                filter.setViewType(view.getType());
                // Need calling getEvents for getting correct duration label, it's not predictable what will be called first: onViewDisplayed or
                // getEvents.
                timesheetEventsProvider.getEvents(view.getVisibleStart().toDateTime(),
                        view.getVisibleEnd().toDateTime());
                onCallGetEventsHook(view, response);
                if (currentDatePanel != null) {
                    currentDatePanel.getDateField().modelChanged();
                    response.getTarget().add(currentDatePanel.getDateField());
                    response.getTarget().appendJavaScript(DatePickerUtils
                            .getDatePickerInitJavaScript(currentDatePanel.getDateField().getMarkupId(), true));
                }
                // Set interval on refresh the timeline.
                response.getTarget().appendJavaScript(
                        "if(first){ first = false; window.setInterval(setTimeline, 60000); } try { setTimeline(); } catch(err) { }");
                response.getTarget().add(((CalendarPage) getPage()).getForm().getDurationLabel());
            }
        };
        calendar.setMarkupId("calendar");
        add(calendar);
        setConfig();

        // Time sheets
        EventSource eventSource = new EventSource();
        timesheetEventsProvider = new TimesheetEventsProvider(this, timesheetDao, filter);
        eventSource.setEventsProvider(timesheetEventsProvider);
        eventSource.setEditable(true);
        config.add(eventSource);
        // Holidays:
        eventSource = new EventSource();
        holidayEventsProvider = new HolidayEventsProvider(this);
        eventSource.setEventsProvider(holidayEventsProvider);
        eventSource.setEditable(false);
        config.add(eventSource);
        // HR planning:
        eventSource = new EventSource();
        hrPlanningEventsProvider = new HRPlanningEventsProvider(this, filter, hrPlanningDao);
        eventSource.setEventsProvider(hrPlanningEventsProvider);
        eventSource.setEditable(false);
        eventSource.setBackgroundColor("#0080FF");
        eventSource.setColor("#0080FF");
        config.add(eventSource);
        // Birthdays:
        eventSource = new EventSource();
        birthdayEventsProvider = new BirthdayEventsProvider(this, filter, addressDao,
                accessChecker.isLoggedInUserMemberOfGroup(ProjectForgeGroup.FINANCE_GROUP) == false);
        eventSource.setEventsProvider(birthdayEventsProvider);
        eventSource.setEditable(false);
        // The default color of birthdays (not favorites), should be gray, see BirthdayEventsProvider for colors of birthdays of favorites.
        eventSource.setBackgroundColor("#EEEEEE");
        eventSource.setColor("#EEEEEE");
        eventSource.setTextColor("#222222");
        config.add(eventSource);
        onRegisterEventSourceHook(config, filter);
    }

    /**
     * Hook method for overwriting children, which is called, when an date range event occurs which can not be handled through this page.
     * 
     * @param selectedCalendar
     * @param range
     * @param response
     */
    protected void onDateRangeSelectedHook(final String selectedCalendar, final SelectedRange range,
            final CalendarResponse response) {
        // by default nothing happens here
    }

    /**
     * Hook method for overwriting children, which is called, when an event source should be registered
     * 
     * @param config
     * @param filter
     */
    protected void onRegisterEventSourceHook(final MyFullCalendarConfig config, final ICalendarFilter filter) {
        // by default nothing happens here
    }

    /**
     * Hook method for overwriting children, which is called, when an event should be modifies which could not be handled through this page.<br/>
     * This could occur, when {@link Event} belongs to an {@link EventProvider} of a child implementation.
     * 
     * @param event
     * @param newStartTime
     * @param newEndTime
     * @param dropMode
     * @param response
     */
    protected void onModifyEventHook(final Event event, final DateTime newStartTime, final DateTime newEndTime,
            final CalendarDropMode dropMode, final CalendarResponse response) {
        // by default nothing happens here
    }

    /**
     * Hook method for overwriting children, which is called, when the {@link EventProvider} should be refreshed.<br/>
     * Please call forceReload on your provider.
     */
    protected void onRefreshEventProvider() {
        // by default nothing happens here
    }

    /**
     * Hook method for overwriting children, which is called, when the pre initialization of the calendars are made.<br/>
     * Please call getEvents on you {@link EventProvider}.
     * @param response
     * @param view
     * 
     */
    protected void onCallGetEventsHook(final View view, final CalendarResponse response) {
        // by default nothing happens here
    }

    /**
     * Hook method for overwriting children, which is called, when something in the calendar was clicked
     * 
     * @param clickedEvent
     * @param response
     * @param event
     * @param eventId
     * @param eventClassName
     */
    protected void onEventClickedHook(final ClickedEvent clickedEvent, final CalendarResponse response,
            final Event event, final String eventId, final String eventClassName) {
        // by default nothing happens here
    }

    private void modifyEvent(final Event event, final DateTime newStartTime, final DateTime newEndTime,
            final CalendarDropMode dropMode, final CalendarResponse response) {
        final String eventId = event != null ? event.getId() : null;
        final String eventClassName = event != null ? event.getClassName() : null;
        if (eventId != null && TimesheetEventsProvider.EVENT_CLASS_NAME.equals(eventClassName) == true) {
            // User clicked on a time sheet, show the time sheet:
            final Integer id = NumberHelper.parseInteger(eventId);
            final TimesheetDO dbTimesheet = timesheetDao.internalGetById(id);
            if (dbTimesheet == null) {
                return;
            }
            final TimesheetDO timesheet = new TimesheetDO();
            timesheet.copyValuesFrom(dbTimesheet);
            final Long newStartTimeMillis = newStartTime != null ? DateHelper.getDateTimeAsMillis(newStartTime)
                    : null;
            final Long newEndTimeMillis = newEndTime != null ? DateHelper.getDateTimeAsMillis(newEndTime) : null;
            if (newStartTimeMillis != null) {
                timesheet.setStartDate(newStartTimeMillis);
            }
            if (newEndTimeMillis != null) {
                timesheet.setStopTime(new Timestamp(newEndTimeMillis));
            }
            final PFUserDO loggedInUser = PFUserContext.getUser();
            if (CalendarDropMode.MOVE_SAVE.equals(dropMode) == true
                    || CalendarDropMode.MOVE_EDIT.equals(dropMode) == true) {
                if (timesheetDao.hasUpdateAccess(loggedInUser, timesheet, dbTimesheet, false) == false) {
                    // User has no update access, therefore ignore this request...
                    return;
                }
                if (CalendarDropMode.MOVE_SAVE.equals(dropMode) == true) {
                    timesheetDao.update(timesheet);
                    setResponsePage(getPage());
                } else {
                    setResponsePage(new TimesheetEditPage(timesheet).setReturnToPage((WebPage) getPage()));
                }
                return;
            }
            // Copy this time sheet:
            timesheet.setId(null);
            timesheet.setDeleted(false);
            timesheetDao.setUser(timesheet, loggedInUser.getId()); // Copy for own user.
            if (CalendarDropMode.COPY_SAVE.equals(dropMode) == true) {
                if (timesheetDao.hasInsertAccess(loggedInUser, timesheet, false) == false) {
                    // User has no insert access, therefore ignore this request...
                    return;
                }
                timesheetDao.save(timesheet);
                setResponsePage(getPage());
                return;
            } else if (CalendarDropMode.COPY_EDIT.equals(dropMode) == true) {
                setResponsePage(new TimesheetEditPage(timesheet).setReturnToPage((WebPage) getPage()));
            } else {
                // CANCEL -> should be handled through javascript now
                setResponsePage(getPage());
            }
        } else {
            onModifyEventHook(event, newStartTime, newEndTime, dropMode, response);
        }
    }

    /**
     * @see org.projectforge.web.wicket.AbstractSecuredPage#onBeforeRender()
     */
    @Override
    protected void onBeforeRender() {
        super.onBeforeRender();
        // Restore current date (e. g. on reload or on coming back from callee page).
        final MyFullCalendarConfig config = calendar.getConfig();
        final DateMidnight startDate = filter.getStartDate();
        if (startDate != null) {
            config.setYear(startDate.getYear());
            config.setMonth(startDate.getMonthOfYear() - 1);
            config.setDate(startDate.getDayOfMonth());
        }
        config.setDefaultView(filter.getViewType().getCode());
        if (refresh == true) {
            refresh = false;
            timesheetEventsProvider.forceReload();
            birthdayEventsProvider.forceReload();
            hrPlanningEventsProvider.forceReload();
            setConfig();
            onRefreshEventProvider();
        }
    }

    private void setConfig() {
        final MyFullCalendarConfig config = calendar.getConfig();
        if (filter.isSlot30() == true) {
            config.setSlotMinutes(30);
        } else {
            config.setSlotMinutes(15);
        }
        if (filter.getFirstHour() != null) {
            config.setFirstHour(filter.getFirstHour());
        }
    }

    /**
     * @param startDate the startDate to set
     * @return this for chaining.
     */
    public CalendarPanel setStartDate(final DateMidnight startDate) {
        filter.setStartDate(startDate);
        return this;
    }

    /**
     * @return the startDate
     */
    public DateMidnight getStartDate() {
        return filter.getStartDate();
    }

    /**
     * Forces to reloaded time sheets in onBeforeRender().
     * @param refresh the refresh to set
     * @return this for chaining.
     */
    public CalendarPanel forceReload() {
        this.refresh = true;
        return this;
    }

    public String getTotalTimesheetDuration() {
        return timesheetEventsProvider.formatDuration(timesheetEventsProvider.getTotalDuration());
    }
}