-
Notifications
You must be signed in to change notification settings - Fork 28
Release notes 7.2
This page gives you an overview of the major changes that came with the release of FullCalendar for Flow, version 7.2.
Platform (unchanged from 7.1): FullCalendar JS 6.1.20, Vaadin 25.x, Java 21, Jackson 3. 7.2 is a source-compatible minor release; see the migration guide for the single binary-compat caveat and migration notes.
7.2.3 — Serializable: client-side event sources and Draggable (#239)
Follow-up to the 7.2.2 fix. A field audit of the whole FullCalendar component graph surfaced two more reachable types that were still not serializable, so session passivation kept failing with NotSerializableException for calendars using them:
- client-side event sources (
JsonFeed,ICalendar,GoogleCalendarviaClientSideEventSource), held in the client-side event source registry -
Draggable, held in the draggable registry
Both now implement Serializable. SerializationTest covers both paths, and the scheduler side gained coverage for component-based resource area columns and views.
Upgrade from 7.2.2 if you use external event sources or server-side draggables together with session passivation or cluster replication.
Since 7.2.1, views containing a FullCalendar failed session passivation and cluster replication: a non-serializable Object refreshLock field was reachable from the component, so the servlet container could not write the session out.
The lock was removed rather than replaced. All Vaadin component access is already serialized under the VaadinSession lock, so the synchronized/volatile guarding around refreshAllEntriesRequested protected a race that cannot occur. The volatile modifiers on the component's flag fields went with it.
To make the remainder of the graph serializable, these types now implement Serializable:
EntryProvider, ClientSideValue (which covers all views, enums and timezones), Entry, RRule, RecurringTime, Resource, BusinessHours, ResourceAreaColumn.
If you subclass Entry or Resource, or write your own EntryProvider, make sure your own fields are serializable too (or transient) — that is the usual requirement for anything held in a Vaadin view. SerializationTest / SchedulerSerializationTest guard this from 7.2.2 onwards.
Also in this release: the repository gained an explicit MIT LICENSE file, and the test suite moved to a newer Mockito (#238).
7.2.1 — RRule exclusion and serialization fixes (#237)
Two defects in the JSON representation of RRule were masking each other. Both are fixed in 7.2.1; no source changes are required for typical use. If you call RRule#toJson() directly (e.g. for persistence or logging), read the return-type change note at the bottom of this section before upgrading.
With 7.2.0, adding an exrule to an entry made no events render on the calendar at all — not just the one carrying the exrule. The browser console showed Error: Invalid options: 0, 1, ..., N, and the calendar stayed empty.
Root cause: FullCalendar's rrule plugin only accepts exrule as a structured object (or array of objects); 7.2.0 serialized it as an iCalendar string. The plugin tried to parse the string as an options object, iterating its char indices as property keys and crashing. The crash aborted parsing of every event on the calendar.
From 7.2.1 the structured form of RRule serializes to a JSON object, so exrule works as documented.
Two patterns keep the string form (nothing to do on your side — just good to know):
- Raw RRules (
RRule.ofRaw(...)) — there is nothing to serialize structurally. - Patterns with positional BYDAY tokens (e.g.
"-1fr","2mo"). FullCalendar's object-form parser resolves weekday strings viarruleLib.RRule[day.toUpperCase()]and only knows plainMO..SU; a positional token would resolve toundefinedand crash the plugin. The string parser handles positional tokens natively, so we fall back to it automatically for these rules.
RRule.weekly().byWeekday(DayOfWeek.MONDAY).excludeDates(...) without an explicit dtstart(...) produced no occurrences, even when the entry had setStart(...). rrule-js requires an anchor and FullCalendar does not fall back to the entry's start on its own.
From 7.2.1 the RRule-to-JSON converter injects the entry's start as dtstart when the structured RRule has none. All-day entries emit a date-only value ("2025-03-03"), timed entries emit a local datetime ("2025-03-03T10:30:00"). An explicit RRule#dtstart(...) continues to win.
The previous silent-failure path (raw exrule → string form → FC plugin crash) is now caught at the API boundary. Passing an ofRaw(...) rule to excludeRules(...) throws IllegalArgumentException with a clear message.
Translate the raw RRULE string into a structured RRule via the fluent API before excluding it:
// Before 7.2.1 — silently broke the whole calendar:
rule.excludeRules(RRule.ofRaw("FREQ=WEEKLY;BYDAY=MO"));
// 7.2.1 and later — supply a structured rule instead:
rule.excludeRules(RRule.weekly().byWeekday(DayOfWeek.MONDAY));RRule#toJson() is public and documented, though primarily intended for internal use by the addon's JSON converters. The returned JsonNode subtype is now:
- ObjectNode for structured RRules without positional BYDAY (the common case).
- StringNode for raw RRules and for patterns with positional BYDAY tokens.
Prior to 7.2.1 it was always a StringNode. Code that consumed the return value as text (e.g. rrule.toJson().asString() for persistence or logging) will now read an empty string for the common case, because asString() on an ObjectNode returns "" silently rather than throwing. If you persist or log the RRule's iCalendar form, use rrule.toRRuleString() — it is unchanged and stable.
- Client-side entry IDs — default on, no configuration needed.
- Auto-revert for unapplied entry changes — default on; prevents server/client drift when a drop/resize listener rejects the change.
-
FullCalendarBuilderdeprecated — direct construction +setOption(...)is the forward path. No removal scheduled for 7.x. - Bug fixes for scheduler extended-prop updates (#230), scheduler timeline sizing (#231) and per-entry editable (#212).
Server-side components such as Popover anchor to target elements via document.getElementById. Before 7.2, rendered calendar entries had no DOM id, so anchoring required a custom eventDidMount callback with JavaScript-side id generation.
Starting with 7.2, FullCalendar assigns id="entry-<entryId>" to the start segment of each rendered entry by default. The feature is controlled by setAutoProvideEntryIdOnClient(boolean), which defaults to true.
// Default on — nothing to configure.
Entry meeting = new Entry("m-1");
meeting.setTitle("Quarterly review");
// ...
// Anchor a Popover to the rendered entry:
Popover popover = new Popover();
popover.setFor("entry-m-1");Opt-out:
calendar.setAutoProvideEntryIdOnClient(false);-
Single-day entry, any view →
id="entry-<entryId>"on the single segment. -
Multi-day entry (month/week view) →
id="entry-<entryId>"on the start segment only; continuation segments stay id-free so HTML id uniqueness is preserved. -
Scheduler, entry assigned to multiple resources → to keep ids unique across the resource rows, the id is suffixed with the row's resource id:
id="entry-<entryId>-<resourceId>". Entries that live in a single resource row get the plainentry-<entryId>schema. - Drag-preview ("mirror") elements during an ongoing drag are not tagged — the id stays on the real element.
If you set your own eventDidMount callback via setOption(Option.ENTRY_DID_MOUNT, JsCallback.of(...)), the default snippet runs first and your callback runs afterwards, so reassigning info.el.id in your callback wins.
calendar.setOption(Option.ENTRY_DID_MOUNT, JsCallback.of(
"function(info) { info.el.id = 'my-' + info.event.id; }"));
// Result: id='my-<entryId>' (your value, the default was overwritten)- For anchoring to the exact segment a user clicked, prefer the element from the click event rather than
getElementById— the click element always points at the clicked segment, even for multi-day entries whose id lives only on the start. - The feature relies on the
eventDidMountrender hook. If you replace that hook with a brace-less arrow expression (e.g."info => info.el.title = info.event.title"), the default snippet is skipped to keep the output syntactically valid. Use a braced function body to keep the default id-assignment alongside your callback.
When a user drags or resizes an entry, FullCalendar JS immediately moves the entry to its new position on the client. The server receives an EntryDroppedEvent or EntryResizedEvent. Previously, if the developer chose not to call event.applyChangesOnEntry() (e.g., after a validation failure), the client would keep showing the entry at the new position while the server still had the old data — an inconsistent state.
Starting with 7.2, the calendar automatically reverts the client-side entry to its original position when applyChangesOnEntry() is not called. This is controlled by setAutoRevertUnappliedEntryChanges(boolean), which defaults to true.
// Auto-revert is enabled by default — no configuration needed.
// Entries revert automatically when applyChangesOnEntry() is not called.
calendar.addEntryDroppedListener(event -> {
if (isDropAllowed(event)) {
event.applyChangesOnEntry(); // entry stays at new position
entryProvider.refreshItem(event.getEntry());
}
// If not called: entry reverts to original position on the client
});To restore the previous behavior (client keeps the new position regardless):
calendar.setAutoRevertUnappliedEntryChanges(false);- When a drop or resize occurs, the FullCalendar JS
revert()function is captured on the client side. - On the server, after all event listeners have been executed, a
beforeClientResponsecallback checks whetherapplyChangesOnEntry()was called. - If not called (and auto-revert is enabled), the server sends a
revertEntrycommand to the client, which triggers FullCalendar's native revert animation. - If
applyChangesOnEntry()was called, the pending revert is cleared — the entry stays at the new position.
- Auto-revert works for
EntryDroppedEvent,EntryResizedEvent, andEntryDroppedSchedulerEvent. -
ExternalEntryDroppedEvent/ExternalEntryResizedEvent(from client-side event sources or draggable external DOM) do not participate in auto-revert — those flows create new entries on drop, so the server-side handler typically owns the accept/reject decision explicitly. - The revert uses FullCalendar's native revert mechanism, which provides a smooth animation back to the original position.
- If you need to inspect the state from another listener or a downstream component,
EntryDataEvent#isChangesApplied()returns whetherapplyChangesOnEntry()was called on this event — useful when multiple listeners are chained and a later one needs to know whether the earlier ones already accepted the change.
FullCalendarBuilder and all its with... methods are now @Deprecated. FullCalendar and FullCalendarScheduler can be constructed directly and configured through setOption(Option, value); the builder no longer provides value beyond syntactic sugar. No removal is scheduled for 7.x — the class is slated for removal in a future major version.
// 7.1 — builder
FullCalendarScheduler scheduler = (FullCalendarScheduler) FullCalendarBuilder.create()
.withScheduler(licenseKey)
.withEntryLimit(5)
.build();// 7.2 — direct construction
FullCalendarScheduler scheduler = new FullCalendarScheduler();
scheduler.setOption(Option.SCHEDULER_LICENSE_KEY, licenseKey);
scheduler.setOption(Option.MAX_ENTRIES_PER_DAY, 5);See the 7.1 → 7.2 migration guide for the full mapping table.
New Option.INITIAL_VIEW: verified that FullCalendar 6.1.20 applies the initialView option correctly on first render, so the withInitialView(view) replacement is a clean setOption(Option.INITIAL_VIEW, view.getClientSideValue()) rather than the attach-listener + changeView() workaround the builder used to carry. The builder's build() method now uses the option directly as well.
Also deprecated: the single-argument constructors FullCalendar(int entryLimit) and FullCalendarScheduler(int entryLimit). Replace with the no-arg constructor plus setOption(Option.MAX_ENTRIES_PER_DAY, n).
The bundled DemoDialog (used by the full demo and entry-provider demos) now shows a "Resources" multi-select field when the edited entry is a ResourceEntry on a Scheduler-capable calendar. Available resources are read from Scheduler.getResources(). When creating an entry via timeslot selection in a resource view, the resource the user clicked on is pre-selected.
Demo-only change — no addon API surface.
FullCalendar JS normalises year/month portions of a drop/resize delta into days before sending it to the server — dragging an entry from e.g. 31 March to 29 February produces days: -31, not months: -1. The Delta.years and Delta.months fields therefore stay zero for every FC-originated drag/drop event.
The two getters are now @Deprecated(since = "7.2.0") to document this. Handlers that react to drag/drop should read Delta.getDays() and the time components; the year/month fields remain only for manually-constructed Delta instances.
No removal scheduled; deprecated methods continue to work throughout 7.x.
The old name was verbose and did not describe what the method actually returns. From 7.2 the method is called getChangesAsEntry(). The old name remains as a @Deprecated(since = "7.2.0") delegate — existing code keeps working.
Previously, calling scheduler.updateResource(resource) only applied changes to known built-in properties (title, color, eventOverlap, …) on the client side. Any changes to extended props were silently dropped.
Resource room = new Resource("room-1", "Room 1", null);
room.addExtendedProps("department", "Engineering");
scheduler.addResource(room);
// Later — update the extended prop
room.addExtendedProps("department", "Marketing");
scheduler.updateResource(room);
// Before 7.2: client still shows "Engineering"
// From 7.2: client reflects "Marketing"Resource properties that are not part of FC's built-in resource object (anything except title, eventColor, eventBackgroundColor, eventBorderColor, eventTextColor, eventConstraint, eventOverlap, eventAllow, eventClassNames) now route through FC's Resource.setExtendedProp(name, value) on update, matching the initial-creation behavior.
Previously, code that registered many resources one at a time — e.g. a for-loop calling scheduler.addResource(...) per entry — could leave the scrollgrid chunk tables stuck at style="width: 0px; min-width: 930px", rendering at min-width instead of filling the container. Users saw a large empty dead-zone to the right of the timeline.
Root cause: every addResource / removeResource / updateResource call on the server fired an immediate callJsFunction. N calls → N JS round-trips → N inner FC updates, which pushed FC's internal Preact tree past its double-rAF settling window; computeScrollerDims() then wrote width: 0 and nothing re-measured afterwards.
Fix: resource writes on the scheduler now accumulate in a per-request pending state and flush once in a beforeClientResponse callback. A request that calls addResource 100 times fires exactly one callJsFunction("addResources", [...100], ...) at the end of the request. FC sees one inner update and settles correctly.
Collapse rules within a single request:
-
addResource(R)followed byremoveResource(R)→ no client op (R was never sent). -
removeResource(R)followed byaddResource(R')(same id) → remove + re-add, in that order. -
updateResource(R)afteraddResource(R)→ collapses into the add (the add payload carries the latest state). -
removeAllResources()→ supersedes any earlier piecewise ops in the same request.
No API change: addResource, addResources(Iterable), removeResource, removeResources(Iterable), updateResource, and removeAllResources keep their existing signatures. The difference is purely in when the client is notified — at the end of the current request, not immediately.
Calling calendar.setOption(Option.EDITABLE, false) to disable drag'n'drop globally used to have no effect — entries still responded to drag. Reason: each Entry carried an implicit editable: true default that was serialized to the client, overriding the calendar-level setting.
Starting with 7.2, Entry.editable defaults to "not set" (internally null). An unset value is no longer sent to the client, so the calendar-level option takes effect. Explicit entry.setEditable(true) or setEditable(false) continue to work as before — they remain per-entry overrides.
// 7.2 — actually works
calendar.setOption(Option.EDITABLE, false);
calendar.setEntryProvider(EntryProvider.inMemoryFrom(someEntries));
// None of the entries are draggable.Source-level compatibility: entry.isEditable() still returns boolean with the same default (true), and entry.setEditable(true/false) still works (auto-boxing). setEditable(null) is newly allowed — it clears the explicit override and lets the entry inherit the calendar-level setting again.
Addon-default change: FullCalendar JS itself defaults the calendar-level editable option to false. With the per-entry override gone, that would have flipped the out-of-the-box UX to "nothing draggable or resizable". To preserve the pre-7.2 experience (entries are draggable by default unless the dev opts out), the addon now sets editable: true as a calendar-level default in every FullCalendar constructor. An explicit setOption(Option.EDITABLE, false) or a user-supplied initialOptions with editable: false continues to win.
Binary compatibility note: the setEditable method descriptor changed from (Z)V (primitive boolean) to (Ljava/lang/Boolean;)V (boxed). Pre-compiled dependents calling setEditable(...) must be recompiled against 7.2 before use. Source recompilation is the normal path when upgrading the addon, so this rarely matters in practice.