diff --git a/blazor-toc.html b/blazor-toc.html index a8f7d7fd7f..4498cf4f18 100644 --- a/blazor-toc.html +++ b/blazor-toc.html @@ -3162,6 +3162,7 @@
  • Critical Path
  • +
  • Undo Redo
  • Toolbar
  • Managing Tasks
  • diff --git a/blazor/gantt-chart/accessibility.md b/blazor/gantt-chart/accessibility.md index 6e83cf71d5..85e95d092c 100644 --- a/blazor/gantt-chart/accessibility.md +++ b/blazor/gantt-chart/accessibility.md @@ -182,6 +182,15 @@ The Blazor Gantt Chart component supports comprehensive [keyboard interaction](h | Tab | Tab | Saves the current cell and moves to the next editable cell in the dialog. | | Shift + Tab | + Tab | Saves the current cell and moves to the previous editable cell in the dialog. | +### Undo Redo + +| Windows | Mac | Action | +|---------|-----|--------| +| Ctrl + Z | + Z | Undoes the most recent tracked action. | +| Ctrl + Y | + Y | Redoes the most recently undone action. | +| Ctrl + Shift + Z | + + Z | Redoes the most recently undone action. | +| Ctrl + Shift + Y | + + Y | Undoes the most recent tracked action. | + ## Validate Accessibility Compliance Accessibility is validated using [axe-core](https://www.nuget.org/packages/Deque.AxeCore.Playwright) with Playwright tests to ensure compliance with WCAG 2.2 and other standards. Evaluate the accessibility of the Blazor Gantt Chart component using the [sample](https://blazor.syncfusion.com/accessibility/gantt-chart) in a new window with accessibility tools. diff --git a/blazor/gantt-chart/column-validation.md b/blazor/gantt-chart/column-validation.md new file mode 100644 index 0000000000..e3c181d982 --- /dev/null +++ b/blazor/gantt-chart/column-validation.md @@ -0,0 +1,541 @@ +--- +layout: post +title: Column Validation in Blazor Gantt Chart Component | Syncfusion +description: Learn to configure built-in and custom column validation in Syncfusion Blazor Gantt Chart, including validation rules, data annotations, and custom validators. +platform: Blazor +control: Gantt Chart +documentation: ug +--- + +# Column Validation in Blazor Gantt Chart + +Column validation ensures that edited or newly added row data meets defined criteria before it is saved. This support is useful for enforcing rules or constraints on individual columns to maintain data integrity. When validation rules are applied, error messages are displayed for invalid fields, and saving is prevented until all validations pass. + +The Syncfusion® Blazor Gantt Chart component uses the Form Validator library to perform column validation. Validation rules can be specified using the [GanttColumn.ValidationRules](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttColumn.html#Syncfusion_Blazor_Gantt_GanttColumn_ValidationRules) property to define criteria for validating column values. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt + + + + + + + + + + + + + + + + + + +@code { + private List TaskCollection { get; set; } + private SfGantt Gantt; + protected override void OnInitialized() + { + this.TaskCollection = EditingData().ToList(); + } + public class TaskInfoModel + { + public int TaskID { get; set; } + public string? ActivityName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime EndDate { get; set; } + public int? Duration { get; set; } + public int Progress { get; set; } + public int? ParentID { get; set; } + } + public static List EditingData() + { + List Tasks = new List() { + new TaskInfoModel() { TaskID = 1, ActivityName = "Product concept", StartDate = new DateTime(2021, 04, 02), Duration = 5, Progress = 60, ParentID = null }, + new TaskInfoModel() { TaskID = 2, ActivityName = "Defining the product usage", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 70, ParentID = 1 }, + new TaskInfoModel() { TaskID = 3, ActivityName = "Defining the target audience", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 80, ParentID = 1 }, + new TaskInfoModel() { TaskID = 4, ActivityName = "Prepare product sketch and notes", StartDate = new DateTime(2021, 04, 05), Duration = 2, Progress = 90, ParentID = 1 }, + new TaskInfoModel() { TaskID = 5, ActivityName = "Concept approval", StartDate = new DateTime(2021, 04, 08), Duration = 0, Progress = 100, ParentID = 1 }, + new TaskInfoModel() { TaskID = 6, ActivityName = "Market research", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 30, ParentID = null }, + new TaskInfoModel() { TaskID = 7, ActivityName = "Demand analysis", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 40, ParentID = 6 } + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/BjheWLChfJEBegzY?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +> Validation is not supported for the [Resource](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttResourceColumn.html) column. + +## Data annotation + +The Syncfusion® Blazor Gantt Chart component supports data annotation validation attributes to validate fields in the underlying data model during add and edit operations. These attributes provide a declarative approach to enforce rules directly on model properties, ensuring data integrity without requiring additional validation logic. + +**Applying data annotation** + +* Add validation attributes to the model class properties that are bound to the Gantt Chart. +* Validation messages are displayed using the built-in tooltip in the Gantt Chart. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt +@using System.ComponentModel.DataAnnotations + + + + + + + + + + + + + + + + + + +@code { + private List TaskCollection { get; set; } + private SfGantt Gantt; + protected override void OnInitialized() + { + this.TaskCollection = EditingData().ToList(); + } + public class TaskInfoModel + { + public int TaskID { get; set; } + [Required(ErrorMessage = "ActivityName is required")] + [StringLength(50, MinimumLength = 5, ErrorMessage = "ActivityName must be between 5 and 50 characters")] + public string? ActivityName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int? Duration { get; set; } + [Range(0, 100, ErrorMessage = "Progress must be between 0 and 100")] + public int Progress { get; set; } + public int? ParentID { get; set; } + } + public static List EditingData() + { + List Tasks = new List() { + new TaskInfoModel() { TaskID = 1, ActivityName = "Product concept", StartDate = new DateTime(2021, 04, 02), Duration = 5, Progress = 60, ParentID = null }, + new TaskInfoModel() { TaskID = 2, ActivityName = "Defining the product usage", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 70, ParentID = 1 }, + new TaskInfoModel() { TaskID = 3, ActivityName = "Defining the target audience", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 80, ParentID = 1 }, + new TaskInfoModel() { TaskID = 4, ActivityName = "Prepare product sketch and notes", StartDate = new DateTime(2021, 04, 05), Duration = 2, Progress = 90, ParentID = 1 }, + new TaskInfoModel() { TaskID = 5, ActivityName = "Concept approval", StartDate = new DateTime(2021, 04, 08), Duration = 0, Progress = 100, ParentID = 1 }, + new TaskInfoModel() { TaskID = 6, ActivityName = "Market research", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 30, ParentID = null }, + new TaskInfoModel() { TaskID = 7, ActivityName = "Demand analysis", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 40, ParentID = 6 } + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/BjLoWLWLpyjcQTAD?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +## Custom validation + +The Syncfusion® Blazor Gantt Chart component supports custom validation for scenarios where built-in rules or data annotations do not meet specific requirements. This option is useful for enforcing business-specific constraints, performing dependent-field checks, or applying conditional validations during add and edit operations. + +**Implementing custom validation** + +* Create a class that inherits from `ValidationAttribute` and override the `IsValid` method to include custom logic. +* Apply the custom attribute to the model property that requires validation. +* The Gantt Chart automatically enforces these rules during add and edit operations. + +The following example demonstrates how to implement custom validation for the **ActivityName** and **Progress** fields. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt +@using System.ComponentModel.DataAnnotations + + + + + + + + + + + + + + + + + + +@code { + private List TaskCollection { get; set; } + private SfGantt Gantt; + protected override void OnInitialized() + { + this.TaskCollection = EditingData().ToList(); + } + public class TaskInfoModel + { + public int TaskID { get; set; } + [CustomValidationActivityName] + public string? ActivityName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int? Duration { get; set; } + [CustomValidationProgress] + public int Progress { get; set; } + public int? ParentID { get; set; } + } + + /// + /// Provides custom validation for the ActivityName property. + /// Ensures that the task name is not empty and its length is between 5 and 10 characters. + /// + public class CustomValidationActivityName : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var str = value as string; + if (string.IsNullOrWhiteSpace(str)) + return new ValidationResult("Task Name is required."); + if (str.Length < 5 || str.Length > 10) + return new ValidationResult("Task Name must be between 5 and 10 characters."); + return ValidationResult.Success; + } + } + + /// + /// Provides custom validation for the Progress property. + /// Ensures that the progress value is provided and falls within the range of 5 to 50. + /// + public class CustomValidationProgress : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext context) + { + if (value == null) + return new ValidationResult("Progress is required."); + var v = (int)value; + if (v < 5) + return new ValidationResult("Progress must be greater than 5"); + if (v > 50) + return new ValidationResult("Progress must be lesser than 50"); + return ValidationResult.Success; + } + } + public static List EditingData() + { + List Tasks = new List() { + new TaskInfoModel() { TaskID = 1, ActivityName = "Product concept", StartDate = new DateTime(2021, 04, 02), Duration = 5, Progress = 60, ParentID = null }, + new TaskInfoModel() { TaskID = 2, ActivityName = "Defining the product usage", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 70, ParentID = 1 }, + new TaskInfoModel() { TaskID = 3, ActivityName = "Defining the target audience", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 80, ParentID = 1 }, + new TaskInfoModel() { TaskID = 4, ActivityName = "Prepare product sketch and notes", StartDate = new DateTime(2021, 04, 05), Duration = 2, Progress = 90, ParentID = 1 }, + new TaskInfoModel() { TaskID = 5, ActivityName = "Concept approval", StartDate = new DateTime(2021, 04, 08), Duration = 0, Progress = 100, ParentID = 1 }, + new TaskInfoModel() { TaskID = 6, ActivityName = "Market research", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 30, ParentID = null }, + new TaskInfoModel() { TaskID = 7, ActivityName = "Demand analysis", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 40, ParentID = 6 } + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/rXreihChTSiAmHFW?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +## Custom validator component + +The Syncfusion® Blazor Gantt Chart component supports custom validator components for scenarios that require validation beyond built-in [ValidationRules](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttColumn.html#Syncfusion_Blazor_Gantt_GanttColumn_ValidationRules) and `ValidationAttribute` classes. This approach allows overriding the default validation logic and implementing complex, application-specific rules. + +**Injecting a custom validator** + +A custom validator component can be injected into the internal EditForm of the Gantt Chart using the [Validator](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html#Syncfusion_Blazor_Gantt_GanttEditSettings_Validator) property of [GanttEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html). Inside the validator, the current row's data and the edit context can be accessed through the implicit parameter context of type [ValidatorTemplateContext](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.ValidatorTemplateContext.html). This enables form-level checks during add and edit operations. + +For details on creating a form validator component, refer to [ASP.NET Core Blazor Validator Components](https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/?view=aspnetcore-8.0#validator-components). + +**Example: Implementing a custom validator** + +In the following example: + +* A custom validator component named **GanttCustomValidator** accepts [ValidatorTemplateContext](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.ValidatorTemplateContext.html) as a parameter. + +* The [GanttEditSettings.Validator](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html#Syncfusion_Blazor_Gantt_GanttEditSettings_Validator) property is used to inject the validator into the internal EditForm. + +* The validator checks **TaskID** and **ActivityName** fields and displays per-field messages. + +* Errors are displayed using the built-in validation tooltip via the [ValidatorTemplateContext.ShowValidationMessage(fieldName, isValid, message)](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.ValidatorTemplateContext.html#Syncfusion_Blazor_Grids_ValidatorTemplateContext_ShowValidationMessage) method. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt +@using Syncfusion.Blazor.Grids +@using System.ComponentModel.DataAnnotations; +@using ColumnValidationComponents + + + + + + @{ + ValidatorTemplateContext txt = context as ValidatorTemplateContext; + } + + + + + + + + + + + + + + + + + +@code { + private List TaskCollection { get; set; } + protected override void OnInitialized() + { + this.TaskCollection = GanttData.EditingData(); + } +} + +{% endhighlight %} +{% endtabs %} + +{% tabs %} +{% highlight c# %} + +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +namespace ColumnValidationComponents +{ + public class GanttData + { + public class TaskInfoModel + { + public int TaskID { get; set; } + public string ActivityName { get; set; } = string.Empty; + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int? Duration { get; set; } + public string Predecessor { get; set; } = string.Empty; + public int Progress { get; set; } + public int? ParentID { get; set; } + } + public static List EditingData() + { + List Tasks = new List() { + new TaskInfoModel() { TaskID = 1, ActivityName = "Product concept", StartDate = new DateTime(2021, 04, 02), Duration = 5, Progress = 60, ParentID = null }, + new TaskInfoModel() { TaskID = 2, ActivityName = "Defining the product usage", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 70, ParentID = 1 }, + new TaskInfoModel() { TaskID = 3, ActivityName = "Defining the target audience", StartDate = new DateTime(2021, 04, 02), Duration = 3, Progress = 80, ParentID = 1 }, + new TaskInfoModel() { TaskID = 4, ActivityName = "Prepare product sketch and notes", StartDate = new DateTime(2021, 04, 05), Duration = 2, Progress = 90, ParentID = 1 }, + new TaskInfoModel() { TaskID = 5, ActivityName = "Concept approval", StartDate = new DateTime(2021, 04, 08), Duration = 0, Progress = 100, ParentID = 1 }, + new TaskInfoModel() { TaskID = 6, ActivityName = "Market research", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 30, ParentID = null }, + new TaskInfoModel() { TaskID = 7, ActivityName = "Demand analysis", StartDate = new DateTime(2021, 04, 09), Duration = 4, Progress = 40, ParentID = 6 } + }; + return Tasks; + } + } +} + +{% endhighlight %} +{% endtabs %} + +{% tabs %} +{% highlight c# %} + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Syncfusion.Blazor.Gantt; +using Syncfusion.Blazor.Grids; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; +using System.Dynamic; +using System.Collections.Generic; + +namespace ColumnValidationComponents +{ + public class GanttCustomValidator : ComponentBase, IDisposable + { + [Parameter] public ValidatorTemplateContext context { get; set; } + [CascadingParameter] private EditContext CurrentEditContext { get; set; } + private ValidationMessageStore _messageStore; + private static readonly DateTime MinDate2020 = new DateTime(2020, 1, 1); + private static DateTime MaxDateToday => DateTime.Today; + private static readonly DateTime MaxDate2030 = new DateTime(2030, 12, 31); + + protected override void OnInitialized() + { + if (CurrentEditContext is null) + { + throw new InvalidOperationException($"{nameof(GanttCustomValidator)} requires a cascading EditContext."); + } + _messageStore = new ValidationMessageStore(CurrentEditContext); + CurrentEditContext.OnValidationRequested += ValidateRequested; + CurrentEditContext.OnFieldChanged += ValidateField; + } + + /// + /// Adds a validation error message for a given field and notifies the Syncfusion validator template + /// to display the message within the corresponding column. + /// + private void AddError(FieldIdentifier id, string message) + { + _messageStore.Add(id, message); + context?.ShowValidationMessage(id.FieldName, false, message); + } + + /// + /// Clears validation messages for a given field and notifies the Syncfusion validator template + /// to remove any message shown for the corresponding column. + /// + private void ClearField(FieldIdentifier id) + { + _messageStore.Clear(id); + context?.ShowValidationMessage(id.FieldName, true, null); + } + + /// + /// Executes field-level validation for the provided . + /// Currently validates TaskID and ActivityName of the . + /// Other fields will be cleared by default. + /// + private void HandleValidation(FieldIdentifier identifier) + { + if (identifier.Model is GanttData.TaskInfoModel TaskInfoModel) + { + _messageStore.Clear(identifier); + switch (identifier.FieldName) + { + case nameof(GanttData.TaskInfoModel.TaskID): + if (TaskInfoModel.TaskID <= 0) + AddError(identifier, "Task ID is required."); + else + ClearField(identifier); + break; + + case nameof(GanttData.TaskInfoModel.ActivityName): + if (string.IsNullOrWhiteSpace(TaskInfoModel.ActivityName)) + AddError(identifier, "Task Name is required."); + else if (TaskInfoModel.ActivityName.Length < 5 || TaskInfoModel.ActivityName.Length > 10) + AddError(identifier, "Task Name must be between 5 and 10 characters."); + else + ClearField(identifier); + break; + default: + ClearField(identifier); + break; + } + return; + } + ClearField(identifier); + } + + /// + /// Handles per-field validation when a field changes in the edit form + /// by delegating to . + /// + private void ValidateField(object sender, FieldChangedEventArgs e) + { + HandleValidation(e.FieldIdentifier); + } + + /// + /// Performs form-level validation when validation is requested (e.g., submit or explicit validation). + /// Iterates predefined fields and validates each via . + /// + private void ValidateRequested(object sender, ValidationRequestedEventArgs e) + { + _messageStore.Clear(); + string[] fieldsToValidate = new[] + { + nameof(GanttData.TaskInfoModel.TaskID), + nameof(GanttData.TaskInfoModel.ActivityName), + nameof(GanttData.TaskInfoModel.Progress), + nameof(GanttData.TaskInfoModel.StartDate), + nameof(GanttData.TaskInfoModel.EndDate), + }; + foreach (var field in fieldsToValidate) + { + HandleValidation(CurrentEditContext.Field(field)); + } + } + + /// + /// Unsubscribes event handlers from the to prevent memory leaks and cleans up resources. + /// + public void Dispose() + { + if (CurrentEditContext is not null) + { + CurrentEditContext.OnValidationRequested -= ValidateRequested; + CurrentEditContext.OnFieldChanged -= ValidateField; + } + } + } +} + +{% endhighlight %} +{% endtabs %} + +## See also +- [How to define columns manually in Blazor Gantt Chart?](https://blazor.syncfusion.com/documentation/gantt-chart/columns#defining-columns) +- [How to use column templates in Blazor Gantt Chart?](https://blazor.syncfusion.com/documentation/gantt-chart/column-template) \ No newline at end of file diff --git a/blazor/gantt-chart/events.md b/blazor/gantt-chart/events.md index 9749f52751..02ada0379c 100644 --- a/blazor/gantt-chart/events.md +++ b/blazor/gantt-chart/events.md @@ -1,13 +1,13 @@ --- layout: post title: Events in Blazor Gantt Chart Component | Syncfusion -description: Checkout and learn here all about Events in Syncfusion Blazor Gantt Chart component and much more details. +description: Checkout and learn here all about events in Syncfusion Blazor Gantt Chart component and much more details. platform: Blazor control: Gantt Chart documentation: ug --- -# Events in Blazor GanttChart Component +# Events in Blazor Gantt Chart Component In this section, the list of events of the Gantt Chart component has been provided which will be triggered for appropriate Gantt Chart actions. @@ -3812,7 +3812,7 @@ The events should be provided to the Gantt Chart using the GanttChartEvents comp ## IndentationChanging -[IndentationChanging](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEvents-1.html#Syncfusion_Blazor_Gantt_GanttEvents_1_IndentationChanging) event triggers before an indent or outdent action is performed in the Gantt Chart. The [IsIndent](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.IndentationChangingEventArgs-1.html#Syncfusion_Blazor_Gantt_IndentationChangingEventArgs_1_IsIndent) property of this event argument determines the type of indentation (indent or outdent). The following sample code demonstrates how to cancel the outdent action based on the `IsIndent` property. +[IndentationChanging](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEvents-1.html#Syncfusion_Blazor_Gantt_GanttEvents_1_IndentationChanging) event triggers before an indent or outdent action is performed in the Gantt Chart. The `IsIndent` property of this event argument determines the type of indentation (indent or outdent). The following sample code demonstrates how to cancel the outdent action based on the `IsIndent` property. ```cshtml @using Syncfusion.Blazor.Gantt @@ -3871,7 +3871,7 @@ The events should be provided to the Gantt Chart using the GanttChartEvents comp ## IndentationChanged -[IndentationChanged](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEvents-1.html#Syncfusion_Blazor_Gantt_GanttEvents_1_IndentationChanged) event triggers after an indent or outdent action is performed in the Gantt Chart. From the event argument, details about the indent or outdent action performed can be obtained using the [IsIndent](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.IndentationChangedEventArgs-1.html#Syncfusion_Blazor_Gantt_IndentationChangedEventArgs_1_IsIndent) property. The following sample demonstrates how to determine whether the performed action is an indent or outdent based on the `IsIndent` property. +[IndentationChanged](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEvents-1.html#Syncfusion_Blazor_Gantt_GanttEvents_1_IndentationChanged) event triggers after an indent or outdent action is performed in the Gantt Chart. From the event argument, details about the indent or outdent action performed can be obtained using the `IsIndent` property. The following sample demonstrates how to determine whether the performed action is an indent or outdent based on the `IsIndent` property. ```cshtml @using Syncfusion.Blazor.Gantt @@ -4550,5 +4550,62 @@ The [PdfQueryTaskbarInfo](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazo } } ``` +## OnUndoRedo +[OnUndoRedo](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEvents-1.html#Syncfusion_Blazor_Gantt_GanttEvents_1_OnUndoRedo) event is triggered after an undo or redo operation completes. Event arguments indicate the operation type (undo or redo), the action performed, and the affected data, including modified records, deleted records, and any added record references. -N> We are not going to limit Gantt Chart with these events, we will be adding new events in the future based on the user requests. If the event, you are looking for is not on the list, then request [here](https://www.syncfusion.com/feedback/blazor-components). +``` cshtml + +@using Syncfusion.Blazor.Gantt + + + + + + + + +@code { + private List TaskCollection { get; set; } + + protected override void OnInitialized() + { + TaskCollection = GetTaskCollection(); + } + + private void UndoRedoHandler(GanttUndoRedoEventArgs args) + { + // args.IsRedo indicates redo (true) or undo (false) + // args.Action indicates the action type (e.g., Edit, Add, Delete, Sort) + // args.ModifiedRecords contains modified records, if any + // args.DeletedRecords contains deleted records, if any + // args.AddRecord contains the added record, if present + } + + public class TaskData + { + public int TaskId { get; set; } + public string TaskName { get; set; } = string.Empty; + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string? Duration { get; set; } + public int Progress { get; set; } + public int? ParentId { get; set; } + } + + private static List GetTaskCollection() + { + return new List() + { + new TaskData { TaskId = 1, TaskName = "Project initiation", StartDate = new DateTime(2023,01,04), EndDate = new DateTime(2023,01,23) }, + new TaskData { TaskId = 2, TaskName = "Identify site location", StartDate = new DateTime(2023,01,04), Duration = "0", Progress = 30, ParentId = 1 }, + new TaskData { TaskId = 3, TaskName = "Perform soil test", StartDate = new DateTime(2023,01,04), Duration = "4", Progress = 40, ParentId = 1 }, + new TaskData { TaskId = 4, TaskName = "Soil test approval", StartDate = new DateTime(2023,01,04), Duration = "0", Progress = 30, ParentId = 1 } + }; + } +} +``` + +N> We are not going to limit Gantt Chart with these events, we will be adding new events in the future based on the user requests. If the event, you are looking for is not on the list, then request [here](https://www.syncfusion.com/feedback/blazor-components). \ No newline at end of file diff --git a/blazor/gantt-chart/undo-redo.md b/blazor/gantt-chart/undo-redo.md new file mode 100644 index 0000000000..5ac1a38216 --- /dev/null +++ b/blazor/gantt-chart/undo-redo.md @@ -0,0 +1,353 @@ +--- +layout: post +title: Undo and Redo in Blazor Gantt Chart Component | Syncfusion +description: Learn how to enable, configure, and handle undo and redo actions in the Syncfusion Blazor Gantt Chart, including keyboard shortcuts and supported actions. +platform: Blazor +control: Gantt Chart +documentation: ug +--- + +# Undo and Redo in Blazor Gantt Chart Component + +The Syncfusion® Blazor Gantt Chart component includes built-in undo and redo functionality to revert or restore recent changes. This support improves editing efficiency, reduces errors, and supports quick recovery from accidental modifications. + +## Enable undo and redo + +The **Undo** in the Blazor Gantt Chart reverts the most recent action, such as modifications to tasks, dependencies, and other supported operations, while the **Redo** reapplies an action that was previously undone using the **Undo** option. This functionality can be enabled by setting the [EnableUndoRedo](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_EnableUndoRedo) property in the Gantt Chart component. When enabled, undo and redo operations can be performed using the built-in toolbar items, and the [OnUndoRedo](https://blazor.syncfusion.com/documentation/gantt-chart/events#onundoredo) event is triggered after each undo or redo operation is completed. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt + + + + + + + + + + + + + + +@code{ + public List TaskCollection { get; set; } = new(); + private List undoRedoActions = new List { + GanttUndoRedoAction.Sort, GanttUndoRedoAction.Add, GanttUndoRedoAction.ColumnReorder, GanttUndoRedoAction.TaskbarEdit, + GanttUndoRedoAction.ColumnState, GanttUndoRedoAction.Edit, GanttUndoRedoAction.Filter, GanttUndoRedoAction.NextTimeSpan, GanttUndoRedoAction.PreviousTimeSpan, GanttUndoRedoAction.Search,GanttUndoRedoAction.Delete, + GanttUndoRedoAction.ZoomIn, GanttUndoRedoAction.ZoomOut, GanttUndoRedoAction.ZoomToFit,GanttUndoRedoAction.Collapse,GanttUndoRedoAction.Expand,GanttUndoRedoAction.SplitterResize + }; + protected override void OnInitialized() + { + this.TaskCollection = GetUndoRedoData(); + } + public class TaskModel + { + public int TaskID { get; set; } + public string? TaskName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string? Duration { get; set; } + public int Progress { get; set; } + public string? Predecessor { get; set; } + public int? ParentID { get; set; } + } + public static List GetUndoRedoData() + { + List Tasks = new List + { + new TaskModel { TaskID = 1, TaskName = "Project initiation", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 02), Duration = "2", Progress = 100 }, + new TaskModel { TaskID = 2, TaskName = "Identify site location", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 03), Duration = "3", Progress = 100, ParentID = 1 }, + new TaskModel { TaskID = 3, TaskName = "Site analyze", StartDate = new DateTime(2025, 11, 02), EndDate = new DateTime(2025, 11, 03), Duration = "2", Progress = 90, ParentID = 1, }, + new TaskModel { TaskID = 4, TaskName = "Perform soil test", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 05), Duration = "3", Progress = 0, }, + new TaskModel { TaskID = 5, TaskName = "Soil test approval", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 04), Duration = "2", Progress = 0, ParentID = 4 }, + new TaskModel { TaskID = 6, TaskName = "Project estimation", StartDate = new DateTime(2025, 11, 05), EndDate = new DateTime(2025, 11, 05), Duration = "0", Progress = 0, ParentID = 4}, + new TaskModel { TaskID = 7, TaskName = "Develop floor plan for estimation", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 09), Duration = "4", Progress = 0}, + new TaskModel { TaskID = 8, TaskName = "List materials", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 07), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 9, TaskName = "Estimation approval", StartDate = new DateTime(2025, 11, 08), EndDate = new DateTime(2025, 11, 09), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 10, TaskName = "Building approval", StartDate = new DateTime(2025, 11, 10), EndDate = new DateTime(2025, 11, 16), Duration = "7", Progress = 0 }, + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/hZrIsLirBDkKyVlQ?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +> Define actions in the [UndoRedoActions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_UndoRedoActions) property to record them in the **Undo/Redo** history. Only actions defined in this property will be recorded. + +## Configure undo and redo actions + +By default, all supported actions are tracked for undo and redo operations. To restrict the actions that are recorded, such as including only edits and deletions, the [UndoRedoActions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_UndoRedoActions) property can be configured. + +The following table lists the built-in actions that can be included for undo and redo functionality: + +| Built-in Undo/Redo Items | Actions | +| ------------------------ | ------------------------------------------------------------------------------------------ | +| Edit | Restores changes made during record edits (cell or dialog). | +| Delete | Restores deleted records. | +| Add | Restores newly added records. | +| ColumnReorder | Restores column reorder operations. | +| Indent | Restores indent operations on records. | +| Outdent | Restores outdent operations on records. | +| ColumnResize | Restores column width changes. | +| Sort | Restores column sorting changes. | +| Filter | Restores applied or cleared filters. | +| Search | Restores applied or cleared search text. | +| ZoomIn | Restores zoom-in actions on the timeline. | +| ZoomOut | Restores zoom-out actions on the timeline. | +| ZoomToFit | Restores zoom-to-fit actions on the timeline. | +| ColumnState | Restores show/hide column visibility changes. | +| RowDragAndDrop | Restores row drag-and-drop reorder operations. | +| TaskbarDragAndDrop | Restores taskbar drag-and-drop operations. | +| PreviousTimeSpan | Restores navigation to the previous timespan. | +| NextTimeSpan | Restores navigation to the next timespan. | +| SplitterResize | Restores splitter position changes. | +| ColumnFreeze | Restores column freeze or unfreeze changes. | +| TaskbarEdit | Restores taskbar edits such as move, resize, progress update, and connector modifications. | +| Expand | Restores expand state changes on records. | +| Collapse | Restores collapse state changes on records. | + + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt + + + + + + + + + + + + + + +@code{ + public List TaskCollection { get; set; } = new(); + private List undoRedoActions = new List { GanttUndoRedoAction.Add, GanttUndoRedoAction.TaskbarEdit, + GanttUndoRedoAction.Edit, GanttUndoRedoAction.Filter, GanttUndoRedoAction.NextTimeSpan, GanttUndoRedoAction.PreviousTimeSpan,GanttUndoRedoAction.Delete, + GanttUndoRedoAction.ZoomIn, GanttUndoRedoAction.ZoomOut, GanttUndoRedoAction.ZoomToFit,GanttUndoRedoAction.Collapse,GanttUndoRedoAction.Expand, GanttUndoRedoAction.SplitterResize + }; + protected override void OnInitialized() + { + this.TaskCollection = GetUndoRedoData(); + } + public class TaskModel + { + public int TaskID { get; set; } + public string? TaskName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string? Duration { get; set; } + public int Progress { get; set; } + public string? Predecessor { get; set; } + public int? ParentID { get; set; } + } + public static List GetUndoRedoData() + { + List Tasks = new List + { + new TaskModel { TaskID = 1, TaskName = "Project initiation", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 02), Duration = "2", Progress = 100 }, + new TaskModel { TaskID = 2, TaskName = "Identify site location", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 03), Duration = "3", Progress = 100, ParentID = 1 }, + new TaskModel { TaskID = 3, TaskName = "Site analyze", StartDate = new DateTime(2025, 11, 02), EndDate = new DateTime(2025, 11, 03), Duration = "2", Progress = 90, ParentID = 1, }, + new TaskModel { TaskID = 4, TaskName = "Perform soil test", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 05), Duration = "3", Progress = 0, }, + new TaskModel { TaskID = 5, TaskName = "Soil test approval", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 04), Duration = "2", Progress = 0, ParentID = 4 }, + new TaskModel { TaskID = 6, TaskName = "Project estimation", StartDate = new DateTime(2025, 11, 05), EndDate = new DateTime(2025, 11, 05), Duration = "0", Progress = 0, ParentID = 4}, + new TaskModel { TaskID = 7, TaskName = "Develop floor plan for estimation", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 09), Duration = "4", Progress = 0}, + new TaskModel { TaskID = 8, TaskName = "List materials", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 07), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 9, TaskName = "Estimation approval", StartDate = new DateTime(2025, 11, 08), EndDate = new DateTime(2025, 11, 09), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 10, TaskName = "Building approval", StartDate = new DateTime(2025, 11, 10), EndDate = new DateTime(2025, 11, 16), Duration = "7", Progress = 0 }, + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/rjVoiBMLhDblevvL?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +## Configure undo redo step count + +The Syncfusion® Blazor Gantt Chart component provides an option to limit the number of undo and redo actions stored in the history. The number of stored history entries can be controlled using the [MaxUndoRedoSteps](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_MaxUndoRedoSteps) property. The default capacity is `20`. When the count exceeds this value, the oldest entry is discarded and the newest action is appended, ensuring consistent memory usage. + +The following example illustrates how to configure the maximum number of undo and redo steps. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt + + + + + + + + + + + + + + +@code{ + public List TaskCollection { get; set; } = new(); + private List undoRedoActions = new List { GanttUndoRedoAction.Add, GanttUndoRedoAction.TaskbarEdit, + GanttUndoRedoAction.Edit, GanttUndoRedoAction.Filter, GanttUndoRedoAction.NextTimeSpan, GanttUndoRedoAction.PreviousTimeSpan,GanttUndoRedoAction.Delete, + GanttUndoRedoAction.ZoomIn, GanttUndoRedoAction.ZoomOut, GanttUndoRedoAction.ZoomToFit,GanttUndoRedoAction.Collapse,GanttUndoRedoAction.Expand, GanttUndoRedoAction.SplitterResize + }; + protected override void OnInitialized() + { + this.TaskCollection = GetUndoRedoData(); + } + public class TaskModel + { + public int TaskID { get; set; } + public string? TaskName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string? Duration { get; set; } + public int Progress { get; set; } + public string? Predecessor { get; set; } + public int? ParentID { get; set; } + } + public static List GetUndoRedoData() + { + List Tasks = new List + { + new TaskModel { TaskID = 1, TaskName = "Project initiation", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 02), Duration = "2", Progress = 100 }, + new TaskModel { TaskID = 2, TaskName = "Identify site location", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 03), Duration = "3", Progress = 100, ParentID = 1 }, + new TaskModel { TaskID = 3, TaskName = "Site analyze", StartDate = new DateTime(2025, 11, 02), EndDate = new DateTime(2025, 11, 03), Duration = "2", Progress = 90, ParentID = 1, }, + new TaskModel { TaskID = 4, TaskName = "Perform soil test", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 05), Duration = "3", Progress = 0, }, + new TaskModel { TaskID = 5, TaskName = "Soil test approval", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 04), Duration = "2", Progress = 0, ParentID = 4 }, + new TaskModel { TaskID = 6, TaskName = "Project estimation", StartDate = new DateTime(2025, 11, 05), EndDate = new DateTime(2025, 11, 05), Duration = "0", Progress = 0, ParentID = 4}, + new TaskModel { TaskID = 7, TaskName = "Develop floor plan for estimation", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 09), Duration = "4", Progress = 0}, + new TaskModel { TaskID = 8, TaskName = "List materials", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 07), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 9, TaskName = "Estimation approval", StartDate = new DateTime(2025, 11, 08), EndDate = new DateTime(2025, 11, 09), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 10, TaskName = "Building approval", StartDate = new DateTime(2025, 11, 10), EndDate = new DateTime(2025, 11, 16), Duration = "7", Progress = 0 }, + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/BNrSMrMVVXPqZxza?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +## Performing undo and redo actions via methods + +The Blazor Gantt Chart supports performing undo and redo operations programmatically using the [UndoAsync](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_UndoAsync) and [RedoAsync](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_RedoAsync) methods. These methods provide an efficient way to revert or reapply changes through external controls. + +In the following example, clicking an external button invokes the `UndoAsync` method to revert the most recent change and the `RedoAsync` method to restore the previously undone action. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt +@using Syncfusion.Blazor.Buttons + +Undo +Redo + + + + + + + + + + + + + + +@code{ + private SfGantt GanttInstance; + public List TaskCollection { get; set; } = new(); + private List undoRedoActions = new List { GanttUndoRedoAction.Add, GanttUndoRedoAction.TaskbarEdit, + GanttUndoRedoAction.Edit, GanttUndoRedoAction.Filter, GanttUndoRedoAction.NextTimeSpan, GanttUndoRedoAction.PreviousTimeSpan,GanttUndoRedoAction.Delete, + GanttUndoRedoAction.ZoomIn, GanttUndoRedoAction.ZoomOut, GanttUndoRedoAction.ZoomToFit,GanttUndoRedoAction.Collapse,GanttUndoRedoAction.Expand, GanttUndoRedoAction.SplitterResize + }; + protected override void OnInitialized() + { + this.TaskCollection = GetUndoRedoData(); + } + + /// + /// Handles the undo action by invoking the Gantt Chart component's asynchronous undo logic. + /// + private async Task UndoHandler() + { + await GanttInstance.UndoAsync(); + } + + /// + /// Handles the redo action by invoking the Gantt Chart component's asynchronous redo logic. + /// + private async Task RedoHandler() + { + await GanttInstance.RedoAsync(); + } + public class TaskModel + { + public int TaskID { get; set; } + public string? TaskName { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string? Duration { get; set; } + public int Progress { get; set; } + public string? Predecessor { get; set; } + public int? ParentID { get; set; } + } + public static List GetUndoRedoData() + { + List Tasks = new List + { + new TaskModel { TaskID = 1, TaskName = "Project initiation", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 02), Duration = "2", Progress = 100 }, + new TaskModel { TaskID = 2, TaskName = "Identify site location", StartDate = new DateTime(2025, 11, 01), EndDate = new DateTime(2025, 11, 03), Duration = "3", Progress = 100, ParentID = 1 }, + new TaskModel { TaskID = 3, TaskName = "Site analyze", StartDate = new DateTime(2025, 11, 02), EndDate = new DateTime(2025, 11, 03), Duration = "2", Progress = 90, ParentID = 1, }, + new TaskModel { TaskID = 4, TaskName = "Perform soil test", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 05), Duration = "3", Progress = 0, }, + new TaskModel { TaskID = 5, TaskName = "Soil test approval", StartDate = new DateTime(2025, 11, 03), EndDate = new DateTime(2025, 11, 04), Duration = "2", Progress = 0, ParentID = 4 }, + new TaskModel { TaskID = 6, TaskName = "Project estimation", StartDate = new DateTime(2025, 11, 05), EndDate = new DateTime(2025, 11, 05), Duration = "0", Progress = 0, ParentID = 4}, + new TaskModel { TaskID = 7, TaskName = "Develop floor plan for estimation", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 09), Duration = "4", Progress = 0}, + new TaskModel { TaskID = 8, TaskName = "List materials", StartDate = new DateTime(2025, 11, 06), EndDate = new DateTime(2025, 11, 07), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 9, TaskName = "Estimation approval", StartDate = new DateTime(2025, 11, 08), EndDate = new DateTime(2025, 11, 09), Duration = "2", Progress = 0, ParentID = 7 }, + new TaskModel { TaskID = 10, TaskName = "Building approval", StartDate = new DateTime(2025, 11, 10), EndDate = new DateTime(2025, 11, 16), Duration = "7", Progress = 0 }, + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +{% previewsample "https://blazorplayground.syncfusion.com/embed/hDroWBCrUEUVmsZF?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +## See Also +- [How to add undo/redo events?](https://blazor.syncfusion.com/documentation/gantt-chart/events##onundoredo) +- [What are the keys used for undo/redo?](https://blazor.syncfusion.com/documentation/gantt-chart/accessibility#undo-redo) \ No newline at end of file