]> source.dussan.org Git - vaadin-framework.git/commitdiff
Provide configuration for events order in month and week views
authorDenis <denis@vaadin.com>
Wed, 14 Dec 2016 11:50:06 +0000 (13:50 +0200)
committerIlia Motornyi <elmot@vaadin.com>
Wed, 14 Dec 2016 11:50:06 +0000 (13:50 +0200)
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VCalendar.java
compatibility-client/src/main/java/com/vaadin/v7/client/ui/calendar/CalendarConnector.java
compatibility-client/src/main/java/com/vaadin/v7/client/ui/calendar/schedule/DateCell.java
compatibility-server/src/main/java/com/vaadin/v7/ui/Calendar.java
compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/calendar/CalendarState.java
uitest/src/main/java/com/vaadin/v7/tests/components/calendar/CalendarEventsSort.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/v7/tests/components/calendar/CalendarEventsSortTest.java [new file with mode: 0644]

index 9130a3faec8b39b642879c4f001d080036173937..57bde573c683da48a5469df750e22ec1ac2f4037 100644 (file)
@@ -41,6 +41,7 @@ import com.vaadin.v7.client.ui.calendar.schedule.SimpleWeekToolbar;
 import com.vaadin.v7.client.ui.calendar.schedule.WeekGrid;
 import com.vaadin.v7.client.ui.calendar.schedule.WeeklyLongEvents;
 import com.vaadin.v7.client.ui.calendar.schedule.dd.CalendarDropHandler;
+import com.vaadin.v7.shared.ui.calendar.CalendarState.EventSortOrder;
 import com.vaadin.v7.shared.ui.calendar.DateConstants;
 
 /**
@@ -98,6 +99,11 @@ public class VCalendar extends Composite implements VHasDropHandler {
     private int firstHour;
     private int lastHour;
 
+    private EventSortOrder eventSortOrder = EventSortOrder.DURATION_DESC;
+
+    private static EventDurationComparator DEFAULT_COMPARATOR = new EventDurationComparator(
+            false);
+
     private CalendarDropHandler dropHandler;
 
     /**
@@ -241,6 +247,106 @@ public class VCalendar extends Composite implements VHasDropHandler {
         void contextMenu(ContextMenuEvent event, Widget widget);
     }
 
+    private static abstract class AbstractEventComparator
+            implements Comparator<CalendarEvent> {
+
+        @Override
+        public int compare(CalendarEvent e1, CalendarEvent e2) {
+            if (e1.isAllDay() != e2.isAllDay()) {
+                if (e2.isAllDay()) {
+                    return 1;
+                }
+                return -1;
+            }
+            int result = doCompare(e1, e2);
+            if (result == 0) {
+                return indexCompare(e1, e2);
+            }
+            return result;
+        }
+
+        protected int indexCompare(CalendarEvent e1, CalendarEvent e2) {
+            return ((Integer) e2.getIndex()).compareTo(e1.getIndex());
+        }
+
+        protected abstract int doCompare(CalendarEvent o1, CalendarEvent o2);
+    }
+
+    private static class EventDurationComparator
+            extends AbstractEventComparator {
+
+        EventDurationComparator(boolean ascending) {
+            isAscending = ascending;
+        }
+
+        @Override
+        public int doCompare(CalendarEvent e1, CalendarEvent e2) {
+            int result = durationCompare(e1, e2, isAscending);
+            if (result == 0) {
+                return StartDateComparator.startDateCompare(e1, e2,
+                        isAscending);
+            }
+            return result;
+        }
+
+        static int durationCompare(CalendarEvent e1, CalendarEvent e2,
+                boolean ascending) {
+            int result = doDurationCompare(e1, e2);
+            return ascending ? -result : result;
+        }
+
+        private static int doDurationCompare(CalendarEvent e1,
+                CalendarEvent e2) {
+            Long d1 = e1.getRangeInMilliseconds();
+            Long d2 = e2.getRangeInMilliseconds();
+            if (!d1.equals(0L) && !d2.equals(0L)) {
+                return d2.compareTo(d1);
+            }
+
+            if (d2.equals(0L) && d1.equals(0L)) {
+                return 0;
+            } else if (d2.equals(0L) && d1 >= DateConstants.DAYINMILLIS) {
+                return -1;
+            } else if (d2.equals(0L) && d1 < DateConstants.DAYINMILLIS) {
+                return 1;
+            } else if (d1.equals(0L) && d2 >= DateConstants.DAYINMILLIS) {
+                return 1;
+            } else if (d1.equals(0L) && d2 < DateConstants.DAYINMILLIS) {
+                return -1;
+            }
+            return d2.compareTo(d1);
+        }
+
+        private boolean isAscending;
+
+    }
+
+    private static class StartDateComparator extends AbstractEventComparator {
+
+        StartDateComparator(boolean ascending) {
+            isAscending = ascending;
+        }
+
+        @Override
+        public int doCompare(CalendarEvent e1, CalendarEvent e2) {
+            int result = startDateCompare(e1, e2, isAscending);
+            if (result == 0) {
+                // show a longer event after a shorter event
+                return EventDurationComparator.durationCompare(e1, e2,
+                        isAscending);
+            }
+            return result;
+        }
+
+        static int startDateCompare(CalendarEvent e1, CalendarEvent e2,
+                boolean ascending) {
+            int result = e1.getStartTime().compareTo(e2.getStartTime());
+            return ascending ? -result : result;
+        }
+
+        private boolean isAscending;
+    }
+
     /**
      * Default constructor
      */
@@ -262,15 +368,15 @@ public class VCalendar extends Composite implements VHasDropHandler {
        e.onselectstart = function() {
                return false;
        }
-
+    
        e.ondragstart = function() {
                return false;
        }
     }-*/;
 
     private void updateEventsToWeekGrid(CalendarEvent[] events) {
-        List<CalendarEvent> allDayLong = new ArrayList<CalendarEvent>();
-        List<CalendarEvent> belowDayLong = new ArrayList<CalendarEvent>();
+        List<CalendarEvent> allDayLong = new ArrayList<>();
+        List<CalendarEvent> belowDayLong = new ArrayList<>();
 
         for (CalendarEvent e : events) {
             if (e.isAllDay()) {
@@ -302,7 +408,7 @@ public class VCalendar extends Composite implements VHasDropHandler {
      */
     public void updateEventsToMonthGrid(Collection<CalendarEvent> events,
             boolean drawImmediately) {
-        for (CalendarEvent e : sortEventsByDuration(events)) {
+        for (CalendarEvent e : sortEvents(events)) {
             // FIXME Why is drawImmediately not used ?????
             addEventToMonthGrid(e, false);
         }
@@ -315,8 +421,8 @@ public class VCalendar extends Composite implements VHasDropHandler {
         boolean eventAdded = false;
         boolean inProgress = false; // Event adding has started
         boolean eventMoving = false;
-        List<SimpleDayCell> dayCells = new ArrayList<SimpleDayCell>();
-        List<SimpleDayCell> timeCells = new ArrayList<SimpleDayCell>();
+        List<SimpleDayCell> dayCells = new ArrayList<>();
+        List<SimpleDayCell> timeCells = new ArrayList<>();
         for (int row = 0; row < monthGrid.getRowCount(); row++) {
             if (eventAdded) {
                 break;
@@ -462,13 +568,45 @@ public class VCalendar extends Composite implements VHasDropHandler {
         addEventToMonthGrid(changedEvent, true);
     }
 
+    /**
+     * Sort the events by current sort order
+     * 
+     * @param events
+     *            The events to sort
+     * @return An array where the events has been sorted
+     */
+    public CalendarEvent[] sortEvents(Collection<CalendarEvent> events) {
+        if (EventSortOrder.DURATION_DESC.equals(eventSortOrder)) {
+            return sortEventsByDuration(events);
+        } else if (!EventSortOrder.UNSORTED.equals(eventSortOrder)) {
+            CalendarEvent[] sorted = events
+                    .toArray(new CalendarEvent[events.size()]);
+            switch (eventSortOrder) {
+            case DURATION_ASC:
+                Arrays.sort(sorted, new EventDurationComparator(true));
+                break;
+            case START_DATE_ASC:
+                Arrays.sort(sorted, new StartDateComparator(true));
+                break;
+            case START_DATE_DESC:
+                Arrays.sort(sorted, new StartDateComparator(false));
+                break;
+            }
+            return sorted;
+        }
+        return events.toArray(new CalendarEvent[events.size()]);
+    }
+
     /**
      * Sort the event by how long they are
-     *
+     * 
      * @param events
      *            The events to sort
      * @return An array where the events has been sorted
+     * @deprecated use {@link #sortEvents(Collection)} method which shorts
+     *             events by current sort order.
      */
+    @Deprecated
     public CalendarEvent[] sortEventsByDuration(
             Collection<CalendarEvent> events) {
         CalendarEvent[] sorted = events
@@ -828,49 +966,18 @@ public class VCalendar extends Composite implements VHasDropHandler {
     }
 
     /**
-     * Returns a comparator which can compare calendar events.
-     *
+     * Returns the default comparator which can compare calendar events by
+     * duration.
+     * 
+     * @deprecated this returns just one default comparator, but there are
+     *             number of comparators that are used to sort events depending
+     *             on order.
+     * 
      * @return
      */
+    @Deprecated
     public static Comparator<CalendarEvent> getEventComparator() {
-        return new Comparator<CalendarEvent>() {
-
-            @Override
-            public int compare(CalendarEvent o1, CalendarEvent o2) {
-                if (o1.isAllDay() != o2.isAllDay()) {
-                    if (o2.isAllDay()) {
-                        return 1;
-                    }
-                    return -1;
-                }
-
-                Long d1 = o1.getRangeInMilliseconds();
-                Long d2 = o2.getRangeInMilliseconds();
-                int r = 0;
-                if (!d1.equals(0L) && !d2.equals(0L)) {
-                    r = d2.compareTo(d1);
-                    return (r == 0)
-                            ? ((Integer) o2.getIndex()).compareTo(o1.getIndex())
-                            : r;
-                }
-
-                if (d2.equals(0L) && d1.equals(0L)) {
-                    return ((Integer) o2.getIndex()).compareTo(o1.getIndex());
-                } else if (d2.equals(0L) && d1 >= DateConstants.DAYINMILLIS) {
-                    return -1;
-                } else if (d2.equals(0L) && d1 < DateConstants.DAYINMILLIS) {
-                    return 1;
-                } else if (d1.equals(0L) && d2 >= DateConstants.DAYINMILLIS) {
-                    return 1;
-                } else if (d1.equals(0L) && d2 < DateConstants.DAYINMILLIS) {
-                    return -1;
-                }
-                r = d2.compareTo(d1);
-                return (r == 0)
-                        ? ((Integer) o2.getIndex()).compareTo(o1.getIndex())
-                        : r;
-            }
-        };
+        return DEFAULT_COMPARATOR;
     }
 
     /**
@@ -1086,7 +1193,7 @@ public class VCalendar extends Composite implements VHasDropHandler {
             weekGrid = new WeekGrid(this, is24HFormat());
         }
         updateWeekGrid(daysInMonth, days, today, realDayNames);
-        updateEventsToWeekGrid(sortEventsByDuration(events));
+        updateEventsToWeekGrid(sortEvents(events));
         outer.add(dayToolbar, DockPanel.NORTH);
         outer.add(weeklyLongEvents, DockPanel.NORTH);
         outer.add(weekGrid, DockPanel.SOUTH);
@@ -1501,4 +1608,28 @@ public class VCalendar extends Composite implements VHasDropHandler {
     public boolean isEventCaptionAsHtml() {
         return eventCaptionAsHtml;
     }
+
+    /**
+     * Set sort strategy for events.
+     * 
+     * @param order
+     *            sort order
+     */
+    public void setSortOrder(EventSortOrder order) {
+        if (order == null) {
+            eventSortOrder = EventSortOrder.DURATION_DESC;
+        } else {
+            eventSortOrder = order;
+        }
+    }
+
+    /**
+     * Return currently active sort order.
+     * 
+     * @return current sort order
+     */
+    public EventSortOrder getSortOrder() {
+        return eventSortOrder;
+    }
+
 }
index 0e50bd639d488dd59ff5a0b92bc2c570d58c9985..456c9f392b5c2ccd73e781dda8f4f46ca75ac0be 100644 (file)
@@ -44,6 +44,7 @@ import com.vaadin.client.ui.ActionOwner;
 import com.vaadin.client.ui.SimpleManagedLayout;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.shared.util.SharedUtil;
 import com.vaadin.v7.client.ui.AbstractLegacyComponentConnector;
 import com.vaadin.v7.client.ui.VCalendar;
 import com.vaadin.v7.client.ui.VCalendar.BackwardListener;
@@ -71,6 +72,7 @@ import com.vaadin.v7.shared.ui.calendar.CalendarClientRpc;
 import com.vaadin.v7.shared.ui.calendar.CalendarEventId;
 import com.vaadin.v7.shared.ui.calendar.CalendarServerRpc;
 import com.vaadin.v7.shared.ui.calendar.CalendarState;
+import com.vaadin.v7.shared.ui.calendar.CalendarState.EventSortOrder;
 import com.vaadin.v7.shared.ui.calendar.DateConstants;
 import com.vaadin.v7.ui.Calendar;
 
@@ -352,6 +354,12 @@ public class CalendarConnector extends AbstractLegacyComponentConnector
 
         widget.setEventCaptionAsHtml(state.eventCaptionAsHtml);
 
+        EventSortOrder oldOrder = getWidget().getSortOrder();
+        if (!SharedUtil.equals(oldOrder, getState().eventSortOrder)) {
+            getWidget().setSortOrder(getState().eventSortOrder);
+        }
+        updateEventsInView();
+
         List<CalendarState.Day> days = state.days;
         List<CalendarState.Event> events = state.events;
 
@@ -449,6 +457,27 @@ public class CalendarConnector extends AbstractLegacyComponentConnector
         return true;
     }
 
+    private void updateEventsInView() {
+        CalendarState state = getState();
+        List<CalendarState.Day> days = state.days;
+        List<CalendarState.Event> events = state.events;
+
+        CalendarDropHandler dropHandler = getWidget().getDropHandler();
+        if (showingMonthView()) {
+            updateMonthView(days, events);
+            if (dropHandler != null
+                    && !(dropHandler instanceof CalendarMonthDropHandler)) {
+                getWidget().setDropHandler(new CalendarMonthDropHandler(this));
+            }
+        } else {
+            updateWeekView(days, events);
+            if (dropHandler != null
+                    && !(dropHandler instanceof CalendarWeekDropHandler)) {
+                getWidget().setDropHandler(new CalendarWeekDropHandler(this));
+            }
+        }
+    }
+
     private void updateMonthView(List<CalendarState.Day> days,
             List<CalendarState.Event> events) {
         CalendarState state = getState();
index 31f443cd77aaac83d6eeda32f8bee00b50961809..d914de8ff567dbc7feca66172a3d905a45d21a1c 100644 (file)
@@ -546,8 +546,7 @@ public class DateCell extends FocusableComplexPanel implements MouseDownHandler,
         events.add(dayEvent.getCalendarEvent());
 
         index = 0;
-        for (CalendarEvent e : weekgrid.getCalendar()
-                .sortEventsByDuration(events)) {
+        for (CalendarEvent e : weekgrid.getCalendar().sortEvents(events)) {
             if (e.equals(dayEvent.getCalendarEvent())) {
                 break;
             }
index a33cce6eef7f4905d5286e38ed48be3db44450ac..d347028895ca05cd0c43b5b946d7003ce5ffa744 100644 (file)
@@ -57,6 +57,7 @@ import com.vaadin.v7.data.util.BeanItemContainer;
 import com.vaadin.v7.shared.ui.calendar.CalendarEventId;
 import com.vaadin.v7.shared.ui.calendar.CalendarServerRpc;
 import com.vaadin.v7.shared.ui.calendar.CalendarState;
+import com.vaadin.v7.shared.ui.calendar.CalendarState.EventSortOrder;
 import com.vaadin.v7.shared.ui.calendar.DateConstants;
 import com.vaadin.v7.ui.components.calendar.CalendarComponentEvent;
 import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents;
@@ -999,6 +1000,36 @@ public class Calendar extends AbstractLegacyComponent
         }
     }
 
+    /**
+     * Sets sort order for events. By default sort order is
+     * {@link EventSortOrder#DURATION_DESC}.
+     * 
+     * @param order
+     *            sort strategy for events
+     */
+    public void setEventSortOrder(EventSortOrder order) {
+        if (order == null) {
+            getState().eventSortOrder = EventSortOrder.DURATION_DESC;
+        } else {
+            getState().eventSortOrder = EventSortOrder.values()[order
+                    .ordinal()];
+        }
+    }
+
+    /**
+     * Returns sort order for events.
+     * 
+     * @return currently active sort strategy
+     */
+    public EventSortOrder getEventSortOrder() {
+        EventSortOrder order = getState(false).eventSortOrder;
+        if (order == null) {
+            return EventSortOrder.DURATION_DESC;
+        } else {
+            return order;
+        }
+    }
+
     private DateFormat getWeeklyCaptionFormatter() {
         if (weeklyCaptionFormat != null) {
             return new SimpleDateFormat(weeklyCaptionFormat, getLocale());
index dd15c09b7f0ad3e69605ff21e47a3698b125962a..646c7eed44ce3034b23814e4783d425c2fc1996c 100644 (file)
@@ -40,6 +40,32 @@ public class CalendarState extends AbstractLegacyComponentState {
     public List<CalendarState.Action> actions;
     public boolean eventCaptionAsHtml;
 
+    public EventSortOrder eventSortOrder = EventSortOrder.DURATION_DESC;
+
+    /**
+     * Defines sort strategy for events in calendar month view and week view. In
+     * month view events will be sorted from top to bottom using the order in
+     * day cell. In week view events inside same day will be sorted from left to
+     * right using the order if their intervals are overlapping.
+     * <p>
+     * <ul>
+     * <li>{@code UNSORTED} means no sort. Events will be in the order provided
+     * by com.vaadin.ui.components.calendar.event.CalendarEventProvider.
+     * <li>{@code START_DATE_DESC} means descending sort by events start date
+     * (earlier event are shown first).
+     * <li>{@code DURATION_DESC} means descending sort by duration (longer event
+     * are shown first).
+     * <li>{@code START_DATE_ASC} means ascending sort by events start date
+     * (later event are shown first).
+     * <li>{@code DURATION_ASC} means ascending sort by duration (shorter event
+     * are shown first).
+     * 
+     * </ul>
+     */
+    public enum EventSortOrder {
+        UNSORTED, START_DATE_DESC, START_DATE_ASC, DURATION_DESC, DURATION_ASC;
+    }
+
     public static class Day implements java.io.Serializable {
         public String date;
         public String localizedDateFormat;
diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/calendar/CalendarEventsSort.java b/uitest/src/main/java/com/vaadin/v7/tests/components/calendar/CalendarEventsSort.java
new file mode 100644 (file)
index 0000000..4236aad
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.tests.components.calendar;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.v7.shared.ui.calendar.CalendarState.EventSortOrder;
+import com.vaadin.v7.ui.Calendar;
+import com.vaadin.v7.ui.components.calendar.event.BasicEvent;
+import com.vaadin.v7.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.v7.ui.components.calendar.event.CalendarEventProvider;
+
+/**
+ * 
+ * Test UI for event sorting in calendar month and week views.
+ * 
+ * @author Vaadin Ltd
+ */
+public class CalendarEventsSort extends AbstractTestUI {
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        getContent().setSizeFull();
+        final Calendar calendar = new Calendar("Test calendar");
+
+        toMonthView(calendar);
+        calendar.setEventSortOrder(EventSortOrder.UNSORTED);
+
+        calendar.setEventProvider(createEventProvider());
+        addComponent(calendar);
+
+        createSortByDateButton(calendar);
+        createSortByDurationButton(calendar);
+        createSortByProviderButton(calendar);
+        createViewSwitchButton(calendar);
+    }
+
+    private void createViewSwitchButton(final Calendar calendar) {
+        Button toWeek = new Button("Switch to week view", new ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                Button button = event.getButton();
+                Boolean month = (Boolean) button.getData();
+                button.setData(!month);
+                if (month) {
+                    button.setCaption("Switch to month view");
+                    toWeekView(calendar);
+                } else {
+                    button.setCaption("Switch to week view");
+                    toMonthView(calendar);
+                }
+            }
+
+        });
+        toWeek.addStyleName("view");
+        toWeek.setData(true);
+        addComponent(toWeek);
+    }
+
+    private Button createSortByProviderButton(final Calendar calendar) {
+        Button byProvider = new Button("Sort by provider", new ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                calendar.setEventSortOrder(EventSortOrder.UNSORTED);
+            }
+        });
+        byProvider.addStyleName("by-provider");
+        addComponent(byProvider);
+        return byProvider;
+    }
+
+    private void createSortByDurationButton(final Calendar calendar) {
+        Button byDuration = new Button("Sort by duration DESC",
+                new ClickListener() {
+
+                    @Override
+                    public void buttonClick(ClickEvent event) {
+                        Button button = event.getButton();
+                        EventSortOrder order = (EventSortOrder) button
+                                .getData();
+                        if (EventSortOrder.DURATION_DESC.equals(order)) {
+                            order = EventSortOrder.DURATION_ASC;
+                            button.setCaption("Sort by duration DESC");
+                            addSortOrder(true, button);
+                        } else {
+                            order = EventSortOrder.DURATION_DESC;
+                            button.setCaption("Sort by duration ASC");
+                            addSortOrder(false, button);
+                        }
+                        button.setData(order);
+                        calendar.setEventSortOrder(order);
+                    }
+                });
+        byDuration.addStyleName("by-duration");
+        byDuration.setData(EventSortOrder.DURATION_ASC);
+        addComponent(byDuration);
+    }
+
+    private void createSortByDateButton(final Calendar calendar) {
+        Button byStartDate = new Button("Sort by start date DESC",
+                new ClickListener() {
+
+                    @Override
+                    public void buttonClick(ClickEvent event) {
+                        Button button = event.getButton();
+                        EventSortOrder order = (EventSortOrder) button
+                                .getData();
+                        if (EventSortOrder.START_DATE_DESC.equals(order)) {
+                            order = EventSortOrder.START_DATE_ASC;
+                            button.setCaption("Sort by start date DESC");
+                            addSortOrder(true, button);
+                        } else {
+                            order = EventSortOrder.START_DATE_DESC;
+                            button.setCaption("Sort by start date ASC");
+                            addSortOrder(false, button);
+                        }
+                        button.setData(order);
+                        calendar.setEventSortOrder(order);
+                    }
+                });
+        byStartDate.setData(EventSortOrder.START_DATE_ASC);
+        byStartDate.addStyleName("by-start-date");
+        addComponent(byStartDate);
+    }
+
+    private CalendarEventProvider createEventProvider() {
+        CalendarEventProvider provider = new CalendarEventProvider() {
+
+            @Override
+            public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+                java.util.Calendar cal = java.util.Calendar.getInstance();
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 5);
+                cal.set(java.util.Calendar.MINUTE, 0);
+                cal.set(java.util.Calendar.SECOND, 0);
+                cal.set(java.util.Calendar.MILLISECOND, 0);
+
+                Date start = cal.getTime();
+                cal.add(java.util.Calendar.HOUR_OF_DAY, 2);
+                Date end = cal.getTime();
+
+                CalendarEvent event1 = new BasicEvent("first", "descr1", start,
+                        end);
+
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 2);
+                start = cal.getTime();
+                cal.add(java.util.Calendar.HOUR_OF_DAY, 4);
+                end = cal.getTime();
+
+                CalendarEvent event2 = new BasicEvent("second", "descr2", start,
+                        end);
+
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 1);
+                start = cal.getTime();
+                cal.add(java.util.Calendar.HOUR_OF_DAY, 2);
+                end = cal.getTime();
+
+                CalendarEvent event3 = new BasicEvent("third", "descr2", start,
+                        end);
+
+                return Arrays.asList(event1, event2, event3);
+            }
+
+        };
+        return provider;
+    }
+
+    private void addSortOrder(boolean ascending, Button button) {
+        if (ascending) {
+            button.addStyleName("asc");
+            button.removeStyleName("desc");
+        } else {
+            button.removeStyleName("asc");
+            button.addStyleName("desc");
+        }
+    }
+
+    private void toMonthView(final Calendar calendar) {
+        final java.util.Calendar cal = java.util.Calendar.getInstance();
+
+        cal.add(java.util.Calendar.DAY_OF_YEAR, -2);
+        calendar.setStartDate(cal.getTime());
+        cal.add(java.util.Calendar.DAY_OF_YEAR, 14);
+        calendar.setEndDate(cal.getTime());
+    }
+
+    private void toWeekView(final Calendar calendar) {
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.add(java.util.Calendar.DAY_OF_YEAR, 2);
+        calendar.setEndDate(cal.getTime());
+    }
+
+    @Override
+    public String getDescription() {
+        return "Make event sorting strategy customizable.";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 14849;
+    }
+}
\ No newline at end of file
diff --git a/uitest/src/test/java/com/vaadin/v7/tests/components/calendar/CalendarEventsSortTest.java b/uitest/src/test/java/com/vaadin/v7/tests/components/calendar/CalendarEventsSortTest.java
new file mode 100644 (file)
index 0000000..27cf549
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.tests.components.calendar;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+/**
+ * Check how event sorting works in calendar month and week views.
+ * 
+ * @author Vaadin Ltd
+ */
+public class CalendarEventsSortTest extends MultiBrowserTest {
+
+    @Test
+    public void testByDuration() {
+        openTestURL();
+
+        checkSortByDuration(true);
+    }
+
+    @Test
+    public void testByStartDate() {
+        openTestURL();
+
+        checkSortByStartDate(true);
+    }
+
+    @Test
+    public void testByProvider() {
+        openTestURL();
+
+        List<WebElement> events = findElements(
+                By.className("v-calendar-event-month"));
+        checkProviderOrder(events);
+    }
+
+    @Test
+    public void testWeekByDuration() {
+        openTestURL();
+
+        findElement(By.className("view")).click();
+
+        checkSortByDuration(false);
+    }
+
+    @Test
+    public void testWeekByStartDate() {
+        openTestURL();
+
+        findElement(By.className("view")).click();
+
+        checkSortByStartDate(false);
+    }
+
+    @Test
+    public void testWeekByProvider() {
+        openTestURL();
+
+        findElement(By.className("view")).click();
+
+        List<WebElement> events = findElements(
+                By.className("v-calendar-event-caption"));
+        checkProviderOrder(events);
+    }
+
+    private void checkSortByStartDate(boolean month) {
+        sort("by-start-date", false);
+
+        String style = month ? "v-calendar-event-month"
+                : "v-calendar-event-caption";
+        List<WebElement> events = findElements(By.className(style));
+        checkStartDateOrderDesc(events);
+
+        sort("by-start-date", true);
+
+        events = findElements(By.className(style));
+        checkStartDateOrderAsc(events);
+    }
+
+    private void sort(String style, boolean ascending) {
+        findElement(By.className(style)).click();
+
+        if (!isElementPresent(
+                By.cssSelector('.' + style + (ascending ? ".asc" : ".desc")))) {
+            findElement(By.className(style)).click();
+        }
+    }
+
+    private void checkSortByDuration(boolean month) {
+        sort("by-duration", false);
+
+        String style = month ? "v-calendar-event-month"
+                : "v-calendar-event-caption";
+
+        List<WebElement> events = findElements(By.className(style));
+        checkDurationOrderDesc(events);
+
+        sort("by-duration", true);
+        events = findElements(By.className(style));
+        checkDurationOrderAsc(events);
+    }
+
+    private void checkDurationOrderDesc(List<WebElement> events) {
+        Assert.assertTrue(
+                "'Second' event should be the first when sorted by duration",
+                events.get(0).getText().endsWith("second"));
+        Assert.assertTrue(
+                "'Third' event should be the second when sorted by duration",
+                events.get(1).getText().endsWith("third"));
+        Assert.assertTrue(
+                "'First' event should be the third when sorted by duration",
+                events.get(2).getText().endsWith("first"));
+    }
+
+    private void checkDurationOrderAsc(List<WebElement> events) {
+        Assert.assertTrue(
+                "'First' event should be the first when sorted by duration",
+                events.get(0).getText().endsWith("first"));
+        Assert.assertTrue(
+                "'Third' event should be the second when sorted by duration",
+                events.get(1).getText().endsWith("third"));
+        Assert.assertTrue(
+                "'Second' event should be the third when sorted by duration",
+                events.get(2).getText().endsWith("second"));
+    }
+
+    private void checkStartDateOrderDesc(List<WebElement> events) {
+        Assert.assertTrue(
+                "'Third' event should be the first when sorted by start date",
+                events.get(0).getText().endsWith("third"));
+        Assert.assertTrue(
+                "'Second' event should be the second when sorted by start date",
+                events.get(1).getText().endsWith("second"));
+        Assert.assertTrue(
+                "'First' event should be the third when sorted by start date",
+                events.get(2).getText().endsWith("first"));
+    }
+
+    private void checkStartDateOrderAsc(List<WebElement> events) {
+        Assert.assertTrue(
+                "'First' event should be the first when sorted by start date",
+                events.get(0).getText().endsWith("first"));
+        Assert.assertTrue(
+                "'Second' event should be the second when sorted by start date",
+                events.get(1).getText().endsWith("second"));
+        Assert.assertTrue(
+                "'Third' event should be the third when sorted by start date",
+                events.get(2).getText().endsWith("third"));
+    }
+
+    private void checkProviderOrder(List<WebElement> events) {
+        Assert.assertTrue(
+                "'First' event should be the first when sorted by provider",
+                events.get(0).getText().endsWith("first"));
+        Assert.assertTrue(
+                "'Second' event should be the second when sorted by provider",
+                events.get(1).getText().endsWith("second"));
+        Assert.assertTrue(
+                "'Third' event should be the third when sorted by provider",
+                events.get(2).getText().endsWith("third"));
+    }
+
+}