A flexible, extensible, and feature-rich timetable/scheduler component for Blazor. It enables you to display, create, update, delete, import/export, and drag-and-drop events in day/week/month views with customization support.
- Features
- Prerequisites
- Project Setup
- Minimal Setup
- Configuration
- Callbacks
- Additional Event Properties
- Properties and Parameters
- Acknowledgments
- License
- Day, Week, Month views
- Extensible Create, Edit with custom validation
- Single & Group events
- Drag & Drop support
- Import & Export functionality
- English and Czech language support
- Customization:
- Day Time Range
- Days & Months
- Extensible/Custom Import & Export Implementation
- .NET 8+ SDK and Runtime
- Blazor-friendly IDE like Visual Studio 2019+ or Visual Studio Code with C# extension:
This tutorial covers setting up a Blazor Server Interactive project.
To create a new Blazor project named BlazorTimetableExample, open a terminal and execute the following command:
dotnet new blazor -n BlazorTimetableExample
Next, navigate into the project's directory:
cd BlazorTimetableExample
Add the Blazor.Timetable
package either via NuGet Package Manager (Visual Studio):
Install-Package Blazor.Timetable
Or via .NET CLI:
dotnet add package Blazor.Timetable
In your Program.cs
, add necessary services to service container using AddBlazorTimetable
and ensure that static files can be used by invoking UseStaticFiles
(In .NET 9 use MapStaticAssets
).
using BlazorTimetableExample.Components;
using Blazor.Timetable.Common.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddBlazorTimetable(); // Add Blazor.Timetable services
var app = builder.Build();
app.UseAntiforgery();
app.UseStaticFiles(); // Enable static files for serving stylesheets
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
In the <head>
of your App.razor
file, add a link to the Blazor stylesheet if not already present:
<link rel="stylesheet" href="BlazorTimetableExample.styles.css"/>
For .NET 9:
<link rel="stylesheet" href="@Assets["BlazorTimetableExample.styles.css"]" />
This section provides a step-by-step guide to integrating the Timetable
component into your Blazor Server project.
Navigate to the Home.razor
component, or another suitable component of your choice, which will act as the parent for the Timetable
component.
This is where you will define the timetable and manage events.
Define an Appointment
class anywhere in your project to represent events.
This model must define properties for the title (string
), start and end of the event (DateTime
) and nullable group identifier (string?
):
public class Appointment
{
public string Title { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string? GroupId { get; set; }
// Include any additional properties as needed
}
Inside the selected component, add @code
block to initialize a list for Appointment
objects.
This list will be bound to the Timetable
component for interactive event management.
Ensure the collection passed to the Timetable
is of type IList<TEvent>
, where TEvent
is your event model, in this case, Appointment
.
@code {
public IList<Appointment> Appointments { get; set; } = new List<Appointment>();
}
Within your component, use the @rendermode InteractiveServer
directive to enable interactivity.
Then, implement the Timetable
component, passing in the necessary parameters:
TEvent
: Type of event (Appointment
)@bind-Events
: List of event objects (Appointments
)DateFrom
andDateTo
: Start and end time (e => e.From
ande => e.To
)Title
: Title property of each event displayed in the grid (e => e.Title
)GroupId
: Identifier to allow grouping of same events e.g. (e => e.GroupId
)
@page "/"
@using Blazor.Timetable.Components
@rendermode InteractiveServer
<Timetable TEvent=Appointment
@bind-Events=Appointments
DateFrom="e => e.From"
DateTo="e => e.To"
Title="e => e.Title"
GroupId="e => e.GroupId" />
The complete code block of Home.razor
should resemble the following:
@page "/"
@using Blazor.Timetable.Components
@rendermode InteractiveServer
<Timetable TEvent=Appointment
@bind-Events=Appointments
DateFrom="e => e.From"
DateTo="e => e.To"
Title="e => e.Title"
GroupId="e => e.GroupId" />
@code {
public IList<Appointment> Appointments { get; set; } = new List<Appointment>();
public class Appointment
{
public string Title { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string? GroupId { get; set; }
}
}
The project is now ready to run. You can start the application using the following command:
dotnet run
The library offers three configuration classes that allow you to customize your timetable component. These classes can be passed as parameters to the component instance.
The available configuration classes are:
TimetableConfig
ExportConfig
ImportConfig
Defines display settings for the timetable.
Property | Type | Description |
---|---|---|
Months |
ICollection<Month> |
Specifies the months to display. Must be within a valid range and consecutive. Options: Month.January to Month.December . |
Days |
IList<DayOfWeek> |
Specifies which days to display. Must be consecutive. Options: DayOfWeek.Sunday to DayOfWeek.Saturday . |
TimeFrom |
TimeOnly |
Starting time, with hours from 0-23 and minutes in quarters (0, 15, 30, 45). |
TimeTo |
TimeOnly |
Ending time, must be greater than TimeFrom . Special case: 23:59 for end of day. |
Is24HourFormat |
bool |
Determines if the timetable uses a 24-hour time format. |
DefaultDisplayType |
DisplayType |
Sets the initial display view. Options: DisplayType.Day , DisplayType.Week , DisplayType.Month . |
Usage:
<Timetable TEvent=Appointment
@bind-Events=Appointments
DateFrom="e => e.From"
DateTo="e => e.To"
Title="e => e.Title"
GroupId="e => e.GroupId"
TimetableConfig="new TimetableConfig {
DefaultDisplayType = DisplayType.Week,
Days = new List<DayOfWeek> { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday,
DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday },
TimeFrom = new TimeOnly(8, 0),
TimeTo = new TimeOnly(16, 0),
Is24HourFormat = true
}" />
Defines settings for exporting timetable data.
Property | Type | Description |
---|---|---|
FileName |
string |
The file name without the extension. |
Transformer |
IExportTransformer |
Transformer used for exporting, default is CsvTransformer . You can supply your own implementation of IExportTransformer |
Selectors |
IList<ISelector<TEvent>> |
Defines the list of properties to be exported and how. |
Usage:
<Timetable TEvent=Appointment
@bind-Events=Appointments
DateFrom="e => e.From"
DateTo="e => e.To"
Title="e => e.Title"
GroupId="e => e.GroupId"
ExportConfig=@(new ExportConfig<Appointment>{
FileName = "FileName",
Transformer = new CsvTransformer(),
Selectors = new List<ISelector<Appointment>> {
new Selector<Appointment, DateTime>("DateFrom", e => e.From),
new Selector<Appointment, DateTime>("DateTo", e => e.To),
new Selector<Appointment, string>("Title", e => e.Title!),
new Selector<Appointment, string>("GroupIdentifier", e => e.GroupId)
}
}) />
You can supply toStringConverter
argument with method to format the value into its string
.
For example converting the DateTo
to specific format:
new Selector<TEvent, DateTime>("DateTo", e => e.To, toStringConverter: e => e.ToString("dd__MM__yyyy"))
Defines settings for importing timetable data.
Property | Type | Description |
---|---|---|
AllowedExtensions |
string[] |
Allowed file extensions. Note that they must be supported by your transformer |
MaxFileSizeBytes |
long |
Maximum allowed file size in bytes. |
Transformer |
IImportTransformer |
Transformer used for importing, default is CsvImportTransformer . |
Selectors |
IList<ISelector<TEvent>> |
Defines properties to be imported and how. |
Usage:
<Timetable TEvent=Appointment
@bind-Events=Appointments
DateFrom="e => e.From"
DateTo="e => e.To"
Title="e => e.Title"
GroupId="e => e.GroupId"
ImportConfig=@(new ImportConfig<Appointment>{
AllowedExtensions = new string[]{ "csv", "txt" },
MaxFileSizeBytes = 10_000_000,
Transformer = new CsvImportTransformer(),
Selectors = new List<ISelector<Appointment>> {
new Selector<Appointment, DateTime>("DateFrom", e => e.From),
new Selector<Appointment, DateTime>("DateTo", e => e.To),
new Selector<Appointment, string>("Title", e => e.Title!),
new Selector<Appointment, string>("GroupIdentifier", e => e.GroupId)
}
}) />
You can supply parser
argument with method to format the value from the string to its property datatype.
For example converting string to the date time:
new Selector<TEvent, DateTime>("DateTo", DateTo, parser: raw => DateTime.ParseExact(raw, "dd__MM__yyyy", CultureInfo.InvariantCulture))
The library supports English and Czech localizations with default English.
Localization must be applied globally when adding services to the service container.
Use the Localize
method chained onto AddBlazorTimetable
:
builder.Services.AddBlazorTimetable().Localize(Language.Czech);
Library exposes several callbacks parameters that can be specified to respond to user interactions or state changes. Here are the available callbacks:
OnPreviousClicked
: Triggered when the user navigates to the previous time period.OnNextClicked
: Triggered when the user navigates to the next time period.OnDisplayTypeChanged
: Invoked when the display type is changed. Provides the new display type.OnChangedToDay
: Invoked when the mode changes to a specific day. Provides the day of week.
OnEventCreated
: Triggered when an event is created. Provides the created event instance.OnGroupEventCreated
: Triggered when a group of events is created. Provides the list of created events.OnEventChanged
: Triggered when an event is updated. Provides the updated event instance.OnGroupEventChanged
: Triggered when a group of events is updated. Provides the list of updated events.OnEventDeleted
: Invoked when an event is deleted. Provides the deleted event instance.OnGroupEventDeleted
: Invoked when a group of events is deleted. Provides the list of deleted events.
Example usage:
<Timetable TEvent=Appointment
@bind-Events=Appointments
DateFrom="e => e.From"
DateTo="e => e.To"
Title="e => e.Title"
GroupId="e => e.GroupId"
OnEventChanged=HandleEventChanged />
@code{
public void HandleEventChanged(Appointment appointment){
// TODO: Update the appointment in the database or other action
}
}
In scenarios where events have limited occupancy or require additional custom attributes, our system allows for the integration of these properties during the creation or update of events.
-
Add Selector to AdditionalProps:
- Each additional property should have its selector specified and passed to the Timetable component via the AdditionalProps parameter.
-
Specify UI in AdditionalFields:
- Use the AdditionalFields RenderFragment to incorporate the required input components for your additional properties. Predefined input components are available for integration, or you can implement custom input components if needed.
These components facilitate the integration with the library, allowing for input, modification, and validation of events. Below are the predefined components available:
Component Name | Data Type | Description |
---|---|---|
Input | string | Text input |
InputDateTime | DateTime | Date and time input |
Dropdown | T | Generic dropdown selector |
Example Implementation:
For instance, if you have a property Description in the Subject class, its selector can be added to AdditionalProps. The AdditionalFields RenderFragment would then include an instance of the Input component with the relevant parameters. The context object, which represents the current event instance, is passed as the Model to this component to enable binding of its label.
<Timetable @bind-Events=Subjects
TEvent=Subject
Title="e => e.Name"
DateFrom="e => e.StartTime"
DateTo="e => e.EndTime"
GroupId="e => e.GroupId"
AdditionalProps=@([x => x.Description])>
<AdditionalFields>
<Input Model="@context"
Label="Description"
Selector="x => x.Description" />
</AdditionalFields>
</Timetable>
Validation is performed via the Validate
parameter on the input component, where you can specify a method to be executed when the property's value changes and before the event is saved.
This method must accept a data type corresponding to the property and return a nullable string string?
with an error message for the UI if the value is invalid, or null
if the value is valid.
<Timetable @bind-Events=Subjects
TEvent=Subject
Title="e => e.Name"
DateFrom="e => e.StartTime"
DateTo="e => e.EndTime"
GroupId="e => e.GroupId"
AdditionalProps=@([x => x.Description])>
<AdditionalFields>
<Input Model="@context"
Label="Description"
Selector="x => x.Description"
Validate="ValidationExample" />
</AdditionalFields>
</Timetable>
@code{
private string? ValidationExample(string? value)
{
if (string.IsNullOrEmpty(value))
return "Cannot be empty";
if (value.Length > 100)
return "Maximum length is 100";
return null; // No error -> valid
}
}
If the predefined components do not provide the required functionality, you can implement custom components.
These must inherit from the BaseInput
class to utilize the predefined interface for property assignment and validation upon value change.
Name | Type | Description |
---|---|---|
Id |
Guid |
A unique identifier for the instance. |
ErrorMessage |
string? |
A message indicating validation errors retrieved from the Validate, if any. |
HasError |
bool |
Indicates if there is a validation error e.g. if ErrorMessage is null or not. |
For example to create a custom input component for int
datatype:
@using Blazor.Timetable.Components.Shared.Forms
@inherits BaseInput<Appointment, int?>
<div>
<input type="number" @bind=BindProperty id=@Id />
<label for=@Id>@Label</label>
</div>
@if (HasError)
{
<span class="error">@ErrorMessage</span>
}
Now you can use this custom input component in the AdditionalFields
section of the Timetable
component.
For example to allow supplying of Maximum Occupancy on the event:
<Timetable @bind-Events=Subjects
TEvent=Subject
Title="e => e.Name"
DateFrom="e => e.StartTime"
DateTo="e => e.EndTime"
GroupId="e => e.GroupId"
AdditionalProps=@([x => x.MaxOccupancy])>
<AdditionalFields>
<Input Model="@context"
Label="Maximum Occupancy"
Selector="x => x.MaxOccupancy"
Validate="ValidationExample" />
</AdditionalFields>
</Timetable>
@code{
private string? ValidationExample(int? value)
{
if (value <= 0)
return "At least 1";
if (value > 100)
return "Maximum is 100";
return null;
}
public class Appointment
{
public string Title { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string? GroupId { get; set; }
public int MaxOccupancy { get; set; }
}
}
This project uses the following open-source resources:
- Tabler Icons: A set of free and open-source icons.
- Interact.js: A JavaScript library for drag and drop, resizing, and multi-touch gestures.
Distributed under the MIT License. See LICENSE for details.