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

Context Menu Integration

In some cases, you may need to know which element the user clicked so you can use it in the command handling. You may also want to adjust the menu contents based on which element the user clicked (e.g., disable or entirely remove some items from the menu based on a condition).

Using the Selector parameter to attach the context menu to one or more targets at a time is simple, and can be useful when you want the same menu for many elements, but it does not matter which one the user clicked. So, the Telerik Context Menu offers the ShowAsync(x, y) method that lets you show it on demand after executing business logic.

To achieve such flexibility and granularity, you can:

  1. Use your own code to hook to an event such as @oncontextmenu to store the desired target and its metadata. You can use other events such as @onclick too.
    • You can use other events to show the context menu, like click, mousedown and so on. Make sure to pass correct coordinates to the menu - they must be relative to the viewport.
    • If you use the @oncontextmenu event, also add @oncontextmenu:preventDefault="true" to avoid the browser context menu which will always show above HTML structures on the page, like the Telerik Context Menu.
  2. Optionally, alter the data source or templates of the menu based on the metadata for the target.
  3. Show the Telerik menu through its @ref and the ShowAsync method it exposes.

This article provides the following two examples:

You can apply the approach of hooking to your own events to show the context menu in other scenarios as well. For example, you can add a context menu for your treeview nodes.

Know The Target And Adjust Items

Hooking to your own HTML elements' events lets you determine what to do with the context menu before showing it (for example, altering its data source).

Use the context menu target and change menu items based on the target data

@* Get context menu target and alter its items based on it *@

<TelerikContextMenu Data="@MenuItems" @ref="@TheContextMenu"
                    TextField="Text" SeparatorField="Separator" IconField="Icon"
                    DisabledField="Disabled"
                    OnClick="@( (ContextMenuItem itm) => ContextMenuClickHandler(itm) )">
</TelerikContextMenu>

<TelerikListView Data="@ListViewData" Width="700px" Pageable="true">
    <Template>
        <div @oncontextmenu:preventDefault="true"
             @oncontextmenu="@( (MouseEventArgs e) => ShowContextMenu(e, context) )"
             class="listview-item">
            <h4>@context.Name</h4>
            <h5>@context.Team</h5>
            <h6>Special context menu: @context.IsSpecial</h6>
        </div>
    </Template>
</TelerikListView>

@code {
    public List<ContextMenuItem> MenuItems { get; set; }
    TelerikContextMenu<ContextMenuItem> TheContextMenu { get; set; }
    SampleData LastClickedItem { get; set; }

    async Task ShowContextMenu(MouseEventArgs e, SampleData clickedItem)
    {
        // save the target/metadata
        LastClickedItem = clickedItem;
        // change the menu items
        PrepareMenuItems(clickedItem);
        // show the menu
        await TheContextMenu.ShowAsync(e.ClientX, e.ClientY);
    }

    void PrepareMenuItems(SampleData clickedItem)
    {
        // disable one item, you can make bigger changes here too
        MenuItems[2].Items[0].Disabled = clickedItem.IsSpecial;
    }

    async Task ContextMenuClickHandler(ContextMenuItem clickedItem)
    {
        // handle the command from the context menu by using the stored metadata
        if (!string.IsNullOrEmpty(clickedItem.CommandName) && LastClickedItem != null)
        {
            Console.WriteLine($"The programm will now perform the {clickedItem.CommandName} operation for {LastClickedItem.Name}");
        }
        LastClickedItem = null;
    }

    // generate sample data for the listview and the menu
    protected override void OnInitialized()
    {
        MenuItems = new List<ContextMenuItem>()
        {
            new ContextMenuItem
            {
                Text = "More Info",
                Icon = SvgIcon.InfoCircle,
                CommandName = "info"
            },
            new ContextMenuItem
            {
                Separator = true
            },
            new ContextMenuItem
            {
                Text = "Advanced",
                Items = new List<ContextMenuItem>()
            {
                    new ContextMenuItem
                    {
                        Text = "Delete",
                        Icon = SvgIcon.Trash,
                        CommandName = "delete"
                    },
                    new ContextMenuItem
                    {
                        Text = "Report",
                        Icon = SvgIcon.ReportElement,
                        CommandName = "report"
                    }
                }
            }
        };

        base.OnInitialized();
    }

    public class ContextMenuItem
    {
        public string Text { get; set; }
        public string CommandName { get; set; }
        public ISvgIcon Icon { get; set; }
        public bool Separator { get; set; }
        public bool Disabled { get; set; }
        public List<ContextMenuItem> Items { get; set; }
    }

    List<SampleData> ListViewData { get; set; } = Enumerable.Range(1, 25).Select(x => new SampleData
        {
            Id = x,
            Name = $"Name {x}",
            Team = $"Team {x % 3}",
            IsSpecial = x % 4 == 0
        }).ToList();

    public class SampleData
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Team { get; set; }
        public bool IsSpecial { get; set; }
    }
}

<style>
    .listview-item {
        height: 150px;
        width: 150px;
        display: inline-block;
        margin: 10px;
        border: 1px solid black;
        border-radius: 10px;
        padding: 10px;
    }
</style>

Context Menu for a Grid Row

To integrate the context menu with the Telerik Grid, you need to:

  1. Use the grid's OnRowContextMenu event to get the current row model and show the menu
  2. Use the context menu's OnClick event to handle the desired operation

In this example, the context menu is used to select/deselect items, put an item in edit mode and delete items

Use a Context Menu for Grid rows

@using System.Collections.Generic
@using System.Collections.ObjectModel

<TelerikContextMenu @ref="@ContextMenuRef" Data="@MenuItems"
                    OnClick="@((MenuItem item) => ContextMenuClickHandler(item))">
</TelerikContextMenu>

<TelerikGrid Data="@GridData" @ref="@GridRef"
             EditMode="@GridEditMode.Inline"
             Height="500px"
             Pageable="true"
             OnCreate="@CreateItem" OnUpdate="@UpdateHandler"
             OnRowContextMenu="@OnContextMenu"
             SelectionMode="@GridSelectionMode.Multiple"
             @bind-SelectedItems="@SelectedItems">
    <GridToolBarTemplate>
        <GridCommandButton Command="Add" Icon="@SvgIcon.Plus">Add Employee</GridCommandButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field=@nameof(SampleData.ID) Editable="false" />
        <GridColumn Field=@nameof(SampleData.Name) />
        <GridCommandColumn>
            <GridCommandButton Command="Save" Icon="@SvgIcon.Save" ShowInEdit="true">Update</GridCommandButton>
            <GridCommandButton Command="Cancel" Icon="@SvgIcon.Cancel" ShowInEdit="true">Cancel</GridCommandButton>
        </GridCommandColumn>
    </GridColumns>
</TelerikGrid>

@if (SelectedItems.Any())
{
    <ul>
        @foreach (var item in SelectedItems)
        {
            <li>@item.Name</li>
        }
    </ul>
}

@code {
    //data sources
    ObservableCollection<SampleData> GridData { get; set; }
    List<MenuItem> MenuItems { get; set; }
    IEnumerable<SampleData> SelectedItems { get; set; } = Enumerable.Empty<SampleData>();
    //metadata for the context menu actions
    SampleData SelectedPerson { get; set; }
    //component references so we can use their methods
    TelerikContextMenu<MenuItem> ContextMenuRef { get; set; }
    TelerikGrid<SampleData> GridRef { get; set; }

    // sample menu item class
    public class MenuItem
    {
        public string Text { get; set; }
        public ISvgIcon Icon { get; set; }
        public Action Action { get; set; }
        public string CommandName { get; set; }
    }

    // show the context menu for a particular row
    async Task OnContextMenu(GridRowClickEventArgs args)
    {
        var argsItem = args.Item as SampleData;

        SelectedPerson = argsItem;

        if (args.EventArgs is MouseEventArgs mouseEventArgs)
        {
            await ContextMenuRef.ShowAsync(mouseEventArgs.ClientX, mouseEventArgs.ClientY);
        }
    }

    // sample handling of the context menu click
    async Task ContextMenuClickHandler(MenuItem item)
    {
        // one way to pass handlers is to use an Action, you don't have to use this
        if (item.Action != null)
        {
            item.Action.Invoke();
        }
        else
        {
            // or you can use local code to perform a task
            // such as put a row in edit mode or select it
            switch (item.CommandName)
            {
                case "BeginEdit": // read more at https://docs.telerik.com/blazor-ui/components/grid/state#initiate-editing-or-inserting-of-an-item
                    var currState = GridRef.GetState();
                    currState.InsertedItem = null;
                    SampleData itemToEdit = SampleData.GetClonedInstance(GridData.Where(itm => itm.ID == SelectedPerson.ID).FirstOrDefault());
                    currState.OriginalEditItem = itemToEdit;
                    await GridRef.SetStateAsync(currState);
                    break;
                case "ToggleSelect":
                    var selItems = SelectedItems.ToList();
                    if (SelectedItems.Contains(SelectedPerson))
                    {
                        selItems.Remove(SelectedPerson);
                    }
                    else
                    {
                        selItems.Add(SelectedPerson);
                    }
                    SelectedItems = selItems;
                    break;
                default:
                    break;
            }
        }
        SelectedPerson = null; // clean up
    }

    // generate data
    protected override void OnInitialized()
    {
        // context menu items
        MenuItems = new List<MenuItem>()
    {
            new MenuItem(){ Text = "Select", Icon = SvgIcon.CheckboxChecked, CommandName="ToggleSelect" },
            new MenuItem(){ Text = "Edit", Icon = SvgIcon.Pencil, CommandName="BeginEdit" },
            new MenuItem(){ Text = "Delete", Icon = SvgIcon.Trash, Action = DeleteItem }
        };

        // generate data for the grid
        GridData = new ObservableCollection<SampleData>();
        var rand = new Random();

        for (int i = 0; i < 100; i++)
        {
            GridData.Add(new SampleData()
                {
                    ID = i,
                    Name = "Employee " + i.ToString(),
                });
        }
    }


    // CUD operations for the grid

    async Task CreateItem(GridCommandEventArgs args)
    {
        var argsItem = args.Item as SampleData;

        // call the actual data service here

        argsItem.ID = GridData.Count + 1;

        GridData.Insert(0, argsItem);
    }

    void DeleteItem() // not async so it can be passed as an Action
    {
        var argsItem = SelectedPerson;

        // call the actual data service here

        GridData.Remove(argsItem);
    }

    async Task UpdateHandler(GridCommandEventArgs args)
    {
        var argsItem = args.Item as SampleData;

        // call the actual data service here

        var index = GridData.ToList().FindIndex(i => i.ID == argsItem.ID);
        if (index != -1)
        {
            GridData[index] = argsItem;
        }
    }

    public class SampleData
    {
        public int ID { get; set; }
        public string Name { get; set; }


        public override bool Equals(object obj)
        {
            if (obj is SampleData)
            {
                return this.ID == (obj as SampleData).ID;
            }
            return false;
        }

        public SampleData()
        {

        }

        public SampleData(SampleData itmToClone)
        {
            this.ID = itmToClone.ID;
            this.Name = itmToClone.Name;
        }

        public static SampleData GetClonedInstance(SampleData itmToClone)
        {
            return new SampleData(itmToClone);
        }
    }
}

Context Menu for a TreeView Node

To integrate the ContextMenu with the TreeView, you need to:

  1. Use the OnItemContextMenu event of the TreeView to get the current row model and show the menu
  2. Use the context menu's OnClick event to handle the desired operation

In this example, the context menu is used to select/deselect items and delete items

Use a Context Menu for TreeView nodes

@* Use the OnItemContextMenu event of the TreeView to show the ContextMenu for its items *@

<TelerikContextMenu Data="@ContextMenuData"
                    @ref="ContextMenu"
                    OnClick="@((ContextMenuItem item) => ContextMenuClickHandler(item))">
</TelerikContextMenu>

<TelerikTreeView Data="@FlatData"
                 OnItemContextMenu="OnItemContextMenuHandler"
                 SelectionMode="@TreeViewSelectionMode.Multiple"
                 @bind-SelectedItems="@SelectedItems">
</TelerikTreeView>

@code {
    private TelerikContextMenu<ContextMenuItem> ContextMenu { get; set; }

    public TreeItem LastClickedItem { get; set; }

    public IEnumerable<object> SelectedItems { get; set; } = new List<object>();

    public List<TreeItem> FlatData { get; set; }

    public List<ContextMenuItem> ContextMenuData { get; set; }

    async Task OnItemContextMenuHandler(TreeViewItemContextMenuEventArgs args)
    {
        LastClickedItem = args.Item as TreeItem;

        if (args.EventArgs is MouseEventArgs mouseEventArgs)
        {
            await ContextMenu.ShowAsync(mouseEventArgs.ClientX, mouseEventArgs.ClientY);
        }
    }

    private void ContextMenuClickHandler(ContextMenuItem item)
    {
        // Use local code to perform a task such as put select/deselect a node or delete it
        switch (item.CommandName)
        {
            case "ToggleSelect":
                var selItems = SelectedItems.ToList();
                if (SelectedItems.Contains(LastClickedItem))
                {
                    selItems.Remove(LastClickedItem);
                }
                else
                {
                    selItems.Add(LastClickedItem);
                }
                SelectedItems = selItems;
                SelectedItems = new List<object>(SelectedItems);
                break;

            case "InvokeDelete":
                FlatData.Remove(LastClickedItem);
                FlatData = new List<TreeItem>(FlatData);
                break;
            default:
                break;
        }
        LastClickedItem = null; // clean up
    }

    // sample data

    public class ContextMenuItem
    {
        public string Text { get; set; }
        public ISvgIcon Icon { get; set; }
        public bool Separator { get; set; }
        public string CommandName { get; set; }
    }

    public class TreeItem
    {
        public int Id { get; set; }
        public string Text { get; set; }
        public int? ParentId { get; set; }
        public bool HasChildren { get; set; }
        public ISvgIcon Icon { get; set; }
        public bool Expanded { get; set; }
    }

    protected override void OnInitialized()
    {
        LoadFlatData();

        ContextMenuData = new List<ContextMenuItem>()
    {
            new ContextMenuItem
            {
                Text = "Select",
                Icon = SvgIcon.CheckboxChecked,
                CommandName = "ToggleSelect"
            },
            new ContextMenuItem
            {
                Separator = true
            },
            new ContextMenuItem
            {
                Text = "Delete",
                Icon = SvgIcon.Trash,
                CommandName = "InvokeDelete"
            }
        };
    }

    private void LoadFlatData()
    {
        List<TreeItem>
            items = new List<TreeItem>
                ();

        items.Add(new TreeItem()
        {
            Id = 1,
            Text = "Project",
            ParentId = null,
            HasChildren = true,
            Icon = SvgIcon.Folder,
            Expanded = true
        });

        items.Add(new TreeItem()
        {
            Id = 2,
            Text = "Design",
            ParentId = 1,
            HasChildren = true,
            Icon = SvgIcon.Brush,
            Expanded = true
        });
        items.Add(new TreeItem()
        {
            Id = 3,
            Text = "Implementation",
            ParentId = 1,
            HasChildren = true,
            Icon = SvgIcon.Folder,
            Expanded = true
        });

        items.Add(new TreeItem()
        {
            Id = 4,
            Text = "site.psd",
            ParentId = 2,
            HasChildren = false,
            Icon = SvgIcon.FilePsd,
            Expanded = true
        });
        items.Add(new TreeItem()
        {
            Id = 5,
            Text = "index.js",
            ParentId = 3,
            HasChildren = false,
            Icon = SvgIcon.Js
        });
        items.Add(new TreeItem()
        {
            Id = 6,
            Text = "index.html",
            ParentId = 3,
            HasChildren = false,
            Icon = SvgIcon.Html5
        });
        items.Add(new TreeItem()
        {
            Id = 7,
            Text = "styles.css",
            ParentId = 3,
            HasChildren = false,
            Icon = SvgIcon.Css
        });

        FlatData = items;
    }
}

See Also

In this article