New to Telerik UI for Blazor? Download free 30-day trial

Gantt Tree Popup Editing

In this article:

Basics

Popup editing lets the user click an Edit command button on the row, and a popup shows up with the editable fields associated with a Gantt Task. They can then click the Save button in the dialog to submit the changes to the model. This fires the OnUpdate event where your code receives the updated model so you can work with the data (for example, to call the appropriate method of your service).

In a similar fashion, the Cancel and Delete command buttons and the Add toolbar button fire events to let you handle the data source operations.

You can also cancel the events by setting the IsCancelled property of the event arguments to true. This lets you prevent the user from editing certain records, inserting or deleting items, based on your application logic.

To enable Popup editing in the Gantt Tree, set its TreeListEditMode property to GanttTreeListEditMode.Popup, then handle the CRUD events as shown in the example below.

The popup editing dialog renders up to four tabs that allow you to edit:

Event Arguments

In Telerik UI for Blazor version 4.5.0, the GanttUpdateEventArgs received three new collections as fields. They are populated when you define Dependencies in the Gantt.

Field Type Description
CreatedDependencies List<GanttDependencyDescriptor> A collection of the newly created dependencies.
UpdatedDependencies List<GanttDependencyDescriptor> A collection of the updated dependencies.
DeletedDependencies List<GanttDependencyDescriptor> A collection of the deleted dependencies.

GanttDependencyDescriptor

The GanttDependencyDescriptor exposes four fields that describe the mutated dependency:

Field Type Description
PredecessorId object The Id of the predecessor of the mutated dependency.
SuccessorId object The Id of the successor of the mutated dependency.
Type int The Type of the dependency.
DataItem object The model associated with this dependency.

The Command buttons and the Gantt events let you handle data operations in Popup edit mode.

@using System.Collections.Generic
@using System.ComponentModel.DataAnnotations;

<TelerikGantt @ref="@GanttRef"
              Data="@Data"
              Width="100%"
              Height="600px"
              IdField="Id"
              ParentIdField="ParentId"
              TreeListEditMode="@GanttTreeListEditMode.Popup"
              OnUpdate="@UpdateItem"
              OnDelete="@DeleteItem"
              OnCreate="@CreateItem"
              Sortable="true"
              SortMode="@SortMode.Multiple"
              FilterMode="@GanttFilterMode.FilterMenu"
              FilterMenuType="@FilterMenuType.Menu">
    <GanttToolBarTemplate>
        <GanttCommandButton Command="Add" Icon="@SvgIcon.Plus">Add</GanttCommandButton>
    </GanttToolBarTemplate>
    <GanttViews>
        <GanttWeekView></GanttWeekView>
        <GanttMonthView></GanttMonthView>
        <GanttYearView></GanttYearView>
    </GanttViews>
    @*If you remove/comment the dependencies the Predecessor and Successor tabs in the popup editing dialog will dissapear.*@
    <GanttDependenciesSettings>
        <GanttDependencies Data="@Dependencies"
                           PredecessorIdField="PredecessorId"
                           SuccessorIdField="SuccessorId"
                           TypeField="Type"
                           OnCreate="@CreateDependency"
                           OnDelete="@DeleteDependency">
        </GanttDependencies>
    </GanttDependenciesSettings>
    <GanttColumns>
        <GanttCommandColumn Width="120px">
            <GanttCommandButton Command="Add" Icon="@SvgIcon.Plus"></GanttCommandButton>
            <GanttCommandButton Command="Edit" Icon="@SvgIcon.Pencil"></GanttCommandButton>
            <GanttCommandButton Command="Delete" Icon="@SvgIcon.Trash"></GanttCommandButton>
        </GanttCommandColumn>
        <GanttColumn Field="@nameof(FlatModel.Id)"
                     Editable="false"
                     Width="40px">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.Title)"
                     Expandable="true"
                     Width="160px"
                     Title="Task Title">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.PercentComplete)"
                     Width="100px">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.Start)"
                     Width="100px"
                     TextAlign="@ColumnTextAlign.Right">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.End)"
                     DisplayFormat="End: {0:d}"
                     Width="100px">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.Text)"
                     Width="100px">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.Bool)"
                     Width="100px">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.Number)"
                     Width="100px">
        </GanttColumn>
        <GanttColumn Field="@nameof(FlatModel.Date)"
                     Width="100px">
        </GanttColumn>
    </GanttColumns>
</TelerikGantt>

@code {
    private TelerikGantt<FlatModel> GanttRef;
    public DateTime SelectedDate { get; set; } = new DateTime(2019, 11, 11, 6, 0, 0);

    class FlatModel
    {
        //Fields editable in the General tab
        public int Id { get; set; }
        public int? ParentId { get; set; }
        [Required]
        public string Title { get; set; }
        public double PercentComplete { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        //Fields editable in the Others tab
        public string Text { get; set; }
        public bool Bool { get; set; }
        public int Number { get; set; }
        public DateTime Date { get; set; }
    }

    class DependencyModel
    {
        public int Id { get; set; }
        public int PredecessorId { get; set; }
        public int SuccessorId { get; set; }
        public int Type { get; set; }
    }

    public int LastId { get; set; } = 1;
    public int LastDependencyId { get; set; } = 1;
    List<FlatModel> Data { get; set; }
    List<DependencyModel> Dependencies { get; set; } = new List<DependencyModel>();

    protected override void OnInitialized()
    {
        Data = new List<FlatModel>();

        for (int i = 1; i < 6; i++)
        {
            var newItem = new FlatModel()
                {
                    Id = LastId,
                    Title = "Employee  " + i.ToString(),
                    Start = new DateTime(2020, 12, 6 + i),
                    End = new DateTime(2020, 12, 11 + i),
                    PercentComplete = i * 0.125
                };

            Data.Add(newItem);
            var parentId = LastId;
            LastId++;

            for (int j = 0; j < 5; j++)
            {
                Data.Add(new FlatModel()
                    {
                        Id = LastId,
                        ParentId = parentId,
                        Title = "    Employee " + i + " : " + j.ToString(),
                        Start = new DateTime(2020, 12, 6 + i + j),
                        End = new DateTime(2020, 12, 7 + i + j),
                        PercentComplete = j * 0.225
                    });

                LastId++;
            }
        }

        Dependencies.Add(new DependencyModel()
            {
                Id = LastDependencyId++,
                PredecessorId = 3,
                SuccessorId = 4,
                Type = 0
            });

        Dependencies.Add(new DependencyModel()
            {
                Id = LastDependencyId++,
                PredecessorId = 2,
                SuccessorId = 5,
                Type = 2
            });

        base.OnInitialized();
    }

    private void CreateDependency(GanttDependencyCreateEventArgs args)
    {
        var dependency = new DependencyModel()
            {
                Id = LastDependencyId++,
                PredecessorId = (int)args.PredecessorId,
                SuccessorId = (int)args.SuccessorId,
                Type = args.Type
            };

        Dependencies.Add(dependency);
    }

    private void DeleteDependency(GanttDependencyDeleteEventArgs args)
    {
        Dependencies.RemoveAll(d => d.Id.Equals((args.Item as DependencyModel).Id));
    }

    private void CreateItem(GanttCreateEventArgs args)
    {
        var argsItem = args.Item as FlatModel;

        argsItem.Id = LastId++;

        if (args.ParentItem != null)
        {
            var parent = (FlatModel)args.ParentItem;

            argsItem.ParentId = parent.Id;
        }

        Data.Insert(0, argsItem);

        CalculateParentPercentRecursive(argsItem);
        CalculateParentRangeRecursive(argsItem);
    }

    private void UpdateItem(GanttUpdateEventArgs args)
    {
        var item = args.Item as FlatModel;
        var parentItem = args.ParentItem as FlatModel;
        var foundItem = Data.FirstOrDefault(i => i.Id.Equals(item.Id));
        var foundParent = Data.FirstOrDefault(i => i.Id.Equals(parentItem?.Id));

        if (foundItem != null)
        {
            var startOffset = item.Start - foundItem.Start;
            if (startOffset != TimeSpan.Zero)
            {
                MoveChildrenRecursive(foundItem, startOffset);
            }

            foundItem.Title = item.Title;
            foundItem.Start = item.Start;
            foundItem.End = item.End;
            foundItem.PercentComplete = item.PercentComplete;

            // update parent
            if (foundItem.ParentId != foundParent?.Id)
            {
                foundItem.ParentId = foundParent?.Id;
            }

            //update custom properties
            foundItem.Text = item.Text;
            foundItem.Bool = item.Bool;
            foundItem.Number = item.Number;
            foundItem.Date = item.Date;
        }

        // update dependencies
        UpdateDependencies(args);

        CalculateParentPercentRecursive(foundItem);
        CalculateParentRangeRecursive(foundItem);
    }

    private void UpdateDependencies(GanttUpdateEventArgs args)
    {
        // add newly created dependencies
        args?.CreatedDependencies?.ForEach(x =>
        {
            var dependency = new DependencyModel()
                {
                    Id = LastDependencyId++,
                    PredecessorId = (int)x.PredecessorId,
                    SuccessorId = (int)x.SuccessorId,
                    Type = x.Type
                };

            Dependencies.Add(dependency);
        });

        // update modified dependencies
        args?.UpdatedDependencies?.ForEach(x =>
        {
            var dependency = GetDependencyDataItemByDescriptor(x);

            dependency.SuccessorId = (int)x.SuccessorId;
            dependency.PredecessorId = (int)x.PredecessorId;
            dependency.Type = x.Type;

        });

        // remove deleted dependencies
        args?.DeletedDependencies?.ForEach(x =>
        {
            var dependency = GetDependencyDataItemByDescriptor(x);

            Dependencies.Remove(dependency);
        });

        DependencyModel GetDependencyDataItemByDescriptor(DependencyDescriptor descriptor)
        {
            return Dependencies.FirstOrDefault(dep => dep.Id == (descriptor.DataItem as DependencyModel).Id);
        }
    }

    private void DeleteItem(GanttDeleteEventArgs args)
    {
        var item = Data.FirstOrDefault(i => i.Id.Equals((args.Item as FlatModel).Id));

        RemoveChildRecursive(item);

        CalculateParentPercentRecursive(item);
        CalculateParentRangeRecursive(item);
    }

    private void RemoveChildRecursive(FlatModel item)
    {
        var children = GetChildren(item).ToList();

        foreach (var child in children)
        {
            RemoveChildRecursive(child);
        }

        Data.Remove(item);
    }

    private void CalculateParentPercentRecursive(FlatModel item)
    {
        if (item.ParentId != null)
        {
            var parent = GetParent(item);

            var children = GetChildren(parent);

            if (children.Any())
            {
                parent.PercentComplete = children.Average(i => i.PercentComplete);

                CalculateParentPercentRecursive(parent);
            }
        }
    }

    private void CalculateParentRangeRecursive(FlatModel item)
    {
        if (item.ParentId != null)
        {
            var parent = GetParent(item);

            var children = GetChildren(parent);

            if (children.Any())
            {
                parent.Start = children.Min(i => i.Start);
                parent.End = children.Max(i => i.End);

                CalculateParentRangeRecursive(parent);
            }
        }
    }

    private void MoveChildrenRecursive(FlatModel item, TimeSpan offset)
    {
        var children = GetChildren(item);

        foreach (var child in children)
        {
            child.Start = child.Start.Add(offset);
            child.End = child.End.Add(offset);

            MoveChildrenRecursive(child, offset);
        }
    }

    private FlatModel GetParent(FlatModel item)
    {
        return Data.FirstOrDefault(i => i.Id.Equals(item.ParentId));
    }

    private IEnumerable<FlatModel> GetChildren(FlatModel item)
    {
        return Data.Where(i => item.Id.Equals(i.ParentId));
    }
}

It is up to the data access logic to save the data once it is changed in the data collection, or to revert changes. The example above showcases the events that allow you to do that. In a real application, the code for handling data operations may be entirely different.

Customization

The Gantt exposes options to customize the edit popup and its form. You can define your desired configuration in the GanttPopupEditSettings and GanttPopupEditFormSettings tags under the GanttSettings tag.

The GanttPopupEditSettings nested tag exposes the following parameters to allow popup customization:

Parameter Type Description
Class string The CSS class of the edit popup
Title string The title of the edit popup
ThemeColor string The color scheme of the window. Use the available members of the static class ThemeConstants.Window.ThemeColor.
Width string The Width of the edit popup
MaxWidth string The maximum width of the window
MinWidth string The minimum width of the window
Height string The height of edit popup
MaxHeight string The maximum height of the window
MinHeight string The minimum height of the window

The min/max options for the width and height apply to the initial rendering of the window and not browser resizing.

Edit Form Customization

The GanttPopupEditFormSettings nested tag exposes the following parameters to allow edit form customization:

Parameter Type Description
Columns int The count of the columns
ColumnSpacing int The column spacing
Orientation FormOrientation
(Vertical)
The orientation of the form. Takes a member of the FormOrientation enum:
- Horizontal
- Vertical

Customize the popup edit form

@*The snippet focuses on the popup edit form customization. CRUD events are not handled for brevity*@

<TelerikGantt Data="@Data"
              @bind-View="@SelectedView"
              TreeListEditMode="@GanttTreeListEditMode.Popup"
              Width="1000px"
              Height="600px"
              IdField="Id"
              ParentIdField="ParentId">
    <GanttSettings>
        <GanttPopupEditSettings Width="700px"
                                MinWidth="650px"
                                MaxHeight="300px"
                                Class="custom-popup">
        </GanttPopupEditSettings>
        <GanttPopupEditFormSettings Orientation="@FormOrientation.Horizontal"
                                    Columns="2"
                                    ColumnSpacing="50px">
        </GanttPopupEditFormSettings>
    </GanttSettings>
    <GanttToolBarTemplate>
        <GanttCommandButton Command="Add" Icon="@SvgIcon.Plus">Add</GanttCommandButton>
    </GanttToolBarTemplate>
    <GanttColumns>
        <GanttCommandColumn Width="110px">
            <GanttCommandButton Command="Add" Icon="@SvgIcon.Plus"></GanttCommandButton>
            <GanttCommandButton Command="Edit" Icon="@SvgIcon.Pencil"></GanttCommandButton>
            <GanttCommandButton Command="Delete" Icon="@SvgIcon.Trash"></GanttCommandButton>
        </GanttCommandColumn>
        <GanttColumn Field="Title"
                     Expandable="true"
                     Width="160px"
                     Title="Task Title">
        </GanttColumn>
        <GanttColumn Field="PercentComplete"
                     Title="Status"
                     Width="60px">
        </GanttColumn>
        <GanttColumn Field="Start"
                     Width="100px"
                     DisplayFormat="{0:d}">
        </GanttColumn>
        <GanttColumn Field="End"
                     Width="100px"
                     DisplayFormat="{0:d}">
        </GanttColumn>
    </GanttColumns>
    <GanttViews>
        <GanttDayView></GanttDayView>
        <GanttWeekView></GanttWeekView>
        <GanttMonthView></GanttMonthView>
    </GanttViews>
</TelerikGantt>

@code {
    public GanttView SelectedView { get; set; } = GanttView.Week;

    List<FlatModel> Data { get; set; }

    class FlatModel
    {
        public int Id { get; set; }
        public int? ParentId { get; set; }
        public string Title { get; set; }
        public double PercentComplete { get; set; }
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
    }

    public int LastId { get; set; } = 1;

    protected override void OnInitialized()
    {
        Data = new List<FlatModel>();
        var random = new Random();

        for (int i = 1; i < 6; i++)
        {
            var newItem = new FlatModel()
                {
                    Id = LastId,
                    Title = "Task  " + i.ToString(),
                    Start = new DateTime(2021, 7, 5 + i),
                    End = new DateTime(2021, 7, 11 + i),
                    PercentComplete = Math.Round(random.NextDouble(), 2)
                };

            Data.Add(newItem);
            var parentId = LastId;
            LastId++;

            for (int j = 0; j < 5; j++)
            {
                Data.Add(new FlatModel()
                    {
                        Id = LastId,
                        ParentId = parentId,
                        Title = "    Task " + i + " : " + j.ToString(),
                        Start = new DateTime(2021, 7, 5 + j),
                        End = new DateTime(2021, 7, 6 + i + j),
                        PercentComplete = Math.Round(random.NextDouble(), 2)
                    });

                LastId++;
            }
        }

        base.OnInitialized();
    }
}

See Also

In this article