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:
- 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.
- Optionally, alter the data source or templates of the menu based on the metadata for the target.
- Show the Telerik menu through its
@ref
and theShowAsync
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).
@* 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:
- Use the grid's
OnRowContextMenu
event to get the current row model and show the menu - 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
@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:
- Use the
OnItemContextMenu
event of the TreeView to get the current row model and show the menu - 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 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;
}
}