references/data-binding.md
# Data Binding
## Table of Contents
- [MenuItemModel Structure](#menuitemmodel-structure)
- [Local Data Source Binding](#local-data-source-binding)
- [Parent-Child Item Relationships](#parent-child-item-relationships)
- [beforeItemRender Event](#beforeitemrender-event)
- [Dynamic Data Updates](#dynamic-data-updates)
## MenuItemModel Structure
The `MenuItemModel` interface defines the structure for menu items. Each item in the menu's `items` array follows this model:
### Complete Property Reference Table
| Property | Type | Optional | Default | Description |
|----------|------|----------|---------|-------------|
| `text` | `string` | No | - | **Required.** Specifies text for menu item. This is the display label shown to users. |
| `id` | `string` | Yes | - | Specifies the id for menu item. Use this for identifying items in methods like `enableItems()`, `removeItems()`, etc. Useful with `isUniqueId` parameter in component methods. |
| `items` | `MenuItemModel[]` | Yes | - | Specifies the sub menu items that is the array of MenuItem model. Creates nested/hierarchical menus. Can have unlimited nesting levels. |
| `separator` | `boolean` | Yes | `false` | Specifies separator between the menu items. Separators are either horizontal or vertical lines used to group menu items. Set to `true` to create a visual divider. |
| `iconCss` | `string` | Yes | - | Defines class/multiple classes separated by a space for the menu Item that is used to include an icon. Menu Item can include font icon and sprite image. Example: `iconCss: 'e-icons e-edit'` or `iconCss: 'fa-solid fa-pen'`. |
| `url` | `string` | Yes | - | Specifies url for menu item that creates the anchor link to navigate to the url provided. When clicked, navigates to this URL. Can be relative or absolute URLs. Example: `/dashboard` or `https://example.com/page`. |
| `htmlAttributes` | `Record<string, any>` | Yes | - | Specifies the htmlAttributes property to support adding custom attributes to the menu items. Example: `{ 'data-info': 'value', 'title': 'My Tooltip', 'aria-label': 'Edit Item' }`. Useful for custom styling, data attributes, and accessibility. |
### Complete Property Example
**Brief Example:**
```typescript
const menuItem: MenuItemModel = {
text: 'Edit', // Display text
id: 'edit-item', // Unique identifier
iconCss: 'e-icons e-edit', // Icon styling
items: [ // Sub-items (nested)
{ text: 'Undo' },
{ text: 'Redo' }
],
separator: false, // Not a separator
url: '', // No navigation
htmlAttributes: { // Custom attributes
'data-action': 'edit',
'title': 'Edit the item'
}
};
```
**Full Working Example - All Properties:**
```typescript
import { Component } from '@angular/core';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-full-menuitem-example',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu target='#target' [items]='menuItems'></ejs-contextmenu>
`
})
export class FullMenuItemExampleComponent {
menuItems: MenuItemModel[] = [
// Simple text item
{
text: 'New',
id: 'new-item'
},
// Item with icon
{
text: 'Edit',
id: 'edit-item',
iconCss: 'e-icons e-edit'
},
// Item with submenu
{
text: 'Format',
id: 'format-item',
iconCss: 'e-icons e-palette',
items: [
{ text: 'Bold', id: 'bold' },
{ text: 'Italic', id: 'italic' },
{ text: 'Underline', id: 'underline' }
]
},
// Separator divider
{
separator: true
},
// URL navigation item
{
text: 'Visit Website',
id: 'website-item',
iconCss: 'e-icons e-export',
url: 'https://example.com',
htmlAttributes: {
'target': '_blank',
'title': 'Open external link'
}
},
// Item with custom HTML attributes
{
text: 'Settings',
id: 'settings-item',
iconCss: 'e-icons e-settings',
htmlAttributes: {
'data-action': 'settings',
'data-premium': 'true',
'aria-label': 'Open settings menu',
'title': 'Configure options'
}
},
// Complex nested structure
{
text: 'Advanced',
id: 'advanced-item',
items: [
{
text: 'Development',
id: 'dev',
items: [
{ text: 'Console', id: 'console' },
{ text: 'Debugger', id: 'debugger' }
]
},
{
text: 'Analytics',
id: 'analytics',
url: '/analytics'
}
]
}
];
}
```
### Property Details and Examples
#### Property: `text` (Required)
Specifies the display text for the menu item. This is the only required property.
**Brief Example:**
```typescript
{ text: 'Edit' }
```
**Full Example:**
```typescript
const items: MenuItemModel[] = [
{ text: 'Save' }, // Simple text
{ text: 'Save As...' }, // Text with ellipsis
{ text: 'Recent Files' }, // Submenu text
{ text: 'A' }, // Single character
{ text: 'Copy (Ctrl+C)' } // Text with shortcut hint
];
```
#### Property: `id` (Optional)
Specifies the unique identifier for the menu item. Use this when you need to reference items programmatically in component methods.
**Brief Example:**
```typescript
{ text: 'Edit', id: 'edit-item' }
```
**Full Example - Using IDs with Component Methods:**
```typescript
export class IdExampleComponent {
items: MenuItemModel[] = [
{ text: 'Edit', id: 'edit-item' },
{ text: 'Delete', id: 'delete-item' },
{ text: 'Copy', id: 'copy-item' }
];
@ViewChild('contextMenu') contextMenu!: ContextMenuComponent;
disableAdminActions() {
// Disable by ID (more reliable than by text)
this.contextMenu.enableItems(['delete-item', 'admin-settings'], false, true);
}
showSuperUserMenu() {
// Show items by ID
this.contextMenu.showItems(['admin-panel', 'audit-logs'], true);
}
findItemIndex() {
// Get index by ID
const index = this.contextMenu.getItemIndex('edit-item', true);
console.log('Edit item at index:', index);
}
}
```
#### Property: `iconCss` (Optional)
Defines CSS classes for displaying an icon next to the menu item text. Supports Syncfusion icons, Font Awesome, or custom icon fonts.
**Brief Example:**
```typescript
{ text: 'Edit', iconCss: 'e-icons e-edit' }
```
**Full Example - Various Icon Sources:**
```typescript
items: MenuItemModel[] = [
// Syncfusion built-in icons
{ text: 'Edit', iconCss: 'e-icons e-edit' },
{ text: 'Delete', iconCss: 'e-icons e-delete' },
{ text: 'Save', iconCss: 'e-icons e-save' },
{ text: 'Copy', iconCss: 'e-icons e-copy' },
// Font Awesome icons (if available)
{ text: 'Settings', iconCss: 'fa-solid fa-gear' },
{ text: 'User', iconCss: 'fa-solid fa-user' },
// Multiple icon classes
{ text: 'Premium Feature', iconCss: 'e-icons e-star custom-premium-icon' },
// Submenu with icons
{
text: 'Format',
iconCss: 'e-icons e-palette',
items: [
{ text: 'Bold', iconCss: 'e-icons e-bold' },
{ text: 'Italic', iconCss: 'e-icons e-italic' },
{ text: 'Underline', iconCss: 'e-icons e-underline' }
]
}
];
```
**CSS for Custom Icons:**
```css
.custom-premium-icon::before {
content: '★';
color: gold;
}
```
#### Property: `separator` (Optional)
Creates a visual divider line between menu items for grouping related items.
**Brief Example:**
```typescript
{ separator: true }
```
**Full Example - Logical Grouping:**
```typescript
items: MenuItemModel[] = [
// Group 1: File operations
{ text: 'New', iconCss: 'e-icons e-new' },
{ text: 'Open', iconCss: 'e-icons e-open' },
{ text: 'Save', iconCss: 'e-icons e-save' },
// Separator
{ separator: true },
// Group 2: Editing operations
{ text: 'Cut', iconCss: 'e-icons e-cut' },
{ text: 'Copy', iconCss: 'e-icons e-copy' },
{ text: 'Paste', iconCss: 'e-icons e-paste' },
// Separator
{ separator: true },
// Group 3: Document operations
{ text: 'Print', iconCss: 'e-icons e-print' },
{ text: 'Export', iconCss: 'e-icons e-export' }
];
```
#### Property: `url` (Optional)
Specifies a URL that the menu item navigates to when clicked. Transforms the item into a clickable link.
**Brief Example:**
```typescript
{ text: 'Google', url: 'https://google.com' }
```
**Full Example - Navigation:**
```typescript
items: MenuItemModel[] = [
// External URLs
{
text: 'Visit Syncfusion',
url: 'https://syncfusion.com',
htmlAttributes: { 'target': '_blank' }
},
// Internal routes
{
text: 'Dashboard',
url: '/dashboard',
iconCss: 'e-icons e-home'
},
{
text: 'Settings',
url: '/settings',
iconCss: 'e-icons e-settings'
},
// Submenu with URLs
{
text: 'Documentation',
items: [
{ text: 'Getting Started', url: '/docs/getting-started' },
{ text: 'API Reference', url: '/docs/api' },
{ text: 'Tutorials', url: 'https://tutorials.example.com' }
]
}
];
```
**Note:** URL items still fire the `select` event, allowing you to intercept navigation if needed:
```typescript
onSelect(args: MenuEventArgs) {
if (args.item.url === 'https://example.com') {
// Custom handling before navigation
console.log('Navigating to external link');
}
}
```
#### Property: `items` (Optional - Nested MenuItemModel[])
Specifies sub-menu items, creating hierarchical/nested menus. Each item can have its own items array, allowing unlimited nesting depth.
**Brief Example:**
```typescript
{
text: 'Format',
items: [
{ text: 'Bold' },
{ text: 'Italic' }
]
}
```
**Full Example - Hierarchical Structure:**
```typescript
items: MenuItemModel[] = [
{
text: 'File',
iconCss: 'e-icons e-folder',
items: [
{ text: 'New', id: 'file-new' },
{ text: 'Open', id: 'file-open' },
{
text: 'Recent Files',
id: 'recent',
items: [
{ text: 'Document1.txt', url: '/recent/doc1' },
{ text: 'Document2.txt', url: '/recent/doc2' },
{ separator: true },
{ text: 'Clear Recent', id: 'clear-recent' }
]
},
{ separator: true },
{ text: 'Exit', id: 'file-exit' }
]
},
{
text: 'Edit',
iconCss: 'e-icons e-edit',
items: [
{ text: 'Undo', id: 'edit-undo' },
{ text: 'Redo', id: 'edit-redo' },
{ separator: true },
{ text: 'Cut', id: 'edit-cut' },
{ text: 'Copy', id: 'edit-copy' },
{ text: 'Paste', id: 'edit-paste' }
]
},
{
text: 'View',
items: [
{
text: 'Zoom',
items: [
{ text: '100%', id: 'zoom-100' },
{ text: '150%', id: 'zoom-150' },
{ text: '200%', id: 'zoom-200' }
]
},
{
text: 'Layout',
items: [
{ text: 'Default', id: 'layout-default' },
{ text: 'Wide', id: 'layout-wide' },
{ text: 'Compact', id: 'layout-compact' }
]
}
]
}
];
```
**Programming Nested Items:**
```typescript
export class NestedProgrammingComponent {
addSubMenu() {
// Add submenu to existing item
const fileItem = this.menuItems[0];
if (fileItem.items) {
fileItem.items.push({
text: 'New Group',
items: [
{ text: 'Project', id: 'new-project' },
{ text: 'File', id: 'new-file' }
]
});
}
}
getNestedItemIndex() {
// Find nested item: Format > Bold
const index = this.contextMenu.getItemIndex('bold', true);
console.log(index); // [1, 0] - second level menu, first item
}
}
```
#### Property: `htmlAttributes` (Optional - Record<string, any>)
Adds custom HTML attributes to the menu item DOM element. Useful for custom data storage, accessibility, and styling.
**Brief Example:**
```typescript
{
text: 'Edit',
htmlAttributes: {
'data-action': 'edit',
'title': 'Edit selected item'
}
}
```
**Full Example - Various Use Cases:**
```typescript
items: MenuItemModel[] = [
// Custom data attributes
{
text: 'Edit',
id: 'edit-item',
htmlAttributes: {
'data-action': 'edit',
'data-icon': 'pencil',
'data-hotkey': 'Ctrl+E'
}
},
// Accessibility attributes
{
text: 'Delete',
htmlAttributes: {
'aria-label': 'Delete the selected file',
'role': 'menuitem',
'title': 'Permanently remove this item (cannot be undone)'
}
},
// External URL with target
{
text: 'Help',
url: 'https://help.example.com',
htmlAttributes: {
'target': '_blank',
'rel': 'noopener noreferrer',
'title': 'Open help in new tab'
}
},
// Permission-based styling
{
text: 'Admin Panel',
htmlAttributes: {
'data-permission': 'admin',
'data-requires-auth': 'true',
'class': 'admin-only',
'style': 'color: red;'
}
},
// Performance tracking
{
text: 'Download Report',
htmlAttributes: {
'data-track': 'true',
'data-event': 'report-download',
'data-category': 'exports'
}
},
// Nested items with attributes
{
text: 'Export',
htmlAttributes: {
'data-submenu': 'export-options'
},
items: [
{
text: 'PDF',
htmlAttributes: {
'data-format': 'pdf',
'data-size': 'small'
}
},
{
text: 'Excel',
htmlAttributes: {
'data-format': 'xlsx',
'data-size': 'medium'
}
}
]
}
];
```
**Accessing Custom Attributes in TypeScript:**
```typescript
onItemSelect(args: MenuEventArgs) {
// Access custom data attributes
const action = args.element.getAttribute('data-action');
const requiresAuth = args.element.getAttribute('data-requires-auth');
console.log('Action:', action);
console.log('Requires authentication:', requiresAuth);
// Track analytics
const trackData = args.element.getAttribute('data-track');
if (trackData === 'true') {
const event = args.element.getAttribute('data-event');
// Send to analytics
console.log('Track event:', event);
}
}
```
**CSS Styling with htmlAttributes:**
```css
/* Style admin-only items */
.admin-only {
color: red;
font-weight: bold;
}
/* Highlight items with data-premium attribute */
[data-premium="true"] {
background-color: #fff3cd;
border-left: 3px solid gold;
}
/* Add visual indicator for items requiring authentication */
[data-requires-auth="true"]::after {
content: ' 🔒';
}
```
### Core Properties
The basic `MenuItemModel` interface structure:
```typescript
import { MenuItemModel } from '@syncfusion/ej2-navigations';
// Minimal required structure
interface MenuItemModel {
text: string; // Display text (required)
// Optional properties for enhancement
id?: string;
items?: MenuItemModel[];
separator?: boolean;
iconCss?: string;
url?: string;
htmlAttributes?: Record<string, any>;
}
```
## Local Data Source Binding
### Array of Objects
Bind menu items from a simple array of objects:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut', iconCss: 'e-cm-icons e-cut' },
{ text: 'Copy', iconCss: 'e-cm-icons e-copy' },
{ text: 'Paste', iconCss: 'e-cm-icons e-paste' }
];
}
```
### Dynamic Array Creation
Create menu items programmatically from data:
```typescript
export class AppComponent {
public menuItems: MenuItemModel[] = [];
constructor() {
this.initializeMenu();
}
initializeMenu(): void {
const actions = ['Cut', 'Copy', 'Paste', 'Delete'];
this.menuItems = actions.map(action => ({
text: action,
iconCss: `e-icons e-${action.toLowerCase()}`
}));
}
}
```
### Data from API/Service
Fetch menu items from an external source:
```typescript
import { Component, OnInit } from '@angular/core';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
// Mock service for demonstration
class MenuService {
getMenuItems(): MenuItemModel[] {
return [
{ text: 'Dashboard', iconCss: 'e-icons e-home' },
{ text: 'Settings', iconCss: 'e-icons e-settings' },
{ text: 'Logout', iconCss: 'e-icons e-close' }
];
}
}
@Component({
selector: 'app-root',
standalone: true,
template: `<ejs-contextmenu [items]='menuItems'></ejs-contextmenu>`
})
export class AppComponent implements OnInit {
public menuItems: MenuItemModel[] = [];
constructor(private menuService: MenuService) {}
ngOnInit(): void {
this.menuItems = this.menuService.getMenuItems();
}
}
```
## Parent-Child Item Relationships
### Hierarchical Structure
Create multi-level menus with parent-child relationships:
```typescript
public menuItems: MenuItemModel[] = [
{
text: 'File',
iconCss: 'e-icons e-folder',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Recent Files', items: [
{ text: 'document1.txt' },
{ text: 'document2.txt' }
]}
]
},
{
text: 'Edit',
iconCss: 'e-icons e-edit',
items: [
{ text: 'Undo' },
{ text: 'Redo' },
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
}
];
```
### Building from Flat Data
Transform flat data structure into hierarchical menu:
```typescript
interface MenuItem {
id: number;
parentId?: number;
text: string;
icon?: string;
}
const flatData: MenuItem[] = [
{ id: 1, text: 'File', icon: 'e-folder' },
{ id: 2, parentId: 1, text: 'New' },
{ id: 3, parentId: 1, text: 'Open' },
{ id: 4, text: 'Edit', icon: 'e-edit' },
{ id: 5, parentId: 4, text: 'Cut' },
{ id: 6, parentId: 4, text: 'Copy' }
];
export class AppComponent {
public menuItems: MenuItemModel[] = [];
constructor() {
this.buildHierarchy();
}
buildHierarchy(): void {
const items: MenuItemModel[] = [];
const itemMap = new Map<number, MenuItemModel>();
// First pass: create all items
flatData.forEach(data => {
const menuItem: MenuItemModel = {
text: data.text,
iconCss: data.icon ? `e-icons ${data.icon}` : undefined,
items: []
};
itemMap.set(data.id, menuItem);
});
// Second pass: build hierarchy
flatData.forEach(data => {
const menuItem = itemMap.get(data.id)!;
if (data.parentId) {
const parent = itemMap.get(data.parentId);
if (parent && parent.items) {
parent.items.push(menuItem);
}
} else {
items.push(menuItem);
}
});
this.menuItems = items;
}
}
```
### Managing Parent-Child Updates
```typescript
updateSubmenu(parentId: string, newSubItems: MenuItemModel[]): void {
const parent = this.menuItems.find(m => m.id === parentId);
if (parent) {
parent.items = newSubItems;
// Trigger menu refresh if needed
}
}
```
## beforeItemRender Event
### Item Rendering Customization
The `beforeItemRender` event fires before each menu item renders, allowing customization:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='itemRender($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
];
itemRender(args: MenuEventArgs): void {
// Called before each item renders
console.log('Rendering item:', args.item.text);
}
}
```
### Conditional Item Styling
```typescript
itemRender(args: MenuEventArgs): void {
// Highlight disabled items
if (args.item.disabled) {
args.element.classList.add('disabled-item');
}
// Add CSS classes based on item properties
if (args.item.iconCss) {
args.element.classList.add('icon-item');
}
}
```
### Custom HTML Content
```typescript
itemRender(args: MenuEventArgs): void {
// Customize item display with HTML
if (args.item.text === 'Save As...') {
args.element.innerHTML = `
<span>Save As...</span>
<span style="color: gray; font-size: 0.9em;">(Ctrl+S)</span>
`;
}
}
```
### Data Transformation
```typescript
itemRender(args: MenuEventArgs): void {
// Format text based on item properties
if (args.item.text) {
// Convert to title case
const titleCase = args.item.text
.split(' ')
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
.join(' ');
args.element.textContent = titleCase;
}
}
```
### Handling Separator Items
```typescript
itemRender(args: MenuEventArgs): void {
// Add custom styling to separators
if (!args.item.text && args.item.separator) {
args.element.classList.add('custom-separator');
}
}
```
## Dynamic Data Updates
### Updating Menu Items at Runtime
Modify menu items and refresh the component:
```typescript
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
];
addMenuItem(newItem: MenuItemModel): void {
this.menuItems.push(newItem);
// Trigger change detection
}
updateMenuItem(index: number, updatedItem: MenuItemModel): void {
this.menuItems[index] = updatedItem;
}
removeMenuItem(index: number): void {
this.menuItems.splice(index, 1);
}
}
```
### Real-Time Data Binding
Bind menu items to a data source that changes:
```typescript
export class AppComponent implements OnInit {
public menuItems: MenuItemModel[] = [];
private dataRefreshInterval: any;
ngOnInit(): void {
this.loadMenuItems();
// Refresh menu items every 5 seconds
this.dataRefreshInterval = setInterval(() => {
this.loadMenuItems();
}, 5000);
}
private loadMenuItems(): void {
// Fetch fresh data from service
this.menuItems = [
{ text: 'Item 1', id: '1' },
{ text: 'Item 2', id: '2' },
{ text: 'Item 3', id: '3' }
];
}
ngOnDestroy(): void {
if (this.dataRefreshInterval) {
clearInterval(this.dataRefreshInterval);
}
}
}
```
### Conditional Item Rendering
```typescript
public get dynamicMenuItems(): MenuItemModel[] {
const items: MenuItemModel[] = [
{ text: 'New', iconCss: 'e-icons e-new' },
{ text: 'Open', iconCss: 'e-icons e-open' }
];
// Add admin-only items
if (this.isAdmin) {
items.push({ text: 'Settings', iconCss: 'e-icons e-settings' });
items.push({ text: 'Manage Users', iconCss: 'e-icons e-users' });
}
return items;
}
```
### Batch Updates
```typescript
updateMenuStructure(newStructure: MenuItemModel[]): void {
this.menuItems = []; // Clear existing
// Add new items in batch
newStructure.forEach(item => {
this.menuItems.push(item);
});
}
```
## Complete Example: Dynamic Data-Bound Menu
```typescript
import { Component, OnInit, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
interface DataRecord {
id: number;
text: string;
iconCss?: string;
subItems?: DataRecord[];
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<button (click)="addItem()">Add Item</button>
<button (click)="refreshData()">Refresh Data</button>
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='onItemRender($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent implements OnInit {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [];
private dataSource: DataRecord[] = [];
ngOnInit(): void {
this.loadDataSource();
this.bindMenuItems();
}
private loadDataSource(): void {
this.dataSource = [
{
id: 1,
text: 'File',
iconCss: 'e-icons e-folder',
subItems: [
{ id: 11, text: 'New' },
{ id: 12, text: 'Open' },
{ id: 13, text: 'Save' }
]
},
{
id: 2,
text: 'Edit',
iconCss: 'e-icons e-edit',
subItems: [
{ id: 21, text: 'Cut' },
{ id: 22, text: 'Copy' },
{ id: 23, text: 'Paste' }
]
}
];
}
private bindMenuItems(): void {
this.menuItems = this.transformData(this.dataSource);
}
private transformData(data: DataRecord[]): MenuItemModel[] {
return data.map(record => ({
text: record.text,
iconCss: record.iconCss,
id: record.id.toString(),
items: record.subItems ? this.transformData(record.subItems) : undefined
}));
}
onItemRender(args: MenuEventArgs): void {
// Apply custom styling
if (args.item.iconCss) {
args.element.classList.add('has-icon');
}
}
addItem(): void {
const newItem: DataRecord = {
id: this.dataSource.length + 1,
text: 'New Item',
iconCss: 'e-icons e-plus'
};
this.dataSource.push(newItem);
this.bindMenuItems();
}
refreshData(): void {
this.loadDataSource();
this.bindMenuItems();
}
}
```
---
**Next:** Handle menu item clicks and events in [references/interaction-and-events.md](../interaction-and-events.md).
references/getting-started.md
# Getting Started with Syncfusion Angular ContextMenu
## Table of Contents
- [Installation and Dependencies](#installation-and-dependencies)
- [Angular Environment Setup](#angular-environment-setup)
- [Creating Your First ContextMenu](#creating-your-first-contextmenu)
- [Target Element Configuration](#target-element-configuration)
- [Basic Menu Item Structure](#basic-menu-item-structure)
## Installation and Dependencies
### Required Packages
The ContextMenu component requires the following Syncfusion packages:
```
@syncfusion/ej2-angular-navigations
├── @syncfusion/ej2-angular-base
└── @syncfusion/ej2-navigations
├── @syncfusion/ej2-base
├── @syncfusion/ej2-data
├── @syncfusion/ej2-lists
├── @syncfusion/ej2-inputs
├── @syncfusion/ej2-splitbuttons
└── @syncfusion/ej2-popups
└── @syncfusion/ej2-buttons
```
### Installation Command
Install the package using npm:
```bash
npm install @syncfusion/ej2-angular-navigations
```
```css
@import "../node_modules/@syncfusion/ej2-material3-theme/styles/context-menu/index.css";
```
## Angular Environment Setup
### Prerequisites
- Node.js and npm installed
- Angular CLI installed globally
```bash
npm install -g @angular/cli
```
### Creating a New Angular Application
Generate a new Angular application with Angular CLI:
```bash
ng new syncfusion-angular-app
```
When prompted, configure:
- Routing: Choose as needed for your project
- Stylesheet format: CSS or SCSS
For modern Angular (21+) which uses standalone architecture:
```bash
ng new syncfusion-angular-app --style=scss
```
### Navigate to Project Directory
```bash
cd syncfusion-angular-app
```
> **Angular 21 Standalone Architecture:** Standalone components are the default in Angular 21+. This guide uses standalone architecture. Components no longer require NgModule declarations.
## Creating Your First ContextMenu
### Basic Component Setup
Create a new component with the ContextMenu:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<!-- Target element -->
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<!-- ContextMenu component -->
<ejs-contextmenu
id='contextmenu'
target='#target'
[items]='menuItems'>
</ejs-contextmenu>
</div>
`,
styles: [`
#target {
height: 200px;
width: 300px;
border: 1px solid #ccc;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: context-menu;
background-color: #f5f5f5;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Back' },
{ text: 'Forward' },
{ text: 'Refresh' },
{ separator: true },
{ text: 'Save As...' },
{ text: 'Print' }
];
}
```
### Standalone Bootstrap
Configure the main entry point:
```typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));
```
### Running the Application
Start the development server:
```bash
ng serve
```
Navigate to `http://localhost:4200/` in your browser. Right-click or touch-hold the target area to see the context menu appear.
## Target Element Configuration
### Single Target Element
Configure a single element as the context menu trigger:
```typescript
<ejs-contextmenu
target='#myElement'
[items]='menuItems'>
</ejs-contextmenu>
```
### Multiple Target Elements (Same Class)
Use CSS selectors to target multiple elements:
```typescript
<div class="target-area">Right click me</div>
<div class="target-area">Or me</div>
<ejs-contextmenu
target='.target-area'
[items]='menuItems'>
</ejs-contextmenu>
```
### Nested Target Elements
Target nested elements using descendant selectors:
```typescript
<div id="container">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
</div>
<ejs-contextmenu
target='#container .item'
[items]='menuItems'>
</ejs-contextmenu>
```
### Dynamic Target Assignment
Assign target via component property:
```typescript
@Component({
template: `
<ejs-contextmenu
[target]='targetElement'
[items]='menuItems'>
</ejs-contextmenu>
`
})
export class AppComponent {
public targetElement = '#target';
}
```
## Basic Menu Item Structure
### MenuItem Properties
Each menu item is defined using the `MenuItemModel` interface:
```typescript
import { MenuItemModel } from '@syncfusion/ej2-navigations';
public menuItems: MenuItemModel[] = [
{
text: 'Cut', // Display text
iconCss: 'e-cut-icon', // Icon CSS class
id: 'cut-item' // Unique identifier
},
{
text: 'Copy',
iconCss: 'e-copy-icon'
},
{
separator: true // Separator line
},
{
text: 'View',
items: [ // Nested sub-items
{ text: 'Large Icons' },
{ text: 'Small Icons' }
]
}
];
```
### Simple Text Items
Basic menu items with just text:
```typescript
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
];
```
### Items with Icons
Add visual icons using CSS classes:
```typescript
public menuItems: MenuItemModel[] = [
{ text: 'Cut', iconCss: 'e-cm-icons e-cut' },
{ text: 'Copy', iconCss: 'e-cm-icons e-copy' },
{ text: 'Paste', iconCss: 'e-cm-icons e-paste' }
];
```
### Items with Separators
Use separators to group related items:
```typescript
public menuItems: MenuItemModel[] = [
{ text: 'Cut' },
{ text: 'Copy' },
{ separator: true }, // Separator line
{ text: 'Save' },
{ text: 'Print' }
];
```
### Nested Multi-Level Menus
Create hierarchical menus with sub-items:
```typescript
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
}
];
```
### Complete Example with All Features
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
id='contextmenu'
target='#target'
[items]='menuItems'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'New', iconCss: 'e-icons e-new' },
{ text: 'Open', iconCss: 'e-icons e-open' },
{ separator: true },
{
text: 'View',
items: [
{ text: 'Large Icons' },
{ text: 'Small Icons' }
]
},
{ separator: true },
{ text: 'Refresh', iconCss: 'e-icons e-refresh' }
];
}
```
## Common Issues & Solutions
**Issue:** Context menu doesn't appear on right-click
- **Solution:** Verify the `target` element exists in DOM and the selector is correct
**Issue:** Styling looks different from Syncfusion samples
- **Solution:** Import Syncfusion CSS files (usually auto-imported, but check in styles.css)
**Issue:** Menu items not displaying icons
- **Solution:** Ensure `iconCss` uses valid Syncfusion icon classes (e.g., `e-icons e-cut`)
**Issue:** Sub-menus not appearing
- **Solution:** Verify nested items are properly defined in the `items` property
**Issue:** "Cannot find module @syncfusion/ej2-angular-navigations"
- **Solution:** Run `npm install @syncfusion/ej2-angular-navigations` and verify package.json
---
**Next:** Proceed to [references/menu-items-management.md](../menu-items-management.md) to learn how to dynamically add, remove, and manage menu items.
references/interaction-and-events.md
# Interaction & Events
## Table of Contents
- [Menu Item Click Handlers](#menu-item-click-handlers)
- [Click-to-Open Submenus](#click-to-open-submenus)
- [Programmatic Open and Close](#programmatic-open-and-close)
- [Menu Positioning](#menu-positioning)
- [Opening Dialogs on Item Selection](#opening-dialogs-on-item-selection)
- [MenuEventArgs Reference](#menueventargs-reference)
## Menu Item Click Handlers
### select Event
Handle menu item clicks using the `select` event:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
(select)='onSelect($event)'>
</ejs-contextmenu>
<p>Selected Item: {{ selectedItem }}</p>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
];
public selectedItem = '';
onSelect(args: MenuEventArgs): void {
this.selectedItem = args.item.text || 'Unknown';
console.log('Item clicked:', args.item.text);
}
}
```
### Conditional Actions Based on Selection
```typescript
onSelect(args: MenuEventArgs): void {
switch (args.item.text) {
case 'Cut':
this.performCut();
break;
case 'Copy':
this.performCopy();
break;
case 'Paste':
this.performPaste();
break;
}
}
private performCut(): void {
console.log('Cutting selected content');
// Implement cut logic
}
private performCopy(): void {
console.log('Copying selected content');
// Implement copy logic
}
private performPaste(): void {
console.log('Pasting content');
// Implement paste logic
}
```
### Accessing Event Arguments
```typescript
onSelect(args: MenuEventArgs): void {
// Get item properties
const itemText = args.item.text;
const itemId = args.item.id;
const itemElement = args.element;
// Get event details
const eventSource = args.event;
const targetElement = args.event.target as HTMLElement;
console.log(`Selected: ${itemText} (ID: ${itemId})`);
console.log(`Target element ID: ${targetElement.id}`);
}
```
## Click-to-Open Submenus
### showItemOnClick Property
By default, submenus open on hover. Enable click-based opening:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
[showItemOnClick]='true'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Show All Bookmarks' },
{
text: 'Bookmarks Toolbar',
items: [
{
text: 'Most Visited',
items: [
{ text: 'Gmail' },
{ text: 'Google' }
]
},
{ text: 'Recently Added' }
]
}
];
}
```
### Advantages of Click-Based Submenus
- Prevents accidental submenu opening on hover
- Better for touch devices where hover isn't available
- More deliberate user interaction
- Reduces menu clutter on hover
## Programmatic Open and Close
### open() Method
Open the context menu programmatically at specific coordinates:
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
import { getInstance } from '@syncfusion/ej2-base';
import { ContextMenu } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule, ButtonModule],
template: `
<div class="e-section-control">
<button ejs-button (click)="openMenu()">Open ContextMenu</button>
<ejs-contextmenu
id='contextmenu'
[items]='menuItems'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
];
openMenu(): void {
// Get reference to context menu component
const contextmenuElement = document.getElementById('contextmenu_0') as HTMLElement;
const contextmenuObj = getInstance(contextmenuElement, ContextMenu) as ContextMenu;
// Open at specific coordinates (top: 40px, left: 20px)
contextmenuObj.open(40, 20);
}
}
```
### close() Method
Close the context menu programmatically:
```typescript
closeMenu(): void {
const contextmenuElement = document.getElementById('contextmenu_0') as HTMLElement;
const contextmenuObj = getInstance(contextmenuElement, ContextMenu) as ContextMenu;
contextmenuObj.close();
}
```
### Open at Mouse Position
```typescript
openMenuAtMouse(event: MouseEvent): void {
const contextmenuElement = document.getElementById('contextmenu_0') as HTMLElement;
const contextmenuObj = getInstance(contextmenuElement, ContextMenu) as ContextMenu;
// Open at mouse cursor position
contextmenuObj.open(event.clientY, event.clientX);
}
```
## Menu Positioning
### Top and Left Coordinates
Position the menu at specific screen coordinates:
```typescript
openMenu(): void {
const contextmenuObj = getInstance(
document.getElementById('contextmenu_0'),
ContextMenu
) as ContextMenu;
// Position: 100px from top, 150px from left
contextmenuObj.open(100, 150);
}
```
### Relative to Target Element
```typescript
openMenuAtElement(): void {
const targetElement = document.getElementById('target') as HTMLElement;
const rect = targetElement.getBoundingClientRect();
const contextmenuObj = getInstance(
document.getElementById('contextmenu_0'),
ContextMenu
) as ContextMenu;
// Position at element's bottom-right
contextmenuObj.open(
rect.top + rect.height,
rect.left + rect.width
);
}
```
### Center on Screen
```typescript
centerMenuOnScreen(): void {
const contextmenuObj = getInstance(
document.getElementById('contextmenu_0'),
ContextMenu
) as ContextMenu;
const top = window.innerHeight / 2;
const left = window.innerWidth / 2;
contextmenuObj.open(top, left);
}
```
## Opening Dialogs on Item Selection
### Dialog Integration
Open a dialog when a specific menu item is clicked:
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { DialogModule, DialogComponent } from '@syncfusion/ej2-angular-popups';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule, DialogModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-dialog
#dialog
[visible]='dialogVisible'
[buttons]='dialogButtons'
header='Save As'
content='Enter file name...'>
</ejs-dialog>
<ejs-contextmenu
target="#target"
[items]="menuItems"
(select)="itemSelect($event)">
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
@ViewChild('dialog')
public dialog?: DialogComponent;
public dialogVisible = false;
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save As...' },
{ text: 'Print' }
];
public dialogButtons: any[] = [
{
buttonModel: {
isPrimary: true,
content: 'Save',
cssClass: 'e-flat'
},
click: () => this.onDialogSave()
}
];
itemSelect(args: MenuEventArgs): void {
if (args.item.text === 'Save As...') {
this.dialogVisible = true;
if (this.dialog) {
this.dialog.show();
}
}
}
onDialogSave(): void {
console.log('File saved');
this.dialogVisible = false;
if (this.dialog) {
this.dialog.hide();
}
}
}
```
### Confirmation Dialog
```typescript
itemSelect(args: MenuEventArgs): void {
if (args.item.text === 'Delete') {
// Show confirmation dialog before deleting
const confirmDelete = confirm('Are you sure you want to delete this item?');
if (confirmDelete) {
this.performDelete();
}
}
}
private performDelete(): void {
console.log('Item deleted');
}
```
## Complete Event Reference
### Event: select
**Triggers:** While selecting menu item (when item is clicked).
**Signature:** `(select)='onSelect($event)'`
**Event Arguments - MenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event. Value: `'select'`. |
| `event` | `Event` | Original browser event object (click event). Contains properties like `type`, `timeStamp`, `target`. |
| `item` | `MenuItemModel` | The menu item that was selected/clicked. Contains all item properties (text, id, iconCss, etc.). |
| `element` | `HTMLElement` | The DOM element of the menu item that was clicked. Can be used for styling or accessing attributes. |
**MenuEventArgs Interface (Complete):**
```typescript
interface MenuEventArgs {
name: string; // 'select' | 'beforeItemRender'
event: Event; // Browser event
item: MenuItemModel; // Selected menu item
element: HTMLElement; // DOM element of item
element.textContent: string; // Item text
element.innerHTML: string; // Item HTML
element.classList: DOMTokenList; // CSS classes
}
```
**Brief Example:**
```typescript
onSelect(args: MenuEventArgs) {
console.log('Selected:', args.item.text);
}
```
**Full Example - Complete Event Handling:**
```typescript
onSelect(args: MenuEventArgs): void {
// Item information
const itemText = args.item.text;
const itemId = args.item.id;
const itemElement = args.element;
// Event information
const eventType = args.event.type; // e.g., 'click'
const timestamp = (args.event as any).timeStamp;
// Element information
const classes = itemElement.classList;
const html = itemElement.innerHTML;
// Event target (the element that triggered the menu)
const targetElement = args.event.target as HTMLElement;
console.log({
itemText,
itemId,
eventType,
timestamp,
classes: Array.from(classes),
targetElementId: targetElement.id
});
}
```
### Event: beforeOpen
**Triggers:** Before opening the menu item.
**Signature:** `(beforeOpen)='onBeforeOpen($event)'`
**Event Arguments - BeforeOpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event. Value: `'beforeOpen'`. |
| `event` | `Event` | Original browser event object (contextmenu or mousedown event) that triggered the menu opening. |
**BeforeOpenCloseMenuEventArgs Interface (Complete):**
```typescript
interface BeforeOpenCloseMenuEventArgs {
name: string; // 'beforeOpen' | 'beforeClose'
event: Event; // Browser event that triggered opening/closing
// Additional properties may be available depending on Syncfusion version
}
```
**Brief Example:**
```typescript
onBeforeOpen(args: BeforeOpenCloseMenuEventArgs) {
console.log('Menu about to open');
}
```
**Full Example - Dynamic Menu Modification:**
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, BeforeOpenCloseMenuEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
selector: 'app-before-open',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu
#cm
target='#target'
[items]='menuItems'
(beforeOpen)='onBeforeOpen($event)'>
</ejs-contextmenu>
`
})
export class BeforeOpenComponent {
@ViewChild('cm') contextMenu!: ContextMenuComponent;
menuItems: MenuItemModel[] = [
{ text: 'Edit', id: 'edit' },
{ text: 'Delete', id: 'delete' },
{ text: 'Admin Only', id: 'admin' }
];
isAdmin = false;
onBeforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
console.log('Event name:', args.name); // 'beforeOpen'
// Access target element
const targetElement = args.event.target as HTMLElement;
console.log('Menu opening on element:', targetElement.id);
// Dynamically show/hide items based on context
if (this.isAdmin) {
this.contextMenu.showItems(['admin'], true);
} else {
this.contextMenu.hideItems(['admin'], true);
}
}
}
```
**Use Case - Context-Aware Menu:**
```typescript
onBeforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
const targetElement = args.event.target as HTMLElement;
// Show different menu items based on target element
if (targetElement.classList.contains('protected')) {
this.contextMenu.hideItems(['Delete', 'Edit'], true);
} else {
this.contextMenu.showItems(['Delete', 'Edit'], true);
}
}
```
### Event: beforeClose
**Triggers:** Before closing the menu.
**Signature:** `(beforeClose)='onBeforeClose($event)'`
**Event Arguments - BeforeOpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event. Value: `'beforeClose'`. |
| `event` | `Event` | Browser event that triggered the closing. |
**Brief Example:**
```typescript
onBeforeClose(args: BeforeOpenCloseMenuEventArgs) {
console.log('Menu about to close');
}
```
**Full Example - State Cleanup:**
```typescript
onBeforeClose(args: BeforeOpenCloseMenuEventArgs): void {
console.log('Cleaning up before menu closes');
// Save menu state
this.previousMenuState = 'closed';
// Reset highlight
this.highlightedItem = null;
}
```
### Event: onOpen
**Triggers:** While opening the menu item (after menu is displayed).
**Signature:** `(onOpen)='onOpen($event)'`
**Event Arguments - OpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event. Value: `'onOpen'`. |
| `event` | `Event` | Browser event that triggered the opening. |
**OpenCloseMenuEventArgs Interface (Complete):**
```typescript
interface OpenCloseMenuEventArgs {
name: string; // 'onOpen' | 'onClose'
event: Event; // Browser event
// Additional properties may be available depending on Syncfusion version
}
```
**Brief Example:**
```typescript
onOpen(args: OpenCloseMenuEventArgs) {
console.log('Menu opened:', args.name);
}
```
**Full Example - Menu State Tracking:**
```typescript
@Component({
selector: 'app-menu-state',
template: `
<div>Menu Status: {{ menuStatus }}</div>
<div id="target">Right click here</div>
<ejs-contextmenu
target='#target'
[items]='items'
(onOpen)='onOpen($event)'
(onClose)='onClose($event)'>
</ejs-contextmenu>
`
})
export class MenuStateComponent {
menuStatus = 'Closed';
items: MenuItemModel[] = [{ text: 'Item 1' }];
onOpen(args: OpenCloseMenuEventArgs) {
this.menuStatus = 'Open - Event: ' + args.name;
console.log('Menu displayed at:', new Date().toLocaleTimeString());
}
onClose(args: OpenCloseMenuEventArgs) {
this.menuStatus = 'Closed - Event: ' + args.name;
}
}
```
### Event: onClose
**Triggers:** While closing the menu (after menu is hidden).
**Signature:** `(onClose)='onClose($event)'`
**Event Arguments - OpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event. Value: `'onClose'`. |
| `event` | `Event` | Browser event that triggered the closing. |
**Brief Example:**
```typescript
onClose(args: OpenCloseMenuEventArgs) {
console.log('Menu closed:', args.name);
}
```
### Event: beforeItemRender
**Triggers:** While rendering each menu item (before each item is displayed).
**Signature:** `(beforeItemRender)='onBeforeItemRender($event)'`
**Event Arguments - MenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event. Value: `'beforeItemRender'`. |
| `event` | `Event` | Browser event associated with rendering. |
| `item` | `MenuItemModel` | The menu item being rendered. |
| `element` | `HTMLElement` | The DOM element of the item being rendered. |
**Brief Example:**
```typescript
onBeforeItemRender(args: MenuEventArgs) {
console.log('Rendering item:', args.item.text);
}
```
**Full Example - Item Customization:**
```typescript
onBeforeItemRender(args: MenuEventArgs): void {
// Customize based on item properties
if (args.item.disabled) {
args.element.classList.add('disabled-item');
args.element.style.opacity = '0.5';
}
// Add custom styling to specific items
if (args.item.text === 'Delete') {
args.element.classList.add('danger-item');
args.element.style.color = 'red';
}
// Highlight admin items
if ((args.item as any).role === 'admin') {
args.element.classList.add('admin-badge');
args.element.setAttribute('data-admin', 'true');
}
}
```
**Use Case - Dynamic Content Rendering:**
```typescript
onBeforeItemRender(args: MenuEventArgs): void {
// Add keyboard shortcut hints
const shortcuts = {
'Cut': '(Ctrl+X)',
'Copy': '(Ctrl+C)',
'Paste': '(Ctrl+V)',
'Undo': '(Ctrl+Z)',
'Redo': '(Ctrl+Y)'
};
if (shortcuts[args.item.text!]) {
const shortcut = shortcuts[args.item.text!];
args.element.innerHTML += `<span style="float: right; color: gray; font-size: 0.9em;">${shortcut}</span>`;
}
}
```
### Event: created
**Triggers:** Once the component rendering is completed (after initialization).
**Signature:** `(created)='onCreated()'` or `(created)='onCreated($event)'`
**Event Arguments:**
| Property | Type | Description |
|----------|------|-------------|
| - | `Event` | Standard JavaScript Event object. |
**Brief Example:**
```typescript
onCreated() {
console.log('ContextMenu component created and ready');
}
```
**Full Example - Component Initialization:**
```typescript
import { Component, ViewChild, OnInit } from '@angular/core';
import { ContextMenuComponent } from '@syncfusion/ej2-angular-navigations';
@Component({
selector: 'app-created-event',
template: `
<div>Status: {{ componentStatus }}</div>
<div id="target">Right click here</div>
<ejs-contextmenu
#cm
target='#target'
[items]='items'
(created)='onCreated()'>
</ejs-contextmenu>
`
})
export class CreatedEventComponent implements OnInit {
@ViewChild('cm') contextMenu!: ContextMenuComponent;
items: MenuItemModel[] = [{ text: 'Item 1' }];
componentStatus = 'Initializing...';
ngOnInit() {
// Note: created event fires during initialization
}
onCreated() {
this.componentStatus = 'Ready';
console.log('Component fully initialized');
// Now safe to access component methods
console.log('Total items:', this.contextMenu.items?.length);
}
}
```
## Event Differences: before* vs on* Events
Understanding the timing of different events:
| Event | Timing | Purpose | Can Prevent |
|-------|--------|---------|-------------|
| `beforeOpen` | Before menu appears | Prepare menu, modify items | Yes (in some cases) |
| `onOpen` | After menu is visible | React to menu opening | No |
| `beforeClose` | Before menu disappears | Save state, cleanup | Yes (in some cases) |
| `onClose` | After menu is hidden | React to menu closing | No |
| `select` | When item clicked | Handle selection | Depends on item action |
| `beforeItemRender` | Before each item renders | Customize appearance | No |
| `created` | After component initialized | Setup component | No |
**Visual Timeline:**
```
Right-click occurs
↓
beforeOpen ← Can modify menu here
↓
[Menu displays]
↓
onOpen ← React to visible menu
↓
User clicks item
↓
select ← Handle selection
↓
beforeClose ← Cleanup before closing
↓
[Menu hides]
↓
onClose ← React to hidden menu
```
## MenuEventArgs Complete Reference
The `MenuEventArgs` object provides comprehensive event information when items are selected or rendered:
```typescript
interface MenuEventArgs {
name: string; // Event name ('select' or 'beforeItemRender')
event: Event; // Browser event (click, render, etc.)
item: MenuItemModel; // Menu item involved in the event
{
text?: string; // Item display text
id?: string; // Item unique id
items?: MenuItemModel[]; // Sub-items
iconCss?: string; // Icon CSS class
url?: string; // Navigation URL
separator?: boolean; // Is separator
htmlAttributes?: Record<string, any>; // Custom HTML attributes
[key: string]: any; // Other properties
}
element: HTMLElement; // DOM element of the item
{
textContent: string; // Element text
innerHTML: string; // Element HTML
classList: DOMTokenList; // CSS classes
getAttribute(name: string): string; // Get attributes
setAttribute(name: string, value: string): void; // Set attributes
// ... other HTMLElement methods
}
}
```
### Practical Usage Examples
```typescript
onSelect(args: MenuEventArgs): void {
// Item information
const itemText = args.item.text;
const itemId = args.item.id;
const itemElement = args.element;
// Event information
const eventType = args.event.type; // e.g., 'click'
const timestamp = (args.event as any).timeStamp;
// Element information
const classes = itemElement.classList;
const html = itemElement.innerHTML;
// Event target (the element that triggered the menu)
const targetElement = args.event.target as HTMLElement;
console.log({
itemText,
itemId,
eventType,
timestamp,
classes: Array.from(classes),
targetElementId: targetElement.id
});
}
```
### BeforeOpenCloseMenuEventArgs Complete Reference
The `BeforeOpenCloseMenuEventArgs` object is used for `beforeOpen` and `beforeClose` events:
```typescript
interface BeforeOpenCloseMenuEventArgs {
name: string; // 'beforeOpen' or 'beforeClose'
event: Event; // Browser event that triggered the action
{
type: string; // Event type ('contextmenu', 'mousedown', etc.)
target: HTMLElement; // Element that triggered the event
preventDefault(): void; // Can prevent default behavior
stopPropagation(): void; // Can stop event bubbling
}
}
```
### OpenCloseMenuEventArgs Complete Reference
The `OpenCloseMenuEventArgs` object is used for `onOpen` and `onClose` events:
```typescript
interface OpenCloseMenuEventArgs {
name: string; // 'onOpen' or 'onClose'
event: Event; // Browser event
}
```
## Complete Example: Interactive Menu with Events
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { BeforeOpenCloseMenuEventArgs } from '@syncfusion/ej2-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<div style="margin-top: 20px;">
<p>Last Action: {{ lastAction }}</p>
<p>Menu State: {{ menuState }}</p>
</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
(created)='onCreated()'
(beforeOpen)='onBeforeOpen($event)'
(select)='onSelect($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{ text: 'New', iconCss: 'e-icons e-new' },
{ text: 'Open', iconCss: 'e-icons e-open' },
{ separator: true },
{ text: 'Save', iconCss: 'e-icons e-save' },
{ text: 'Save As...' }
];
public lastAction = 'None';
public menuState = 'Closed';
onCreated(): void {
console.log('ContextMenu initialized');
this.lastAction = 'Component Created';
}
onBeforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
this.menuState = 'Opening...';
console.log('Menu opening');
}
onSelect(args: MenuEventArgs): void {
this.lastAction = `Selected: ${args.item.text}`;
this.menuState = 'Closed';
console.log(`User clicked: ${args.item.text}`);
// Execute action based on selection
this.executeAction(args.item.text);
}
private executeAction(itemText?: string): void {
switch (itemText) {
case 'New':
console.log('Creating new file');
break;
case 'Open':
console.log('Opening file');
break;
case 'Save':
console.log('Saving file');
break;
case 'Save As...':
console.log('Saving file with new name');
break;
}
}
}
```
---
**Next:** Customize menu appearance, animations, and styling in [references/styling-and-customization.md](../styling-and-customization.md).
references/menu-items-management.md
# Menu Items Management
## Table of Contents
- [Adding Menu Items Dynamically](#adding-menu-items-dynamically)
- [Removing Menu Items](#removing-menu-items)
- [Enabling and Disabling Items](#enabling-and-disabling-items)
- [Showing and Hiding Items](#showing-and-hiding-items)
- [Dynamic Context-Aware Menus](#dynamic-context-aware-menus)
- [Multi-Level Nested Menus](#multi-level-nested-menus)
## Adding Menu Items Dynamically
### insertAfter() Method
Add new menu items after a specified target item:
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuItemModel } from '@syncfusion/ej2-angular-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
(created)='onCreated()'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{ text: 'View' },
{ text: 'Refresh' },
{ text: 'New' }
];
onCreated(): void {
// Insert 'Sort By' after 'Refresh'
(this.contextmenu as ContextMenuComponent).insertAfter(
[{ text: 'Sort By' }],
'Refresh'
);
}
}
```
### insertBefore() Method
Add new menu items before a specified target item:
```typescript
onCreated(): void {
// Insert 'Display Settings' before 'Personalize'
(this.contextmenu as ContextMenuComponent).insertBefore(
[{ text: 'Display Settings' }],
'Personalize'
);
}
```
### Multiple Items Insertion
Insert multiple items at once:
```typescript
onCreated(): void {
const newItems: MenuItemModel[] = [
{ text: 'Sort By Name' },
{ text: 'Sort By Date' },
{ text: 'Sort By Size' }
];
(this.contextmenu as ContextMenuComponent).insertAfter(
newItems,
'Refresh'
);
}
```
## Removing Menu Items
### removeItems() Method
Remove specified items from the context menu:
```typescript
onCreated(): void {
// Remove single item by text
(this.contextmenu as ContextMenuComponent).removeItems(['Paste']);
}
```
### Remove Multiple Items
```typescript
onCreated(): void {
const itemsToRemove = ['Cut', 'Copy', 'Paste'];
(this.contextmenu as ContextMenuComponent).removeItems(itemsToRemove);
}
```
### Remove Based on Condition
```typescript
removeUnauthorizedItems(userRole: string): void {
if (userRole === 'viewer') {
(this.contextmenu as ContextMenuComponent).removeItems([
'Delete',
'Edit',
'Rename'
]);
}
}
```
## Enabling and Disabling Items
### enableItems() Method
Control item availability based on application state:
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuItemModel } from '@syncfusion/ej2-angular-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
(created)='onCreated()'
(beforeOpen)='beforeOpen()'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{
text: 'View',
items: [
{ text: 'Large icons' },
{ text: 'Medium icons' },
{ text: 'Small icons' }
]
},
{ text: 'Refresh' },
{ separator: true },
{ text: 'New' }
];
onCreated(): void {
// Disable parent item 'View'
(this.contextmenu as ContextMenuComponent).enableItems(
['View'],
false
);
}
beforeOpen(): void {
// Disable sub-item 'Medium icons' on menu open
(this.contextmenu as ContextMenuComponent).enableItems(
['Medium icons'],
false
);
}
}
```
### Re-enable Disabled Items
```typescript
// Enable previously disabled items
(this.contextmenu as ContextMenuComponent).enableItems(
['Edit', 'Delete'],
true // enable = true
);
```
### Conditional Item State
```typescript
updateItemState(canEdit: boolean): void {
(this.contextmenu as ContextMenuComponent).enableItems(
['Edit', 'Cut', 'Delete'],
canEdit
);
}
```
## Showing and Hiding Items
### hideItems() Method
Hide menu items dynamically without removing them:
```typescript
beforeOpen(args: BeforeOpenCloseMenuEventArgs) {
// Hide specific items
(this.contextmenu as ContextMenuComponent).hideItems([
'Cut',
'Copy',
'Paste'
]);
}
```
### showItems() Method
Display previously hidden items:
```typescript
beforeOpen(args: BeforeOpenCloseMenuEventArgs) {
// Show specific items
(this.contextmenu as ContextMenuComponent).showItems([
'Save',
'Export'
]);
}
```
## Dynamic Context-Aware Menus
### Show Different Menus Based on Target
Display different menu items depending on which element was right-clicked:
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { BeforeOpenCloseMenuEventArgs } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">
<div id='clipboard' class='e-div'>Clipboard Area</div>
<div id='editor' class='e-div'>Editor Area</div>
</div>
<ejs-contextmenu
#contextmenu
target='#target .e-div'
[items]='menuItems'
(beforeOpen)='beforeOpen($event)'>
</ejs-contextmenu>
</div>
`,
styles: [`
.e-div {
padding: 20px;
border: 1px solid #ccc;
margin: 10px;
height: 100px;
}
`]
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' },
{ text: 'Add' },
{ text: 'Edit' },
{ text: 'Delete' }
];
beforeOpen(args: BeforeOpenCloseMenuEventArgs) {
const targetId = (args.event.target as HTMLElement).id;
if (targetId === 'clipboard') {
// Show clipboard operations
(this.contextmenu as ContextMenuComponent).showItems([
'Cut',
'Copy',
'Paste'
]);
(this.contextmenu as ContextMenuComponent).hideItems([
'Add',
'Edit',
'Delete'
]);
} else if (targetId === 'editor') {
// Show editor operations
(this.contextmenu as ContextMenuComponent).showItems([
'Add',
'Edit',
'Delete'
]);
(this.contextmenu as ContextMenuComponent).hideItems([
'Cut',
'Copy',
'Paste'
]);
}
}
}
```
### Role-Based Menu Items
```typescript
beforeOpen(args: BeforeOpenCloseMenuEventArgs) {
const userRole = this.getUserRole();
if (userRole === 'admin') {
(this.contextmenu as ContextMenuComponent).showItems([
'Delete',
'Export',
'Manage Users'
]);
} else if (userRole === 'editor') {
(this.contextmenu as ContextMenuComponent).showItems([
'Edit',
'Copy'
]);
(this.contextmenu as ContextMenuComponent).hideItems(['Delete']);
} else {
(this.contextmenu as ContextMenuComponent).hideItems([
'Delete',
'Edit',
'Export'
]);
}
}
private getUserRole(): string {
// Fetch user role from service
return 'editor';
}
```
## Multi-Level Nested Menus
### Creating Nested Structure
Define menu items with multiple nesting levels:
```typescript
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{
text: 'New',
items: [
{ text: 'Document' },
{ text: 'Project' }
]
},
{ text: 'Open' },
{ text: 'Save' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
}
];
```
### Managing Nested Items
Access and manipulate items at any level:
```typescript
// Get reference to nested items
const viewItems = this.menuItems.find(m => m.text === 'View')?.items;
// Add nested item
this.contextmenu.insertAfter(
[{ text: 'Compact View' }],
'Large Icons'
);
```
### Dynamic Nested Menus
```typescript
addDynamicSubmenu(parentItem: string, newSubItem: MenuItemModel): void {
const parent = this.menuItems.find(m => m.text === parentItem);
if (parent) {
if (!parent.items) {
parent.items = [];
}
parent.items.push(newSubItem);
}
}
```
### Example: Nested Submenu Filtering
```typescript
beforeOpen(args: BeforeOpenCloseMenuEventArgs) {
// Disable certain sub-items based on conditions
if (this.isReadOnly) {
(this.contextmenu as ContextMenuComponent).enableItems(
['Delete', 'Edit'],
false
);
}
}
```
## Complete Example: Advanced Menu Management
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuItemModel, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { BeforeOpenCloseMenuEventArgs } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<button (click)="addItem()">Add Item</button>
<button (click)="removeItem()">Remove Item</button>
<button (click)="toggleItemState()">Toggle State</button>
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
(created)='onCreated()'
(beforeOpen)='beforeOpen($event)'
(select)='onSelect($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ separator: true },
{ text: 'Edit' },
{ text: 'Delete' }
];
private isDeleteEnabled = true;
onCreated(): void {
console.log('Context menu created');
}
beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
// Dynamic state management
if (!this.isDeleteEnabled) {
(this.contextmenu as ContextMenuComponent).enableItems(['Delete'], false);
}
}
addItem(): void {
(this.contextmenu as ContextMenuComponent).insertAfter(
[{ text: 'Print' }],
'Open'
);
}
removeItem(): void {
(this.contextmenu as ContextMenuComponent).removeItems(['Print']);
}
toggleItemState(): void {
this.isDeleteEnabled = !this.isDeleteEnabled;
(this.contextmenu as ContextMenuComponent).enableItems(
['Delete'],
this.isDeleteEnabled
);
}
onSelect(args: MenuEventArgs): void {
console.log('Selected:', args.item.text);
}
}
```
---
**Next:** Learn how to bind menu items from data sources in [references/data-binding.md](../data-binding.md).
references/styling-and-customization.md
# Styling & Customization
## Table of Contents
- [Animation Settings](#animation-settings)
- [CSS Customization](#css-customization)
- [Icon Styling](#icon-styling)
- [URL Navigation](#url-navigation)
- [Scrollable Menus](#scrollable-menus)
- [Responsive Design](#responsive-design)
## Animation Settings
### Supported Animation Effects
The ContextMenu supports four animation effects:
| Effect | Behavior |
|--------|----------|
| **None** | No animation, menu appears instantly |
| **SlideDown** | Menu slides down from top (default) |
| **ZoomIn** | Menu scales from small to full size |
| **FadeIn** | Menu fades in gradually |
### Basic Animation Configuration
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, MenuAnimationSettingsModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
[animationSettings]='animationSettings'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
];
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 400
};
}
```
### Animation Options
```typescript
// SlideDown effect (default)
public animationSettings1 = {
effect: 'SlideDown',
duration: 400,
easing: 'ease'
};
// ZoomIn effect
public animationSettings2 = {
effect: 'ZoomIn',
duration: 600,
easing: 'ease-out'
};
// FadeIn effect
public animationSettings3 = {
effect: 'FadeIn',
duration: 300,
easing: 'linear'
};
// No animation
public animationSettings4 = {
effect: 'None',
duration: 0
};
```
### Custom Duration and Easing
```typescript
public animationSettings = {
effect: 'SlideDown',
duration: 800, // 800ms animation time
easing: 'ease-in-out'
};
```
### Complete Animation Example
```typescript
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<label>Select Animation:</label>
<select [(ngModel)]='selectedEffect' (change)='updateAnimation()'>
<option>SlideDown</option>
<option>ZoomIn</option>
<option>FadeIn</option>
<option>None</option>
</select>
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
[animationSettings]='animationSettings'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public selectedEffect = 'SlideDown';
public animationSettings: MenuAnimationSettingsModel = {
effect: 'SlideDown',
duration: 400
};
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
];
updateAnimation(): void {
this.animationSettings.effect = this.selectedEffect as any;
}
}
```
## CSS Customization
### CSS Classes Reference
Target specific menu elements with these classes:
```css
/* Main wrapper */
.e-contextmenu-wrapper {
background-color: #fff;
border: 1px solid #ddd;
}
/* Menu item */
.e-contextmenu-wrapper .e-menu-parent {
color: #333;
}
/* Menu item on hover */
.e-contextmenu-wrapper ul .e-menu-item:hover {
background-color: #f0f0f0;
}
/* Selected item */
.e-contextmenu-wrapper ul .e-menu-item.e-selected {
background-color: #007bff;
color: #fff;
}
/* context menu caret icon */
.e-contextmenu-wrapper ul .e-menu-item.e-selected .e-caret::before {
color: inherit;
}
/* Caret/Arrow for submenus */
.e-contextmenu-wrapper ul .e-menu-item .e-menu-icon::before {
color: inherit;
}
/* Disabled item */
.e-contextmenu-wrapper ul .e-menu-item.e-disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Separator line */
.e-contextmenu-wrapper .e-separator {
border-bottom: 1px solid #ddd;
margin: 5px 0;
}
```
### Custom Theme Example
```typescript
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'>
</ejs-contextmenu>
</div>
`,
styles: [`
:host ::ng-deep .e-contextmenu-wrapper {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item {
color: #fff;
padding: 12px 16px;
font-weight: 500;
}
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item:hover {
background-color: rgba(255,255,255,0.2);
}
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item.e-selected {
background-color: rgba(255,255,255,0.3);
}
:host ::ng-deep .e-separator {
background-color: rgba(255,255,255,0.3);
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ separator: true },
{ text: 'Save' }
];
}
```
## Icon Styling
### Adding Icons with iconCss
Use the `iconCss` property to add icons from Syncfusion icon library:
```typescript
public menuItems: MenuItemModel[] = [
{ text: 'Cut', iconCss: 'e-cm-icons e-cut' },
{ text: 'Copy', iconCss: 'e-cm-icons e-copy' },
{ text: 'Paste', iconCss: 'e-cm-icons e-paste' },
{ separator: true },
{ text: 'Select All', iconCss: 'e-icons e-select-all' }
];
```
### Common Syncfusion Icons
```typescript
// File operations
{ text: 'New', iconCss: 'e-icons e-new' }
{ text: 'Open', iconCss: 'e-icons e-open' }
{ text: 'Save', iconCss: 'e-icons e-save' }
{ text: 'Delete', iconCss: 'e-icons e-delete' }
// Edit operations
{ text: 'Cut', iconCss: 'e-cm-icons e-cut' }
{ text: 'Copy', iconCss: 'e-cm-icons e-copy' }
{ text: 'Paste', iconCss: 'e-cm-icons e-paste' }
{ text: 'Undo', iconCss: 'e-icons e-undo' }
{ text: 'Redo', iconCss: 'e-icons e-redo' }
// Navigation
{ text: 'Back', iconCss: 'e-icons e-back' }
{ text: 'Forward', iconCss: 'e-icons e-forward' }
{ text: 'Refresh', iconCss: 'e-icons e-refresh' }
// Other
{ text: 'Settings', iconCss: 'e-icons e-settings' }
{ text: 'Help', iconCss: 'e-icons e-help' }
{ text: 'Info', iconCss: 'e-icons e-info' }
```
### Custom Icon Classes
Define custom icon classes in your CSS:
```typescript
@Component({
template: `<ejs-contextmenu [items]='menuItems'></ejs-contextmenu>`,
styles: [`
.custom-icon-download::before {
content: '⬇️';
}
.custom-icon-upload::before {
content: '⬆️';
}
.custom-icon-share::before {
content: '↗️';
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Download', iconCss: 'custom-icon-download' },
{ text: 'Upload', iconCss: 'custom-icon-upload' },
{ text: 'Share', iconCss: 'custom-icon-share' }
];
}
```
## URL Navigation
### Navigate to External URLs
Configure menu items to navigate to URLs:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='onBeforeItemRender($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Flipkart', iconCss: 'e-cart-icon e-link', url: 'https://www.flipkart.com' },
{ text: 'Amazon', iconCss: 'e-cart-icon e-link', url: 'https://www.amazon.com' },
{ separator: true },
{ text: 'GitHub', iconCss: 'e-code-icon e-link', url: 'https://github.com' }
];
onBeforeItemRender(args: MenuEventArgs): void {
if (args.item.url) {
// Make URL items clickable
args.element.style.cursor = 'pointer';
}
}
}
```
### Navigate via Event Handler
```typescript
onSelect(args: MenuEventArgs): void {
if (args.item.url) {
// Open in new tab
window.open(args.item.url, '_blank');
}
}
```
### Navigate to Routes
```typescript
import { Router } from '@angular/router';
export class AppComponent {
constructor(private router: Router) {}
onSelect(args: MenuEventArgs): void {
switch (args.item.text) {
case 'Dashboard':
this.router.navigate(['/dashboard']);
break;
case 'Settings':
this.router.navigate(['/settings']);
break;
case 'Profile':
this.router.navigate(['/profile']);
break;
}
}
}
```
## Scrollable Menus
### enableScrolling Property
Enable scrolling for menus with many items:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
[enableScrolling]='true'
(beforeOpen)='beforeOpen($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Option 1' },
{ text: 'Option 2' },
{ text: 'Option 3' },
{ text: 'Option 4' },
{ text: 'Option 5' },
{ text: 'Option 6' },
{ text: 'Option 7' },
{ text: 'Option 8' },
{ text: 'Option 9' },
{ text: 'Option 10' }
];
beforeOpen(args: MenuEventArgs): void {
if (args.element && args.element.parentElement) {
// Set max height for scrollable area
args.element.parentElement.style.height = '200px';
}
}
}
```
### Setting Menu Container Height
```typescript
beforeOpen(args: MenuEventArgs): void {
if (args.element && args.element.parentElement) {
args.element.parentElement.style.maxHeight = '300px';
args.element.parentElement.style.overflowY = 'auto';
}
}
```
## Responsive Design
### Mobile-Friendly Configuration
Adapt menu behavior for different devices:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { Browser } from '@syncfusion/ej2-base';
import { MenuItemModel, MenuAnimationSettingsModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
[animationSettings]='animationSettings'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
];
public animationSettings: MenuAnimationSettingsModel;
constructor() {
// Adjust animation for mobile devices
if (Browser.isDevice) {
this.animationSettings = {
effect: 'ZoomIn',
duration: 200
};
} else {
this.animationSettings = {
effect: 'SlideDown',
duration: 400
};
}
}
}
```
### Responsive Styling
```typescript
@Component({
selector: 'app-root',
template: `<ejs-contextmenu [items]='menuItems'></ejs-contextmenu>`,
styles: [`
:host ::ng-deep .e-contextmenu-wrapper {
font-size: 14px;
}
/* Tablet screens */
@media (max-width: 768px) {
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item {
padding: 12px 16px;
font-size: 16px;
}
}
/* Mobile screens */
@media (max-width: 480px) {
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item {
padding: 16px 20px;
font-size: 18px;
}
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
];
}
```
## Complete Example: Advanced Customization
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, MenuAnimationSettingsModel } from '@syncfusion/ej2-navigations';
import { Browser } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
[animationSettings]='animationSettings'
[enableScrolling]='true'
(beforeOpen)='beforeOpen($event)'
(select)='onSelect($event)'>
</ejs-contextmenu>
</div>
`,
styles: [`
:host ::ng-deep .e-contextmenu-wrapper {
background: #f8f9fa;
border: 2px solid #dee2e6;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item {
color: #212529;
padding: 10px 16px;
transition: all 0.2s ease;
}
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item:hover {
background-color: #e7f1ff;
color: #0066cc;
}
:host ::ng-deep .e-contextmenu-wrapper ul .e-menu-item.e-selected {
background-color: #0066cc;
color: #fff;
}
:host ::ng-deep .e-separator {
background-color: #dee2e6;
margin: 5px 0;
}
`]
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{ text: 'New', iconCss: 'e-icons e-new' },
{ text: 'Open', iconCss: 'e-icons e-open' },
{ text: 'Save', iconCss: 'e-icons e-save' },
{ separator: true },
{ text: 'Print', iconCss: 'e-icons e-print' },
{ text: 'Settings', iconCss: 'e-icons e-settings' }
];
public animationSettings: MenuAnimationSettingsModel = Browser.isDevice
? { effect: 'ZoomIn', duration: 200 }
: { effect: 'SlideDown', duration: 400 };
beforeOpen(args: MenuEventArgs): void {
if (args.element && args.element.parentElement) {
args.element.parentElement.style.maxHeight = '300px';
}
}
onSelect(args: MenuEventArgs): void {
console.log(`Selected: ${args.item.text}`);
}
}
```
---
**Next:** Create custom templates and advanced features in [references/templates-and-advanced.md](../templates-and-advanced.md).
references/templates-and-advanced.md
# Templates & Advanced Features
## Table of Contents
- [Custom Item Templates](#custom-item-templates)
- [Rich Content with HTML](#rich-content-with-html)
- [Table Templates](#table-templates)
- [Character Underlining](#character-underlining)
- [Separator Items](#separator-items)
- [Accessibility & Keyboard Navigation](#accessibility--keyboard-navigation)
## Custom Item Templates
### itemTemplate Property
The `itemTemplate` property allows custom HTML rendering for each menu item:
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
import { Browser } from '@syncfusion/ej2-base';
import { ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
itemTemplate='itemTemplate'
[animationSettings]='animationSettings'>
</ejs-contextmenu>
<ng-template #itemTemplate let-data="data">
<div class="custom-item">
<span class="item-icon">{{ data.answerType }}</span>
<div class="item-content">
<div class="item-title">{{ data.answerType }}</div>
<div class="item-description">{{ data.description }}</div>
</div>
</div>
</ng-template>
</div>
`,
styles: [`
.custom-item {
display: flex;
align-items: center;
padding: 8px 0;
gap: 12px;
}
.item-icon {
font-size: 20px;
min-width: 30px;
text-align: center;
}
.item-content {
flex: 1;
}
.item-title {
font-weight: 600;
color: #333;
}
.item-description {
font-size: 12px;
color: #999;
margin-top: 2px;
}
`]
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public animationSettings = {
effect: Browser.isDevice ? 'ZoomIn' : 'SlideDown',
duration: 400
};
public menuItems: any[] = [
{
answerType: 'Selection',
description: 'Choose from options'
},
{
answerType: 'Yes / No',
description: 'Select Yes or No'
},
{
answerType: 'Text',
description: 'Type own answer'
}
];
onCreated(): void {
if (Browser.isDevice) {
this.animationSettings.effect = 'ZoomIn';
} else {
this.animationSettings.effect = 'SlideDown';
}
}
}
```
## Rich Content with HTML
### HTML Content in Items
Use `beforeItemRender` to inject custom HTML:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='onItemRender($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut', id: 'cut' },
{ text: 'Copy', id: 'copy' },
{ text: 'Paste', id: 'paste' }
];
onItemRender(args: MenuEventArgs): void {
if (args.item.id === 'cut') {
args.element.innerHTML = `
<div class="menu-item-custom">
<strong>Cut</strong>
<small style="color: #999;">Ctrl+X</small>
</div>
`;
} else if (args.item.id === 'copy') {
args.element.innerHTML = `
<div class="menu-item-custom">
<strong>Copy</strong>
<small style="color: #999;">Ctrl+C</small>
</div>
`;
}
}
}
```
### Complex HTML Structures
```typescript
onItemRender(args: MenuEventArgs): void {
if (args.item.text === 'Format') {
args.element.innerHTML = `
<div style="padding: 8px; background: #f5f5f5; border-radius: 4px;">
<div style="font-weight: bold; margin-bottom: 4px;">Format Options</div>
<div style="font-size: 12px; color: #666;">
Choose text formatting
</div>
</div>
`;
}
}
```
## Table Templates
### Show Table in Sub ContextMenu
Create table layouts within menu items:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
import { createCheckBox } from '@syncfusion/ej2-buttons';
import { closest } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='onItemRender($event)'>
</ejs-contextmenu>
</div>
`,
styles: [`
:host ::ng-deep table {
width: 100%;
border-collapse: collapse;
margin: 10px 0;
}
:host ::ng-deep td {
border: 1px solid #ddd;
padding: 8px;
cursor: pointer;
width: 30px;
height: 30px;
text-align: center;
}
:host ::ng-deep td:hover {
background-color: #007bff;
color: white;
}
:host ::ng-deep h4 {
margin: 10px 0 5px 0;
}
:host ::ng-deep .bg-transparent {
background-color: transparent !important;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut', iconCss: 'e-cm-icons e-cut' },
{ text: 'Copy', iconCss: 'e-cm-icons e-copy' },
{ separator: true },
{
text: 'Table',
iconCss: 'e-icons e-table',
items: [
{ id: 'table' }
]
}
];
onItemRender(args: MenuEventArgs): void {
if (args.item.id === 'table') {
args.element.classList.add('bg-transparent');
args.element.appendChild(this.createHeader());
args.element.appendChild(this.createTable());
}
}
private createHeader(): HTMLElement {
const header = document.createElement('h4');
header.textContent = 'Insert Table';
return header;
}
private createTable(): HTMLElement {
const table = document.createElement('table');
// Create 5x5 grid
for (let i = 0; i < 5; i++) {
const row = document.createElement('tr');
for (let j = 0; j < 5; j++) {
const cell = document.createElement('td');
cell.textContent = '';
cell.addEventListener('click', (e) => {
console.log(`Selected: ${i + 1} x ${j + 1} table`);
e.stopPropagation();
});
row.appendChild(cell);
}
table.appendChild(row);
}
return table;
}
}
```
## Character Underlining
### Underline Specific Characters
Use `beforeItemRender` to underline keyboard shortcuts:
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold</div>
<ejs-contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='onItemRender($event)'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
];
onItemRender(args: MenuEventArgs): void {
// Underline first character
if (args.item.text === 'Cut') {
args.element.innerHTML = '<u>C</u>ut';
} else if (args.item.text === 'Copy') {
args.element.innerHTML = '<u>C</u>opy';
} else if (args.item.text === 'Paste') {
args.element.innerHTML = '<u>P</u>aste';
}
}
}
```
### Advanced Character Formatting
```typescript
onItemRender(args: MenuEventArgs): void {
const text = args.item.text || '';
// Underline character after colon (e.g., "Save: S")
if (text.includes(':')) {
const [before, after] = text.split(':');
args.element.innerHTML = `
<span>${before}:</span>
<u style="margin-left: 8px;">${after}</u>
`;
}
}
```
## Separator Items
### Basic Separators
Use separator property to create visual dividers:
```typescript
public menuItems: MenuItemModel[] = [
{ text: 'New' },
{ text: 'Open' },
{ separator: true }, // Visual separator line
{ text: 'Save' },
{ text: 'Save As...' },
{ separator: true },
{ text: 'Exit' }
];
```
### Conditional Separators
```typescript
public get dynamicMenuItems(): MenuItemModel[] {
const items: MenuItemModel[] = [
{ text: 'Edit' },
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
];
// Add separator and delete option if not read-only
if (!this.isReadOnly) {
items.push({ separator: true });
items.push({ text: 'Delete' });
}
return items;
}
```
### Styled Separators
```typescript
@Component({
styles: [`
:host ::ng-deep .e-separator {
background: linear-gradient(90deg,
transparent 0%,
#999 50%,
transparent 100%);
height: 1px;
margin: 8px 0;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Option 1' },
{ separator: true },
{ text: 'Option 2' }
];
}
```
## Accessibility & Keyboard Navigation
### ARIA Attributes
Enhance accessibility with proper ARIA labels:
```typescript
onItemRender(args: MenuEventArgs): void {
if (args.element) {
// Add ARIA attributes
args.element.setAttribute('role', 'menuitem');
args.element.setAttribute('aria-label', args.item.text);
// Add keyboard indicator
if (args.item.text === 'Cut') {
args.element.setAttribute('aria-keyshortcuts', 'Ctrl+X');
}
}
}
```
### Keyboard Shortcut Display
```typescript
@Component({
template: `
<ejs-contextmenu
[items]='menuItems'
(beforeItemRender)='onItemRender($event)'>
</ejs-contextmenu>
`,
styles: [`
.shortcut {
float: right;
font-size: 12px;
color: #999;
margin-left: 16px;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut', id: 'cut' },
{ text: 'Copy', id: 'copy' },
{ text: 'Paste', id: 'paste' }
];
private shortcuts: { [key: string]: string } = {
'cut': 'Ctrl+X',
'copy': 'Ctrl+C',
'paste': 'Ctrl+V'
};
onItemRender(args: MenuEventArgs): void {
const shortcut = this.shortcuts[args.item.id as string];
if (shortcut) {
args.element.innerHTML = `
<div style="display: flex; justify-content: space-between; width: 100%;">
<span>${args.item.text}</span>
<span class="shortcut">${shortcut}</span>
</div>
`;
}
}
}
```
### Keyboard Event Handling
```typescript
@Component({
host: {
'(keydown)': 'onKeyDown($event)'
}
})
export class AppComponent {
private menuOpen = false;
private selectedIndex = -1;
onKeyDown(event: KeyboardEvent): void {
if (event.key === 'ArrowDown') {
this.selectNextItem();
event.preventDefault();
} else if (event.key === 'ArrowUp') {
this.selectPreviousItem();
event.preventDefault();
} else if (event.key === 'Enter') {
this.activateCurrentItem();
event.preventDefault();
} else if (event.key === 'Escape') {
this.closeMenu();
event.preventDefault();
}
}
private selectNextItem(): void {
// Navigation logic
console.log('Next item');
}
private selectPreviousItem(): void {
// Navigation logic
console.log('Previous item');
}
private activateCurrentItem(): void {
// Activate logic
console.log('Activate current');
}
private closeMenu(): void {
console.log('Close menu');
}
}
```
## Complete Example: Advanced Features
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent, ContextMenuModule, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
import { ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="e-section-control">
<div id="target">Right click / Touch hold for advanced menu</div>
<ejs-contextmenu
#contextmenu
target='#target'
[items]='menuItems'
(beforeItemRender)='onItemRender($event)'
(select)='onSelect($event)'>
</ejs-contextmenu>
</div>
`,
styles: [`
:host ::ng-deep .e-contextmenu-wrapper {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
}
:host ::ng-deep .e-menu-item-text {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
:host ::ng-deep .shortcut-hint {
font-size: 12px;
color: #aaa;
margin-left: 20px;
}
:host ::ng-deep .icon-label {
display: flex;
align-items: center;
gap: 8px;
}
`]
})
export class AppComponent {
@ViewChild('contextmenu')
public contextmenu?: ContextMenuComponent;
public menuItems: MenuItemModel[] = [
{ text: 'Cut', id: 'cut', iconCss: 'e-cm-icons e-cut' },
{ text: 'Copy', id: 'copy', iconCss: 'e-cm-icons e-copy' },
{ text: 'Paste', id: 'paste', iconCss: 'e-cm-icons e-paste' },
{ separator: true },
{ text: 'Select All', id: 'select-all', iconCss: 'e-icons e-select-all' },
{ separator: true },
{ text: 'Delete', id: 'delete', iconCss: 'e-icons e-delete' }
];
private shortcuts: { [key: string]: string } = {
'cut': 'Ctrl+X',
'copy': 'Ctrl+C',
'paste': 'Ctrl+V',
'select-all': 'Ctrl+A',
'delete': 'Del'
};
onItemRender(args: MenuEventArgs): void {
if (!args.item.text) return; // Skip separators
const shortcut = this.shortcuts[args.item.id as string];
if (shortcut) {
args.element.innerHTML = `
<div class="e-menu-item-text">
<span>${args.item.text}</span>
<span class="shortcut-hint">${shortcut}</span>
</div>
`;
}
// Add accessibility attributes
args.element.setAttribute('role', 'menuitem');
args.element.setAttribute('aria-label', args.item.text);
}
onSelect(args: MenuEventArgs): void {
console.log(`Action: ${args.item.text}`);
switch (args.item.id) {
case 'cut':
console.log('Cutting content...');
break;
case 'copy':
console.log('Copying content...');
break;
case 'paste':
console.log('Pasting content...');
break;
case 'select-all':
console.log('Selecting all...');
break;
case 'delete':
console.log('Deleting content...');
break;
}
}
}
```
---
**Skill Complete!** You now have comprehensive coverage of all ContextMenu features. For more advanced customization, refer to the Syncfusion documentation or explore the API reference.
SKILL.md
---
name: syncfusion-angular-context-menu
description: "Implement Syncfusion Angular ContextMenu component for right-click and touch-hold menus. Use this skill when user needs to create context menus, add/remove/enable menu items, handle menu clicks, customize animations, apply templates, handle data binding, trigger dialogs from menu items, show/hide items dynamically, add icons, create scrollable menus, or customize menu appearance."
metadata:
author: "Syncfusion Inc"
version: "34.1.29"
category: "Navigation Components"
---
# Implementing Syncfusion Angular ContextMenu
The **ContextMenu** is a graphical user interface that appears when users right-click or perform touch-hold actions. It provides a context-aware menu with support for nested items, dynamic updates, animations, custom templates, and comprehensive event handling. This skill guides you through implementing, configuring, and customizing context menus for Angular applications.
## When to Use This Skill
**Use this skill when:**
- You need to create a right-click or touch-hold context menu
- Managing menu items dynamically (add, remove, enable, disable)
- Handling menu item click events and actions
- Opening dialogs or navigating from menu selections
- Customizing menu appearance with animations, icons, or themes
- Showing/hiding items based on context or user permissions
- Binding menu items from data sources
- Creating complex menu templates or nested structures
- Implementing responsive menus with scrolling
- Adding keyboard shortcuts or accessibility features
## Component Overview
The ContextMenu component enables intuitive right-click interfaces with:
- ✅ Dynamic item management (add/remove/enable/disable)
- ✅ Multi-level nested menus
- ✅ Data binding from arrays or objects
- ✅ Customizable animations (FadeIn, SlideDown, ZoomIn, None)
- ✅ Template support for rich content (icons, HTML, tables)
- ✅ Event handling (click, open, close)
- ✅ Icon and URL navigation
- ✅ Scrollable menus for large item lists
- ✅ Accessibility with keyboard support
## Documentation and Navigation Guide
### Getting Started
📄 **Read:** [references/getting-started.md](references/getting-started.md)
- Package installation and dependencies
- Angular environment setup (standalone architecture)
- Creating your first ContextMenu
- Configuring target elements
- Basic menu item structure
### Menu Items Management
📄 **Read:** [references/menu-items-management.md](references/menu-items-management.md)
- Adding menu items dynamically (insertBefore, insertAfter)
- Removing menu items (removeItems method)
- Enabling and disabling items (enableItems)
- Showing and hiding items (showItems, hideItems)
- Dynamic context-aware menus
- Multi-level nested menus
### Data Binding
📄 **Read:** [references/data-binding.md](references/data-binding.md)
- Populating items from data sources
- MenuItemModel structure and properties
- Parent-child item relationships
- beforeItemRender event for item formatting
- Dynamic data updates
### Interaction & Events
📄 **Read:** [references/interaction-and-events.md](references/interaction-and-events.md)
- Menu item click handlers (select event)
- Click-to-open submenus (showItemOnClick)
- Programmatic open and close methods
- Menu positioning with coordinates
- Opening dialogs on item selection
- MenuEventArgs and event properties
### Styling & Customization
📄 **Read:** [references/styling-and-customization.md](references/styling-and-customization.md)
- Animation settings and effects (FadeIn, SlideDown, ZoomIn, None)
- CSS customization and class targeting
- Icon styling with iconCss property
- URL navigation and external links
- Scrollable menus (enableScrolling)
- Responsive design and Theme Studio
### Templates & Advanced Features
📄 **Read:** [references/templates-and-advanced.md](references/templates-and-advanced.md)
- Custom item templates (itemTemplate)
- Rich content with HTML and tables
- Character underlining and formatting
- Separator items and grouping
- Accessibility and keyboard navigation
- Advanced template patterns
## Quick Start Example
```typescript
import { Component } from '@angular/core';
import { ContextMenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-root',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="e-section-control">
<!-- Target element for context menu -->
<div id="target">Right click / Touch hold to open the ContextMenu</div>
<!-- ContextMenu component -->
<ejs-contextmenu
id='contextmenu'
target='#target'
[items]='menuItems'>
</ejs-contextmenu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Cut', iconCss: 'e-cm-icons e-cut' },
{ text: 'Copy', iconCss: 'e-cm-icons e-copy' },
{ text: 'Paste', iconCss: 'e-cm-icons e-paste' },
{ separator: true },
{
text: 'View',
items: [
{ text: 'Large icons' },
{ text: 'Small icons' }
]
}
];
}
```
## Common Patterns
### Pattern 1: Dynamic Item Management
```typescript
// Add items after 'Refresh'
this.contextmenu.insertAfter([{ text: 'Sort By' }], 'Refresh');
// Remove 'Paste' item
this.contextmenu.removeItems(['Paste']);
// Disable 'Edit' item
this.contextmenu.enableItems(['Edit'], false);
```
### Pattern 2: Context-Aware Menus
```typescript
beforeOpen(args: BeforeOpenCloseMenuEventArgs) {
if ((args.event.target as HTMLElement).id === 'editor') {
this.contextmenu.showItems(['Add', 'Edit', 'Delete']);
this.contextmenu.hideItems(['Cut', 'Copy', 'Paste']);
}
}
```
### Pattern 3: Menu Item Click Handler
```typescript
itemSelect(args: MenuEventArgs): void {
if (args.item.text === 'Save As...') {
this.dialogComponent.show();
}
}
```
### Pattern 4: Animation Configuration
```typescript
public animationSettings = {
effect: 'FadeIn',
duration: 400,
easing: 'ease'
};
```
## Complete API Reference
### Component Properties
#### Core Configuration Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `target` | `string` | `''` | **Required.** Specifies target element selector in which the ContextMenu should be opened. |
| `items` | `MenuItemModel[]` | `[]` | Specifies menu items with its properties which will be rendered as ContextMenu. |
| `showItemOnClick` | `boolean` | `false` | Specifies whether to show the sub menu or not on click. When `true`, the sub menu will open only on mouse click. |
| `filter` | `string` | `''` | Specifies the filter selector for elements inside the target in that the context menu will be opened. |
| `hoverDelay` | `number` | `0` | If `hoverDelay` is set by particular number, the menu will open after that period (in milliseconds). |
#### Styling & Appearance Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `animationSettings` | `MenuAnimationSettingsModel` | `{ duration: 400, easing: 'ease', effect: 'SlideDown' }` | Specifies the animation settings for the sub menu open/close. See [Animation Settings](#animation-settings) section. |
| `cssClass` | `string` | `''` | Defines class/multiple classes separated by a space in the Menu wrapper. Use for custom styling. |
| `enableRtl` | `boolean` | `false` | Enable or disable rendering component in right to left direction. |
#### Data & Behavior Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `itemTemplate` | `string \| Function` | `null` | This property allows you to define custom templates for items in the ContextMenu. Can be string selector or template function. |
| `locale` | `string` | `''` | Overrides the global culture and localization value for this component. Default global culture is `'en-US'`. |
| `enableScrolling` | `boolean` | `false` | Specifies whether to enable/disable the scrollable option in ContextMenu. |
#### Security & Persistence Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `enableHtmlSanitizer` | `boolean` | `true` | Specifies whether to enable the rendering of untrusted HTML values. If `true`, the component will sanitize any suspected untrusted strings and scripts before rendering them. Set to `false` only when you trust the HTML source completely. |
| `enablePersistence` | `boolean` | `false` | Enable or disable persisting component's state between page reloads. When enabled, menu state is saved in localStorage. |
#### Property Example - Basic Configuration
**Brief Example:**
```typescript
@Component({
selector: 'app-context-menu',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu
target='#target'
[items]='items'
[animationSettings]='animSettings'
cssClass='custom-menu'>
</ejs-contextmenu>
`
})
export class ContextMenuComponent {
items: MenuItemModel[] = [
{ text: 'Edit' },
{ text: 'Delete' }
];
animSettings = {
duration: 300,
effect: 'FadeIn',
easing: 'ease-out'
};
}
```
**Full Working Example:**
```typescript
import { Component, ViewChild } from '@angular/core';
import { ContextMenuComponent } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-navigations';
@Component({
selector: 'app-context-menu-config',
standalone: true,
imports: [ContextMenuModule],
template: `
<div class="container">
<h3>Advanced Configuration Example</h3>
<!-- Target element for context menu -->
<div id="configTarget" class="target-box">
Right click or touch hold to open menu
</div>
<!-- ContextMenu with complete configuration -->
<ejs-contextmenu
#contextMenu
id='contextMenu'
target='#configTarget'
[items]='menuItems'
[animationSettings]='animationSettings'
cssClass='modern-menu'
[showItemOnClick]='true'
[enableScrolling]='true'
[hoverDelay]='300'
[enableHtmlSanitizer]='true'
[enablePersistence]='true'
locale='en-US'>
</ejs-contextmenu>
</div>
`,
styles: [`
.target-box {
width: 300px;
height: 200px;
border: 2px dashed #ccc;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
cursor: context-menu;
}
`]
})
export class AdvancedContextMenuComponent {
@ViewChild('contextMenu') contextMenu!: ContextMenuComponent;
menuItems: MenuItemModel[] = [
{ text: 'Edit', iconCss: 'e-cm-icons e-edit', id: 'edit' },
{ text: 'Delete', iconCss: 'e-cm-icons e-delete', id: 'delete' },
{ separator: true },
{ text: 'More', id: 'more' }
];
animationSettings = {
effect: 'FadeIn' as any,
duration: 400,
easing: 'ease-in-out'
};
}
```
### Menu Item Properties (MenuItemModel)
MenuItemModel defines the structure for each menu item. Each item in the `items` array follows this model:
| Property | Type | Optional | Description |
|----------|------|----------|-------------|
| `text` | `string` | No | **Required.** Specifies text for menu item. This is the display label shown to users. |
| `id` | `string` | Yes | Specifies the id for menu item. Use this for identifying items in methods like `enableItems()`, `removeItems()`, etc. |
| `items` | `MenuItemModel[]` | Yes | Specifies the sub menu items that is the array of MenuItem model. Creates nested/hierarchical menus. |
| `separator` | `boolean` | Yes | Specifies separator between the menu items. Separators are either horizontal or vertical lines used to group menu items. Set to `true` to create a visual divider. |
| `iconCss` | `string` | Yes | Defines class/multiple classes separated by a space for the menu Item that is used to include an icon. Menu Item can include font icon and sprite image. Example: `iconCss: 'e-icons e-edit'`. |
| `url` | `string` | Yes | Specifies url for menu item that creates the anchor link to navigate to the url provided. When clicked, navigates to this URL. |
| `htmlAttributes` | `Record<string, any>` | Yes | Specifies the htmlAttributes property to support adding custom attributes to the menu items. Example: `{ 'data-info': 'value', 'title': 'My Tooltip' }`. |
#### MenuItem Example - Basic Usage
**Brief Example:**
```typescript
items: MenuItemModel[] = [
{ text: 'Cut', id: 'cut', iconCss: 'e-icons e-cut' },
{ text: 'Copy', id: 'copy', iconCss: 'e-icons e-copy' },
{ text: 'Paste', id: 'paste', iconCss: 'e-icons e-paste' },
{ separator: true },
{ text: 'Delete', id: 'delete', iconCss: 'e-icons e-delete' }
];
```
**Full Working Example - Complex MenuItemModel:**
```typescript
@Component({
selector: 'app-menu-items-config',
standalone: true,
imports: [ContextMenuModule],
template: `
<div id="target">Right click for advanced menu</div>
<ejs-contextmenu target='#target' [items]='menuItems'></ejs-contextmenu>
`
})
export class MenuItemsComponent {
menuItems: MenuItemModel[] = [
// Item with icon
{
text: 'Edit',
id: 'edit-item',
iconCss: 'e-icons e-edit',
htmlAttributes: {
'data-action': 'edit',
'title': 'Edit selected item'
}
},
// Item with submenu
{
text: 'Format',
id: 'format',
iconCss: 'e-icons e-palette',
items: [
{ text: 'Bold', id: 'bold' },
{ text: 'Italic', id: 'italic' },
{ text: 'Underline', id: 'underline' }
]
},
// Navigation item with URL
{
text: 'Visit Website',
id: 'website',
iconCss: 'e-icons e-export',
url: 'https://example.com',
htmlAttributes: { 'target': '_blank' }
},
// Separator
{ separator: true },
// Item with nested submenu
{
text: 'Advanced',
id: 'advanced',
items: [
{
text: 'Settings',
id: 'settings',
items: [
{ text: 'General', id: 'general' },
{ text: 'Advanced', id: 'adv' }
]
},
{ text: 'Help', id: 'help', url: 'https://help.example.com' }
]
}
];
}
```
### Component Methods
#### Method: enableItems
**Signature:**
```typescript
enableItems(items: string[], enable?: boolean, isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `items` | `string[]` | No | Array of item text or ids that needs to be enabled/disabled. |
| `enable` | `boolean` | Yes, default: `true` | Set `true` to enable items; set `false` to disable items. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if items array contains unique ids instead of text. |
**Return:** `void`
**Brief Example:**
```typescript
// Disable 'Delete' item by text
this.contextMenu.enableItems(['Delete'], false);
// Enable items by id
this.contextMenu.enableItems(['edit-item', 'copy-item'], true, true);
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-enable-items',
template: `
<button (click)="disableDelete()">Disable Delete</button>
<button (click)="enableAll()">Enable All</button>
<div id="target">Right click here</div>
<ejs-contextmenu #cm target='#target' [items]='items'></ejs-contextmenu>
`
})
export class EnableItemsComponent {
@ViewChild('cm') contextMenu!: ContextMenuComponent;
items: MenuItemModel[] = [
{ text: 'Edit', id: 'edit' },
{ text: 'Delete', id: 'delete' },
{ text: 'Copy', id: 'copy' }
];
disableDelete() {
this.contextMenu.enableItems(['Delete'], false);
}
enableAll() {
this.contextMenu.enableItems(['Edit', 'Delete', 'Copy'], true);
}
}
```
#### Method: insertAfter
**Signature:**
```typescript
insertAfter(items: MenuItemModel[], text: string, isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `items` | `MenuItemModel[]` | No | Array of MenuItemModel that needs to be inserted. |
| `text` | `string` | No | Text item after which the element to be inserted. If `isUniqueId` is true, this is the unique id. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if text is a unique id instead of display text. |
**Return:** `void`
**Brief Example:**
```typescript
// Insert item after 'Edit'
this.contextMenu.insertAfter([{ text: 'Save' }], 'Edit');
// Insert after item with specific id
this.contextMenu.insertAfter([{ text: 'New', id: 'new-item' }], 'edit-item', true);
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-insert-after',
template: `
<button (click)="addAfterEdit()">Add Item After Edit</button>
<div id="target">Right click here</div>
<ejs-contextmenu #cm target='#target' [items]='items'></ejs-contextmenu>
`
})
export class InsertAfterComponent {
@ViewChild('cm') contextMenu!: ContextMenuComponent;
items: MenuItemModel[] = [
{ text: 'Edit', id: 'edit' },
{ text: 'Delete', id: 'delete' }
];
addAfterEdit() {
this.contextMenu.insertAfter(
[
{ text: 'Copy', id: 'copy' },
{ text: 'Paste', id: 'paste' }
],
'Edit'
);
}
}
```
#### Method: insertBefore
**Signature:**
```typescript
insertBefore(items: MenuItemModel[], text: string, isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `items` | `MenuItemModel[]` | No | Array of MenuItemModel that needs to be inserted. |
| `text` | `string` | No | Text item before which the element to be inserted. If `isUniqueId` is true, this is the unique id. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if text is a unique id instead of display text. |
**Return:** `void`
**Brief Example:**
```typescript
this.contextMenu.insertBefore([{ text: 'Undo' }], 'Edit');
```
#### Method: removeItems
**Signature:**
```typescript
removeItems(items: string[], isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `items` | `string[]` | No | Array of item text or ids that needs to be removed. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if items array contains unique ids instead of text. |
**Return:** `void`
**Brief Example:**
```typescript
this.contextMenu.removeItems(['Delete', 'Copy']);
```
#### Method: hideItems
**Signature:**
```typescript
hideItems(items: string[], isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `items` | `string[]` | No | Array of item text or ids that needs to be hidden. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if items array contains unique ids instead of text. |
**Return:** `void`
**Brief Example:**
```typescript
this.contextMenu.hideItems(['AdminOnly'], true);
```
#### Method: showItems
**Signature:**
```typescript
showItems(items: string[], isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `items` | `string[]` | No | Array of item text or ids that needs to be shown. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if items array contains unique ids instead of text. |
**Return:** `void`
**Brief Example:**
```typescript
this.contextMenu.showItems(['AdminOnly'], true);
```
#### Method: getItemIndex
**Signature:**
```typescript
getItemIndex(item: MenuItem | string, isUniqueId?: boolean): number[]
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `item` | `MenuItem \| string` | No | MenuItem object or id/text to get the index for. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if item is a unique id instead of text. |
**Return:** `number[]` - Array of indices representing the position of the item in the menu hierarchy.
**Brief Example:**
```typescript
// Get index by text
const index = this.contextMenu.getItemIndex('Edit'); // [0] for first item
// Get index by id
const index = this.contextMenu.getItemIndex('edit-item', true); // [0]
// For nested items
const index = this.contextMenu.getItemIndex('Bold', true); // [1, 0] for Format > Bold
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-get-index',
template: `
<button (click)="findItem()">Find Item Index</button>
<div>Index: {{ itemIndex }}</div>
<div id="target">Right click here</div>
<ejs-contextmenu #cm target='#target' [items]='items'></ejs-contextmenu>
`
})
export class GetIndexComponent {
@ViewChild('cm') contextMenu!: ContextMenuComponent;
itemIndex: any = null;
items: MenuItemModel[] = [
{ text: 'Edit', id: 'edit' },
{
text: 'Format',
id: 'format',
items: [
{ text: 'Bold', id: 'bold' },
{ text: 'Italic', id: 'italic' }
]
}
];
findItem() {
this.itemIndex = this.contextMenu.getItemIndex('Bold', true);
console.log('Item index:', this.itemIndex); // [1, 0]
}
}
```
#### Method: setItem
**Signature:**
```typescript
setItem(item: MenuItem, id?: string, isUniqueId?: boolean): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `item` | `MenuItem` | No | MenuItem object containing updated properties. |
| `id` | `string` | Yes | id or text of the item to be updated. If not provided, updates the item passed as first parameter. |
| `isUniqueId` | `boolean` | Yes, default: `false` | Set `true` if id is a unique id instead of text. |
**Return:** `void`
**Brief Example:**
```typescript
// Update item by text
this.contextMenu.setItem({ text: 'Edit Document' }, 'Edit');
// Update item by id
this.contextMenu.setItem(
{ text: 'Remove', iconCss: 'e-icons e-delete' },
'delete-item',
true
);
```
#### Method: open
**Signature:**
```typescript
open(top: number, left: number, target?: HTMLElement): void
```
| Parameter | Type | Optional | Description |
|-----------|------|----------|-------------|
| `top` | `number` | No | To specify ContextMenu vertical positioning (Y coordinate in pixels). |
| `left` | `number` | No | To specify ContextMenu horizontal positioning (X coordinate in pixels). |
| `target` | `HTMLElement` | Yes | To calculate z-index for ContextMenu based upon the specified target element. |
**Return:** `void`
**Brief Example:**
```typescript
// Open at mouse position
this.contextMenu.open(event.clientY, event.clientX);
// Open at specific coordinates
this.contextMenu.open(200, 300);
// Open relative to target element
this.contextMenu.open(100, 150, document.getElementById('target'));
```
#### Method: close
**Signature:**
```typescript
close(): void
```
| Parameter | - | - | - |
|-----------|---|---|---|
**Return:** `void`
**Brief Example:**
```typescript
// Close the open ContextMenu
this.contextMenu.close();
```
#### Method: destroy
**Signature:**
```typescript
destroy(): void
```
| Parameter | - | - | - |
|-----------|---|---|---|
**Return:** `void`
**Brief Example:**
```typescript
// Completely destroy the component and cleanup resources
this.contextMenu.destroy();
```
**Full Working Example - Destroy on Component Destroy:**
```typescript
import { Component, ViewChild, OnDestroy } from '@angular/core';
import { ContextMenuComponent } from '@syncfusion/ej2-angular-navigations';
@Component({
selector: 'app-destroy-example',
template: `
<button (click)="destroyMenu()">Destroy Menu</button>
<div id="target">Right click here</div>
<ejs-contextmenu #cm target='#target' [items]='items'></ejs-contextmenu>
`
})
export class DestroyExampleComponent implements OnDestroy {
@ViewChild('cm') contextMenu!: ContextMenuComponent;
items: MenuItemModel[] = [{ text: 'Item 1' }];
destroyMenu() {
if (this.contextMenu) {
this.contextMenu.destroy();
console.log('Menu destroyed');
}
}
ngOnDestroy() {
// Cleanup when component is destroyed
if (this.contextMenu) {
this.contextMenu.destroy();
}
}
}
```
### Animation Settings (MenuAnimationSettingsModel)
Configure how menu items animate when opening/closing:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `duration` | `number` | `400` | Specifies the time duration (in milliseconds) to transform/animate the menu. Example: `300` for 300ms animation. |
| `easing` | `string` | `'ease'` | Specifies the easing effect applied while transform. Examples: `'ease'`, `'ease-in'`, `'ease-out'`, `'ease-in-out'`, `'linear'`, `'cubic-bezier(0.25, 0.1, 0.25, 1)'`. |
| `effect` | `MenuEffect` | `'SlideDown'` | Specifies the effect that shown in the sub menu transform. Options: `'None' \| 'SlideDown' \| 'ZoomIn' \| 'FadeIn'`. |
**Effect Options:**
- **None**: Specifies the sub menu transform with no animation effect.
- **SlideDown**: Specifies the sub menu transform with slide down effect (default).
- **ZoomIn**: Specifies the sub menu transform with zoom in effect.
- **FadeIn**: Specifies the sub menu transform with fade in effect.
**Brief Example:**
```typescript
animationSettings = {
duration: 300,
effect: 'FadeIn' as any,
easing: 'ease-out'
};
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-animation-config',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu
target='#target'
[items]='items'
[animationSettings]='animSettings'>
</ejs-contextmenu>
`
})
export class AnimationConfigComponent {
items: MenuItemModel[] = [
{ text: 'Option 1' },
{ text: 'Option 2' }
];
animSettings = {
effect: 'ZoomIn' as any,
duration: 200,
easing: 'ease-in-out'
};
}
```
### Component Events
#### Event: beforeOpen
**Signature:** `(beforeOpen): EmitType<BeforeOpenCloseMenuEventArgs>`
Triggers before opening the menu item. Use this to prevent menu opening, show/hide items based on context, or customize menu before display.
**Event Arguments - BeforeOpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event (value: `'beforeOpen'`). |
**Brief Example:**
```typescript
onBeforeOpen(args: BeforeOpenCloseMenuEventArgs) {
console.log('Menu about to open');
}
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-before-open',
template: `
<div id="target">Right click to open</div>
<ejs-contextmenu
target='#target'
[items]='items'
(beforeOpen)='onBeforeOpen($event)'>
</ejs-contextmenu>
`
})
export class BeforeOpenComponent {
items: MenuItemModel[] = [{ text: 'Item 1' }];
onBeforeOpen(args: BeforeOpenCloseMenuEventArgs) {
console.log('Event:', args.name); // 'beforeOpen'
}
}
```
#### Event: beforeClose
**Signature:** `(beforeClose): EmitType<BeforeOpenCloseMenuEventArgs>`
Triggers before closing the menu. Use this to perform cleanup or prevent menu from closing.
**Event Arguments - BeforeOpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event (value: `'beforeClose'`). |
**Brief Example:**
```typescript
onBeforeClose(args: BeforeOpenCloseMenuEventArgs) {
console.log('Menu about to close');
}
```
#### Event: onOpen
**Signature:** `(onOpen): EmitType<OpenCloseMenuEventArgs>`
Triggers while opening the menu item. This event fires after the menu has been opened and is visible.
**Event Arguments - OpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event (value: `'onOpen'`). |
**Brief Example:**
```typescript
onOpen(args: OpenCloseMenuEventArgs) {
console.log('Menu opened:', args.name);
}
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-open-event',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu
target='#target'
[items]='items'
(onOpen)='onOpen($event)'
(onClose)='onClose($event)'>
</ejs-contextmenu>
<p>Status: {{ menuStatus }}</p>
`
})
export class OpenEventComponent {
items: MenuItemModel[] = [{ text: 'Item 1' }];
menuStatus = 'Closed';
onOpen(args: OpenCloseMenuEventArgs) {
this.menuStatus = 'Menu Opened - ' + args.name;
}
onClose(args: OpenCloseMenuEventArgs) {
this.menuStatus = 'Menu Closed - ' + args.name;
}
}
```
#### Event: onClose
**Signature:** `(onClose): EmitType<OpenCloseMenuEventArgs>`
Triggers while closing the menu. This event fires after the menu has been closed and is no longer visible.
**Event Arguments - OpenCloseMenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event (value: `'onClose'`). |
**Brief Example:**
```typescript
onClose(args: OpenCloseMenuEventArgs) {
console.log('Menu closed:', args.name);
}
```
#### Event: select
**Signature:** `(select): EmitType<MenuEventArgs>`
Triggers while selecting menu item. Use this to handle menu item clicks and perform actions.
**Event Arguments - MenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event (value: `'select'`). |
**Brief Example:**
```typescript
onSelect(args: MenuEventArgs) {
console.log('Item selected:', args.name);
}
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-select-event',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu
target='#target'
[items]='items'
(select)='onSelect($event)'>
</ejs-contextmenu>
<p>Selected: {{ selectedItem }}</p>
`
})
export class SelectEventComponent {
items: MenuItemModel[] = [
{ text: 'Edit', id: 'edit' },
{ text: 'Delete', id: 'delete' },
{ text: 'Copy', id: 'copy' }
];
selectedItem = 'None';
onSelect(args: MenuEventArgs) {
this.selectedItem = args.name;
console.log('Selected item event:', args.name);
}
}
```
#### Event: beforeItemRender
**Signature:** `(beforeItemRender): EmitType<MenuEventArgs>`
Triggers while rendering each menu item. Use this to customize each item's appearance or behavior before rendering.
**Event Arguments - MenuEventArgs:**
| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Specifies name of the event (value: `'beforeItemRender'`). |
**Brief Example:**
```typescript
onBeforeItemRender(args: MenuEventArgs) {
console.log('Rendering item:', args.name);
}
```
**Full Working Example:**
```typescript
@Component({
selector: 'app-before-item-render',
template: `
<div id="target">Right click here</div>
<ejs-contextmenu
target='#target'
[items]='items'
(beforeItemRender)='onBeforeItemRender($event)'>
</ejs-contextmenu>
`
})
export class BeforeItemRenderComponent {
items: MenuItemModel[] = [
{ text: 'Edit', id: 'edit' },
{ text: 'Delete', id: 'delete' }
];
onBeforeItemRender(args: MenuEventArgs) {
console.log('Rendering:', args.name);
// Customize items before rendering
}
}
```
#### Event: created
**Signature:** `(created): EmitType<Event>`
Triggers once the component rendering is completed. Use this for post-initialization setup.
**Event Arguments:**
| Property | Type | Description |
|----------|------|-------------|
| - | `Event` | Standard JavaScript Event object. |
**Brief Example:**
```typescript
onCreated(event: Event) {
console.log('ContextMenu component created and ready');
}
```
---
## Key Properties & Events Quick Reference
| Property/Event | Purpose | Example |
|---|---|---|
| `items` | Define menu structure | `items: MenuItemModel[]` |
| `target` | Element triggering menu | `target='#target'` |
| `select` | Menu item click | `(select)="onSelect($event)"` |
| `beforeOpen` | Before menu opens | `(beforeOpen)="onBeforeOpen($event)"` |
| `enableItems()` | Toggle item availability | `enableItems(['Edit'], false)` |
| `insertAfter()` | Add items after target | `insertAfter([...], 'Refresh')` |
| `hideItems()` | Hide specific items | `hideItems(['Cut', 'Copy'])` |
| `animationSettings` | Control appearance effects | `animationSettings: {...}` |
## Common Use Cases
1. **File Operations Menu** - Cut, Copy, Paste, Delete options
2. **Content Editor Menu** - Format, Insert, Link, Media options
3. **Data Grid Context** - Edit, Delete, Export, Filter actions
4. **Navigation Menu** - Links to pages or external URLs
5. **Permission-Based Menu** - Show/hide items based on user role
6. **Multi-Language Menu** - Dynamic item text from translations
7. **Confirmation Dialogs** - Open dialog before executing action
8. **Table Operations** - Row/column management in data tables
## Next Steps
1. **Select a reference file** based on your task (Getting Started, Menu Management, Data Binding, Events, Styling, or Templates)
2. **Review the code examples** for your specific use case
3. **Copy relevant code patterns** to your project
4. **Customize menu items** for your application context
5. **Test interactions** with different target elements
---
**Need help with a specific task?** Reference the appropriate guide above and let me know which menu feature you're implementing.