From 7e28b68bedf9c91414c0f9ab4f02b70464b6672f Mon Sep 17 00:00:00 2001 From: vendasankarsf3945 Date: Fri, 12 Dec 2025 12:55:23 +0530 Subject: [PATCH 1/3] 998236: Added undo redo and column validation functionalities in the UG. --- blazor/gantt-chart/accessibility.md | 9 + blazor/gantt-chart/column-validation.md | 591 ++++++++++++++++++++++++ blazor/gantt-chart/events.md | 57 +++ blazor/gantt-chart/undo-redo.md | 363 +++++++++++++++ 4 files changed, 1020 insertions(+) create mode 100644 blazor/gantt-chart/column-validation.md create mode 100644 blazor/gantt-chart/undo-redo.md 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..1afacd8c47 --- /dev/null +++ b/blazor/gantt-chart/column-validation.md @@ -0,0 +1,591 @@ +--- +layout: post +title: Column validation in Blazor Gantt Chart Component | Syncfusion +description: Learn how to configure built-in and custom column validation in the Syncfusion Blazor Gantt Chart component, including ValidationRules, DataAnnotations, and custom validator components with form-level checks. +platform: Blazor +control: Gantt Chart +documentation: ug +--- + +### Column validation in Blazor Gantt Chart + +Column validation allows validating edited or newly added row data before saving it. This feature is particularly useful for enforcing specific rules or constraints on individual columns to maintain data integrity. By applying validation rules to columns, error messages are displayed for invalid fields, and saving is prevented until all validations succeed. + +The Syncfusion® Blazor Gantt Chart component leverages the Form Validator library for column validation. Validation rules can be defined using the `GanttColumn.ValidationRules` property to specify 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 TaskData + { + public int TaskId { get; set; } + public string ActivityName { get; set; } + public int? Duration { get; set; } + public int Progress { get; set; } + public int? ParentId { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + } + public static List EditingData() + { + List Tasks = new List() { + new TaskData() { + TaskId = 1, + ActivityName = "Product concept", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 08), + Duration = 5, + Progress = 60, + ParentId = null, + }, + new TaskData() { + TaskId = 2, + ActivityName = "Defining the product usage", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 08), + Duration = 3, + Progress = 70, + ParentId = 1, + }, + new TaskData() { + TaskId = 3, + ActivityName = "Defining the target audience", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 04), + Duration = 3, + Progress = 80, + ParentId = 1, + }, + new TaskData() { + TaskId = 4, + ActivityName = "Prepare product sketch and notes", + StartDate = new DateTime(2021, 04, 05), + EndDate = new DateTime(2021, 04, 08), + Duration = 2, + Progress = 90, + ParentId = 1, + }, + new TaskData() { + TaskId = 5, + ActivityName = "Concept approval", + StartDate = new DateTime(2021, 04, 08), + EndDate = new DateTime(2021, 04, 08), + Duration= 0, + Progress = 100, + ParentId = 1, + }, + new TaskData() { + TaskId = 6, + ActivityName = "Market research", + StartDate = new DateTime(2021, 04, 09), + EndDate = new DateTime(2021, 04, 18), + Duration = 4, + Progress = 30, + ParentId = null, + }, + new TaskData() { + TaskId = 7, + ActivityName = "Demand analysis", + StartDate = new DateTime(2021, 04, 09), + EndDate = new DateTime(2021, 04, 12), + Duration = 4, + Progress = 40, + ParentId = 6, + }, + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +### Custom validation +Custom validation enables logic beyond built-in rules, including business constraints, dependent-field checks, and conditional validations during Add and Edit operations. + +## How it works + +## To implement custom validation in Blazor Gantt Chart: + +Create a class that inherits from `ValidationAttribute`. +Override the `IsValid` method to include custom logic. +Apply the custom attribute to the model property that needs validation. +The Gantt Chart will automatically enforce these rules during Add and Edit operations. + +The following sample code 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 TaskData + { + public int TaskId { get; set; } + [CustomValidationActivityName] + public string ActivityName { get; set; } + public int? Duration { get; set; } + [CustomValidationProgress] + public int Progress { get; set; } + public int? ParentId { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + } + 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; + } + } + 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 TaskData() { + TaskId = 1, + ActivityName = "Product concept", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 08), + Duration = 5, + Progress = 60, + ParentId = null, + }, + new TaskData() { + TaskId = 2, + ActivityName = "Defining the product usage", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 08), + Duration = 3, + Progress = 70, + ParentId = 1, + }, + new TaskData() { + TaskId = 3, + ActivityName = "Defining the target audience", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 04), + Duration = 3, + Progress = 80, + ParentId = 1, + }, + new TaskData() { + TaskId = 4, + ActivityName = "Prepare product sketch and notes", + StartDate = new DateTime(2021, 04, 05), + EndDate = new DateTime(2021, 04, 08), + Duration = 2, + Progress = 90, + ParentId = 1, + }, + new TaskData() { + TaskId = 5, + ActivityName = "Concept approval", + StartDate = new DateTime(2021, 04, 08), + EndDate = new DateTime(2021, 04, 08), + Duration= 0, + Progress = 100, + ParentId = 1, + }, + new TaskData() { + TaskId = 6, + ActivityName = "Market research", + StartDate = new DateTime(2021, 04, 09), + EndDate = new DateTime(2021, 04, 18), + Duration = 4, + Progress = 30, + ParentId = null, + }, + new TaskData() { + TaskId = 7, + ActivityName = "Demand analysis", + StartDate = new DateTime(2021, 04, 09), + EndDate = new DateTime(2021, 04, 12), + Duration = 4, + Progress = 40, + ParentId = 6, + }, + }; + return Tasks; + } +} + +{% endhighlight %} +{% endtabs %} + +### Custom validator component +Custom validator components provide flexible validation beyond built‑in ValidationRules and ValidationAttribute classes.There are scenarios where these options are not enough. +For example: Complex business logic +You may need to validate multiple fields together (e.g., EndDate must be after StartDate, or Duration must match Progress). + +## How does it work in Gantt Chart? + +The Syncfusion® Blazor Gantt Chart supports injecting a custom validator component into its internal EditForm using the `Validator` property of [GanttEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html). + +Inside the validator, you can access the current row’s data and the edit context via the implicit parameter context of type `ValidatorTemplateContext`. This enables form-level checks during Add/Edit operations. + +For creating a form validator component you can refer [here](https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/?view=aspnetcore-8.0#validator-components). + +In the below code example, the following things have been done. + +* A form validator component named GanttCustomValidator that accepts `ValidatorTemplateContext` as a parameter. +* Usage of `GanttEditSettings.Validator` to inject the validator into the internal EditForm. +* This validator component will checks for TaskId and ActivityName with per‑field messages. +* Display of errors using the built‑in validation tooltip via +`ValidatorTemplateContext.ShowValidationMessage(fieldName, isValid, message)` method. + +{% 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; + } + public void Dispose() + { + if (CurrentEditContext is not null) + { + CurrentEditContext.OnValidationRequested -= ValidateRequested; + CurrentEditContext.OnFieldChanged -= ValidateField; + } + } + + private void AddError(FieldIdentifier id, string message) + { + _messageStore.Add(id, message); + context?.ShowValidationMessage(id.FieldName, false, message); + } + + private void ClearField(FieldIdentifier id) + { + _messageStore.Clear(id); + context?.ShowValidationMessage(id.FieldName, true, null); + } + + private void HandleValidation(FieldIdentifier identifier) + { + if (identifier.Model is GanttData.TaskData taskdata) + { + _messageStore.Clear(identifier); + switch (identifier.FieldName) + { + case nameof(GanttData.TaskData.TaskId): + if (taskdata.TaskId <= 0) + AddError(identifier, "Task ID is required."); + else + ClearField(identifier); + break; + + case nameof(GanttData.TaskData.ActivityName): + if (string.IsNullOrWhiteSpace(taskdata.ActivityName)) + AddError(identifier, "Task Name is required."); + else if (taskdata.ActivityName.Length < 5 || taskdata.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); + } + private void ValidateField(object sender, FieldChangedEventArgs e) + { + HandleValidation(e.FieldIdentifier); + } + + private void ValidateRequested(object sender, ValidationRequestedEventArgs e) + { + _messageStore.Clear(); + string[] fieldsToValidate = new[] + { + nameof(GanttData.TaskData.TaskId), + nameof(GanttData.TaskData.ActivityName), + nameof(GanttData.TaskData.Progress), + nameof(GanttData.TaskData.StartDate), + nameof(GanttData.TaskData.EndDate), + }; + foreach (var field in fieldsToValidate) + { + HandleValidation(CurrentEditContext.Field(field)); + } + } + } +} + +{% endhighlight %} +{% endtabs %} + +{% 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 TaskData + { + public int TaskId { get; set; } + public string ActivityName { get; set; } = string.Empty; + public int? Duration { get; set; } + public string Predecessor { get; set; } = string.Empty; + public int Progress { get; set; } + public int? ParentId { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + } + public static List EditingData() + { + List Tasks = new List() { + new TaskData() { + TaskId = 1, + ActivityName = "Product concept", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 08), + Duration = 5, + Progress = 60, + ParentId = null, + }, + new TaskData() { + TaskId = 2, + ActivityName = "Defining the product usage", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 08), + Duration = 3, + Progress = 70, + ParentId = 1, + }, + new TaskData() { + TaskId = 3, + ActivityName = "Defining the target audience", + StartDate = new DateTime(2021, 04, 02), + EndDate = new DateTime(2021, 04, 04), + Duration = 3, + Progress = 80, + ParentId = 1, + }, + new TaskData() { + TaskId = 4, + ActivityName = "Prepare product sketch and notes", + StartDate = new DateTime(2021, 04, 05), + EndDate = new DateTime(2021, 04, 08), + Duration = 2, + Progress = 90, + ParentId = 1, + }, + new TaskData() { + TaskId = 5, + ActivityName = "Concept approval", + StartDate = new DateTime(2021, 04, 08), + EndDate = new DateTime(2021, 04, 08), + Duration= 0, + Progress = 100, + ParentId = 1, + }, + new TaskData() { + TaskId = 6, + ActivityName = "Market research", + StartDate = new DateTime(2021, 04, 09), + EndDate = new DateTime(2021, 04, 18), + Duration = 4, + Progress = 30, + ParentId = null, + }, + new TaskData() { + TaskId = 7, + ActivityName = "Demand analysis", + StartDate = new DateTime(2021, 04, 09), + EndDate = new DateTime(2021, 04, 12), + Duration = 4, + Progress = 40, + ParentId = 6, + }, + }; + return Tasks; + } + } +} + +{% endhighlight %} +{% endtabs %} + + + diff --git a/blazor/gantt-chart/events.md b/blazor/gantt-chart/events.md index 9749f52751..fa4472dace 100644 --- a/blazor/gantt-chart/events.md +++ b/blazor/gantt-chart/events.md @@ -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 triggers after an undo or redo operation completes. Event arguments provide the operation type (undo/redo), the action performed, and affected data such as modified records, deleted records, and an added record reference. + +``` 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). diff --git a/blazor/gantt-chart/undo-redo.md b/blazor/gantt-chart/undo-redo.md new file mode 100644 index 0000000000..cd98dfaacb --- /dev/null +++ b/blazor/gantt-chart/undo-redo.md @@ -0,0 +1,363 @@ +--- +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 component, including keyboard shortcuts and supported actions. +platform: Blazor +control: Gantt Chart +documentation: ug +--- + +# Undo and redo in Blazor Gantt Chart Component + +## Overview + +The Syncfusion® Blazor Gantt component includes built-in undo and redo functionality to revert or restore recent changes. This feature improves editing efficiency, reduces errors, and supports quick recovery from accidental modifications. + +## Enable undo and redo + +The Undo feature reverts the most recent action performed in the Blazor Gantt Chart, including changes to tasks, dependencies, and other supported operations. + +The Redo feature can reapply an action that was previously undone using the Undo feature. + +The undo redo feature can be enabled in Gantt by using the [EnableUndoRedo](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_EnableUndoRedo) property. + +Use the built-in toolbar items to perform undo and redo actions. + +{% 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" %} + +## Configure undo and redo actions + +By default, all supported actions are tracked. To limit which actions are recorded (for example, only edits and deletions), specify them via the [UndoRedoActions](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_UndoRedoActions). + +The following table shows built-in undo/redo actions: + +| 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 component allows limiting the number of undo and redo actions stored in the history of the Gantt chart. Control the number of stored history entries using [MaxUndoRedoSteps]( +https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_MaxUndoRedoSteps). +The default capacity is 20. When the count exceeds this value, the oldest entry is discarded and the newest action is appended, maintaining consistent memory usage. + +The following example demonstrates configuring 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" %} + +## Programmatic undo and redo + +Undo Redo actions can be triggered dynamically or through external controls using the following methods: + +* **Undo** – Use `UndoAsync` when an external Undo button is clicked. This method reverts the most recent tracked change from the undo collection, restoring the previous state of the Gantt chart. + +* **Redo** – Use `RedoAsync` when an external Redo button is clicked. This method reapplies the most recently undone change from the redo collection, restoring the state before the undo action. + +The following example demonstrates configuring the undo redo public methods. + +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@using Syncfusion.Blazor.Gantt + + + + + + + + + + + + + + + + + +@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(); + } + private async Task UndoMethod() + { + await GanttInstance.UndoAsync(); + } + private async Task RedoMethod() + { + 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/VjLyiBMhrNQxdzjs?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + +## Notes + +>* `EnableUndoRedo` must be set to **true** for keyboard shortcuts and programmatic APIs to operate. +>* Only actions listed in `UndoRedoActions` are recorded in history. +>* History length is constrained by `MaxUndoRedoSteps`; older entries are discarded when the limit is exceeded. + +## See Also + +* [How to add undo/redo events?]() +* [What are the keys used for undo/redo?]() \ No newline at end of file From b7272efeceb8b4d3d04672231e1ee80b32158416 Mon Sep 17 00:00:00 2001 From: vendasankarsf3945 Date: Fri, 12 Dec 2025 15:22:50 +0530 Subject: [PATCH 2/3] 998236: Updated the features path. --- blazor-toc.html | 2 ++ blazor/gantt-chart/column-validation.md | 18 ++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/blazor-toc.html b/blazor-toc.html index 73eb53a9d9..064c04e4c6 100644 --- a/blazor-toc.html +++ b/blazor-toc.html @@ -3111,6 +3111,7 @@
  • Critical Path
  • +
  • Undo Redo
  • Toolbar
  • Managing Tasks
  • diff --git a/blazor/gantt-chart/column-validation.md b/blazor/gantt-chart/column-validation.md index 1afacd8c47..16c07be233 100644 --- a/blazor/gantt-chart/column-validation.md +++ b/blazor/gantt-chart/column-validation.md @@ -7,7 +7,7 @@ control: Gantt Chart documentation: ug --- -### Column validation in Blazor Gantt Chart +## Column validation in Blazor Gantt Chart Column validation allows validating edited or newly added row data before saving it. This feature is particularly useful for enforcing specific rules or constraints on individual columns to maintain data integrity. By applying validation rules to columns, error messages are displayed for invalid fields, and saving is prevented until all validations succeed. @@ -145,17 +145,15 @@ The Syncfusion® Blazor Gantt Chart component leverages the Form Validator libra {% endhighlight %} {% endtabs %} -### Custom validation +## Custom validation Custom validation enables logic beyond built-in rules, including business constraints, dependent-field checks, and conditional validations during Add and Edit operations. -## How it works +### To implement custom validation in Blazor Gantt Chart: -## To implement custom validation in Blazor Gantt Chart: - -Create a class that inherits from `ValidationAttribute`. +* Create a class that inherits from `ValidationAttribute`. Override the `IsValid` method to include custom logic. -Apply the custom attribute to the model property that needs validation. -The Gantt Chart will automatically enforce these rules during Add and Edit operations. +* Apply the custom attribute to the model property that needs validation. +* The Gantt Chart will automatically enforce these rules during Add and Edit operations. The following sample code demonstrates how to implement custom validation for the ActivityName and Progress fields. @@ -307,12 +305,12 @@ The following sample code demonstrates how to implement custom validation for th {% endhighlight %} {% endtabs %} -### Custom validator component +## Custom validator component Custom validator components provide flexible validation beyond built‑in ValidationRules and ValidationAttribute classes.There are scenarios where these options are not enough. For example: Complex business logic You may need to validate multiple fields together (e.g., EndDate must be after StartDate, or Duration must match Progress). -## How does it work in Gantt Chart? +### How does it work in Gantt Chart? The Syncfusion® Blazor Gantt Chart supports injecting a custom validator component into its internal EditForm using the `Validator` property of [GanttEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html). From b688634e22e3c6e3e69cb1dd9df202fbcf841155 Mon Sep 17 00:00:00 2001 From: vendasankarsf3945 Date: Fri, 12 Dec 2025 18:03:18 +0530 Subject: [PATCH 3/3] 998236: updated review changes --- blazor/gantt-chart/column-validation.md | 603 +++++++++++------------- blazor/gantt-chart/undo-redo.md | 46 +- 2 files changed, 306 insertions(+), 343 deletions(-) diff --git a/blazor/gantt-chart/column-validation.md b/blazor/gantt-chart/column-validation.md index 16c07be233..d71cb915da 100644 --- a/blazor/gantt-chart/column-validation.md +++ b/blazor/gantt-chart/column-validation.md @@ -11,13 +11,13 @@ documentation: ug Column validation allows validating edited or newly added row data before saving it. This feature is particularly useful for enforcing specific rules or constraints on individual columns to maintain data integrity. By applying validation rules to columns, error messages are displayed for invalid fields, and saving is prevented until all validations succeed. -The Syncfusion® Blazor Gantt Chart component leverages the Form Validator library for column validation. Validation rules can be defined using the `GanttColumn.ValidationRules` property to specify criteria for validating column values. +The Syncfusion® Blazor Gantt Chart component leverages the Form Validator library for column validation. Validation rules can be defined using the [GanttColumn.ValidationRules](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttColumn.html?_gl=1*8pkqn2*_gcl_au*OTM1ODE5OTI5LjE3NjUzNTkzNzE.*_ga*MjU4MTYzMzQ2LjE3NjUzNTkzNzE.*_ga_41J4HFMX1J*czE3NjU1NDE1MDQkbzgkZzEkdDE3NjU1NDIwNzMkajYwJGwwJGgw) property to specify criteria for validating column values. {% tabs %} {% highlight razor tabtitle="Index.razor" %} @using Syncfusion.Blazor.Gantt - @@ -55,88 +55,108 @@ The Syncfusion® Blazor Gantt Chart component leverages the Form Validator libra @code { - private List TaskCollection { get; set; } - private SfGantt Gantt; + private List TaskCollection { get; set; } + private SfGantt Gantt; protected override void OnInitialized() { this.TaskCollection = EditingData().ToList(); } - public class TaskData + public class TaskInfoModel { public int TaskId { get; set; } - public string ActivityName { 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" %} + +## 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 way to enforce rules directly on the model properties, ensuring data integrity without writing additional validation logic. + +### How it works + +* Apply these attributes to the model class properties bound to the Gantt Chart. +* Validation messages are displayed using the built-in tooltip in the Gantt. + +{% 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() + public static List EditingData() { - List Tasks = new List() { - new TaskData() { - TaskId = 1, - ActivityName = "Product concept", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 08), - Duration = 5, - Progress = 60, - ParentId = null, - }, - new TaskData() { - TaskId = 2, - ActivityName = "Defining the product usage", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 08), - Duration = 3, - Progress = 70, - ParentId = 1, - }, - new TaskData() { - TaskId = 3, - ActivityName = "Defining the target audience", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 04), - Duration = 3, - Progress = 80, - ParentId = 1, - }, - new TaskData() { - TaskId = 4, - ActivityName = "Prepare product sketch and notes", - StartDate = new DateTime(2021, 04, 05), - EndDate = new DateTime(2021, 04, 08), - Duration = 2, - Progress = 90, - ParentId = 1, - }, - new TaskData() { - TaskId = 5, - ActivityName = "Concept approval", - StartDate = new DateTime(2021, 04, 08), - EndDate = new DateTime(2021, 04, 08), - Duration= 0, - Progress = 100, - ParentId = 1, - }, - new TaskData() { - TaskId = 6, - ActivityName = "Market research", - StartDate = new DateTime(2021, 04, 09), - EndDate = new DateTime(2021, 04, 18), - Duration = 4, - Progress = 30, - ParentId = null, - }, - new TaskData() { - TaskId = 7, - ActivityName = "Demand analysis", - StartDate = new DateTime(2021, 04, 09), - EndDate = new DateTime(2021, 04, 12), - Duration = 4, - Progress = 40, - ParentId = 6, - }, + 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; } @@ -145,8 +165,10 @@ The Syncfusion® Blazor Gantt Chart component leverages the Form Validator libra {% endhighlight %} {% endtabs %} +{% previewsample "https://blazorplayground.syncfusion.com/embed/BjLoWLWLpyjcQTAD?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + ## Custom validation -Custom validation enables logic beyond built-in rules, including business constraints, dependent-field checks, and conditional validations during Add and Edit operations. +Custom validation allows defining your own validation logic when built-in rules or data annotations do not meet specific requirements. This approach is useful for enforcing business-specific constraints, dependent-field checks, or conditional validations during Add and Edit operations. ### To implement custom validation in Blazor Gantt Chart: @@ -162,49 +184,54 @@ The following sample code demonstrates how to implement custom validation for th @using Syncfusion.Blazor.Gantt @using System.ComponentModel.DataAnnotations - + + ParentID="ParentId"> + Width="80" TextAlign="Syncfusion.Blazor.Grids.TextAlign.Right"> - + + Format="g" EditType="Syncfusion.Blazor.Grids.EditType.DateTimePickerEdit"> - + + TextAlign="Syncfusion.Blazor.Grids.TextAlign.Right"> - + @code { - private List TaskCollection { get; set; } - private SfGantt Gantt; + private List TaskCollection { get; set; } + private SfGantt Gantt; protected override void OnInitialized() { this.TaskCollection = EditingData().ToList(); } - public class TaskData + public class TaskInfoModel { public int TaskId { get; set; } [CustomValidationActivityName] - public string ActivityName { get; set; } + 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 Progress { get; set; } public int? ParentId { get; set; } - public DateTime? StartDate { get; set; } - public DateTime? EndDate { 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) @@ -217,6 +244,11 @@ The following sample code demonstrates how to implement custom validation for th 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) @@ -231,72 +263,16 @@ The following sample code demonstrates how to implement custom validation for th return ValidationResult.Success; } } - public static List EditingData() + public static List EditingData() { - List Tasks = new List() { - new TaskData() { - TaskId = 1, - ActivityName = "Product concept", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 08), - Duration = 5, - Progress = 60, - ParentId = null, - }, - new TaskData() { - TaskId = 2, - ActivityName = "Defining the product usage", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 08), - Duration = 3, - Progress = 70, - ParentId = 1, - }, - new TaskData() { - TaskId = 3, - ActivityName = "Defining the target audience", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 04), - Duration = 3, - Progress = 80, - ParentId = 1, - }, - new TaskData() { - TaskId = 4, - ActivityName = "Prepare product sketch and notes", - StartDate = new DateTime(2021, 04, 05), - EndDate = new DateTime(2021, 04, 08), - Duration = 2, - Progress = 90, - ParentId = 1, - }, - new TaskData() { - TaskId = 5, - ActivityName = "Concept approval", - StartDate = new DateTime(2021, 04, 08), - EndDate = new DateTime(2021, 04, 08), - Duration= 0, - Progress = 100, - ParentId = 1, - }, - new TaskData() { - TaskId = 6, - ActivityName = "Market research", - StartDate = new DateTime(2021, 04, 09), - EndDate = new DateTime(2021, 04, 18), - Duration = 4, - Progress = 30, - ParentId = null, - }, - new TaskData() { - TaskId = 7, - ActivityName = "Demand analysis", - StartDate = new DateTime(2021, 04, 09), - EndDate = new DateTime(2021, 04, 12), - Duration = 4, - Progress = 40, - ParentId = 6, - }, + 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; } @@ -305,27 +281,124 @@ The following sample code demonstrates how to implement custom validation for th {% endhighlight %} {% endtabs %} +{% previewsample "https://blazorplayground.syncfusion.com/embed/rXreihChTSiAmHFW?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} + ## Custom validator component -Custom validator components provide flexible validation beyond built‑in ValidationRules and ValidationAttribute classes.There are scenarios where these options are not enough. -For example: Complex business logic -You may need to validate multiple fields together (e.g., EndDate must be after StartDate, or Duration must match Progress). +Custom validator components provide flexible validation beyond built‑in ValidationRules and ValidationAttribute classes.Here, you can override the default validation logic by implementing your custom validation rules, which allows for more complex and specific validation scenarios tailored to your application's needs. ### How does it work in Gantt Chart? The Syncfusion® Blazor Gantt Chart supports injecting a custom validator component into its internal EditForm using the `Validator` property of [GanttEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html). -Inside the validator, you can access the current row’s data and the edit context via the implicit parameter context of type `ValidatorTemplateContext`. This enables form-level checks during Add/Edit operations. +Inside the validator, you can access the current row’s data and the edit context via the implicit parameter context of type [ValidatorTemplateContext](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.ValidatorTemplateContext.html?_gl=1*b3u6h5*_gcl_au*OTM1ODE5OTI5LjE3NjUzNTkzNzE.*_ga*MjU4MTYzMzQ2LjE3NjUzNTkzNzE.*_ga_41J4HFMX1J*czE3NjU1NDE1MDQkbzgkZzEkdDE3NjU1NDIyODgkajQ4JGwwJGgw). This enables form-level checks during Add/Edit operations. For creating a form validator component you can refer [here](https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/?view=aspnetcore-8.0#validator-components). +For example: Complex business logic +You may need to validate multiple fields together (e.g., EndDate must be after StartDate, or Duration must match Progress). + In the below code example, the following things have been done. * A form validator component named GanttCustomValidator that accepts `ValidatorTemplateContext` as a parameter. -* Usage of `GanttEditSettings.Validator` to inject the validator into the internal EditForm. +* Usage of [GanttEditSettings.Validator]((https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.GanttEditSettings.html)) to inject the validator into the internal EditForm. * This validator component will checks for TaskId and ActivityName with per‑field messages. * Display of errors using the built‑in validation tooltip via `ValidatorTemplateContext.ShowValidationMessage(fieldName, isValid, message)` method. +{% tabs %} +{% highlight razor tabtitle="Index.razor" %} + +@page "/gantt-validator" +@rendermode InteractiveServer +@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# %} @@ -359,45 +432,50 @@ namespace ColumnValidationComponents CurrentEditContext.OnValidationRequested += ValidateRequested; CurrentEditContext.OnFieldChanged += ValidateField; } - public void Dispose() - { - if (CurrentEditContext is not null) - { - 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.TaskData taskdata) + if (identifier.Model is GanttData.TaskInfoModel TaskInfoModel) { _messageStore.Clear(identifier); switch (identifier.FieldName) { - case nameof(GanttData.TaskData.TaskId): - if (taskdata.TaskId <= 0) + case nameof(GanttData.TaskInfoModel.TaskId): + if (TaskInfoModel.TaskId <= 0) AddError(identifier, "Task ID is required."); else ClearField(identifier); break; - case nameof(GanttData.TaskData.ActivityName): - if (string.IsNullOrWhiteSpace(taskdata.ActivityName)) + case nameof(GanttData.TaskInfoModel.ActivityName): + if (string.IsNullOrWhiteSpace(TaskInfoModel.ActivityName)) AddError(identifier, "Task Name is required."); - else if (taskdata.ActivityName.Length < 5 || taskdata.ActivityName.Length > 10) + else if (TaskInfoModel.ActivityName.Length < 5 || TaskInfoModel.ActivityName.Length > 10) AddError(identifier, "Task Name must be between 5 and 10 characters."); else ClearField(identifier); @@ -410,174 +488,47 @@ namespace ColumnValidationComponents } 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.TaskData.TaskId), - nameof(GanttData.TaskData.ActivityName), - nameof(GanttData.TaskData.Progress), - nameof(GanttData.TaskData.StartDate), - nameof(GanttData.TaskData.EndDate), + 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)); } } - } -} - -{% endhighlight %} -{% endtabs %} - -{% 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 TaskData - { - public int TaskId { get; set; } - public string ActivityName { get; set; } = string.Empty; - public int? Duration { get; set; } - public string Predecessor { get; set; } = string.Empty; - public int Progress { get; set; } - public int? ParentId { get; set; } - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } - } - public static List EditingData() + /// + /// Unsubscribes event handlers from the to prevent memory leaks and cleans up resources. + /// + public void Dispose() { - List Tasks = new List() { - new TaskData() { - TaskId = 1, - ActivityName = "Product concept", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 08), - Duration = 5, - Progress = 60, - ParentId = null, - }, - new TaskData() { - TaskId = 2, - ActivityName = "Defining the product usage", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 08), - Duration = 3, - Progress = 70, - ParentId = 1, - }, - new TaskData() { - TaskId = 3, - ActivityName = "Defining the target audience", - StartDate = new DateTime(2021, 04, 02), - EndDate = new DateTime(2021, 04, 04), - Duration = 3, - Progress = 80, - ParentId = 1, - }, - new TaskData() { - TaskId = 4, - ActivityName = "Prepare product sketch and notes", - StartDate = new DateTime(2021, 04, 05), - EndDate = new DateTime(2021, 04, 08), - Duration = 2, - Progress = 90, - ParentId = 1, - }, - new TaskData() { - TaskId = 5, - ActivityName = "Concept approval", - StartDate = new DateTime(2021, 04, 08), - EndDate = new DateTime(2021, 04, 08), - Duration= 0, - Progress = 100, - ParentId = 1, - }, - new TaskData() { - TaskId = 6, - ActivityName = "Market research", - StartDate = new DateTime(2021, 04, 09), - EndDate = new DateTime(2021, 04, 18), - Duration = 4, - Progress = 30, - ParentId = null, - }, - new TaskData() { - TaskId = 7, - ActivityName = "Demand analysis", - StartDate = new DateTime(2021, 04, 09), - EndDate = new DateTime(2021, 04, 12), - Duration = 4, - Progress = 40, - ParentId = 6, - }, - }; - return Tasks; + if (CurrentEditContext is not null) + { + CurrentEditContext.OnValidationRequested -= ValidateRequested; + CurrentEditContext.OnFieldChanged -= ValidateField; + } } } } @@ -585,5 +536,11 @@ namespace ColumnValidationComponents {% endhighlight %} {% endtabs %} +## Limitation +* **Resource column**: Validation is not supported because resource data is managed externally and updated as an empty string in the model. +* **Predecessor column**: Validation cannot be applied when using localization or rendering Grid columns inside a dialog, as rules and attributes cannot be passed to these columns. +## See Also +- [How to define columns manually in Blazor Gantt Chart?](https://ej2.syncfusion.com/blazor/documentation/gantt-chart/columns/column) +- [How to use column templates in Blazor Gantt Chart?](https://ej2.syncfusion.com/blazor/documentation/gantt-chart/columns/column-template) diff --git a/blazor/gantt-chart/undo-redo.md b/blazor/gantt-chart/undo-redo.md index cd98dfaacb..f159011afc 100644 --- a/blazor/gantt-chart/undo-redo.md +++ b/blazor/gantt-chart/undo-redo.md @@ -9,25 +9,23 @@ documentation: ug # Undo and redo in Blazor Gantt Chart Component -## Overview - The Syncfusion® Blazor Gantt component includes built-in undo and redo functionality to revert or restore recent changes. This feature improves editing efficiency, reduces errors, and supports quick recovery from accidental modifications. ## Enable undo and redo -The Undo feature reverts the most recent action performed in the Blazor Gantt Chart, including changes to tasks, dependencies, and other supported operations. +* The Undo feature reverts the most recent action performed in the Blazor Gantt Chart, including changes to tasks, dependencies, and other supported operations. -The Redo feature can reapply an action that was previously undone using the Undo feature. +* The Redo feature can reapply an action that was previously undone using the Undo feature. -The undo redo feature can be enabled in Gantt by using the [EnableUndoRedo](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_EnableUndoRedo) property. +* The undo redo feature can be enabled in Gantt by using the [EnableUndoRedo](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Gantt.SfGantt-1.html#Syncfusion_Blazor_Gantt_SfGantt_1_EnableUndoRedo) property. -Use the built-in toolbar items to perform undo and redo actions. +* Use the built-in toolbar items to perform undo and redo actions. {% tabs %} {% highlight razor tabtitle="Index.razor" %} @using Syncfusion.Blazor.Gantt - Undo - +Undo +Redo @@ -308,11 +307,19 @@ The following example demonstrates configuring the undo redo public methods. { this.TaskCollection = GetUndoRedoData(); } - private async Task UndoMethod() + + /// + /// Handles the undo action by invoking the Gantt component's asynchronous undo logic. + /// + private async Task UndoHandler() { await GanttInstance.UndoAsync(); } - private async Task RedoMethod() + + /// + /// Handles the redo action by invoking the Gantt component's asynchronous redo logic. + /// + private async Task RedoHandler() { await GanttInstance.RedoAsync(); } @@ -349,15 +356,14 @@ The following example demonstrates configuring the undo redo public methods. {% endhighlight %} {% endtabs %} -{% previewsample "https://blazorplayground.syncfusion.com/embed/VjLyiBMhrNQxdzjs?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} +{% previewsample "https://blazorplayground.syncfusion.com/embed/hDroWBCrUEUVmsZF?appbar=true&editor=true&result=true&errorlist=true&theme=bootstrap5" %} ## Notes ->* `EnableUndoRedo` must be set to **true** for keyboard shortcuts and programmatic APIs to operate. ->* Only actions listed in `UndoRedoActions` are recorded in history. ->* History length is constrained by `MaxUndoRedoSteps`; older entries are discarded when the limit is exceeded. +>* `EnableUndoRedo` must be set to **true** for keyboard shortcuts and method-based operations to function. +>* Only actions specified in `UndoRedoActions` are recorded in the undo/redo history. +>* History length is constrained by `MaxUndoRedoSteps`; the first entries are discarded when the limit is exceeded. ## See Also - -* [How to add undo/redo events?]() -* [What are the keys used for undo/redo?]() \ No newline at end of file +- [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