LIDOR SYSTEMS

Advanced User Interface Controls and Components

Angular ContextMenu with Custom Content

Created: 06 February 2018

In most cases, the context menu displays a list of options that usually are represented by an icon and a label. However, sometimes you may need to add or arrange custom HTML elements inside a menu item in different layout, for example changing the font style or zooming the window. These changes also require the context menu to remain open while performing an action.

All of above functionalities are possible with IntegralUI ContextMenu for Angular, where using templates you can add any custom content to each menu item separately. Also, you can modify the way the menu appears and prevent it from closing when specific action that requires a focus is in effect.

ContextMenu component is part of IntegralUI Web
a suite of UI Components for development of web apps

If you have any questions, don't hesitate to contact us at support@lidorsystems.com

In above demo, the context menu has several options with two of them that are using templates. The first option allows you to change the font size of the element over which the menu is shown, and the second option has three buttons placed in line for Cut, Copy and Paste operations. While these custom menu options are clicked, the context menu will remain open and to close it, you need to select the Exit option.

Add Custom Content to Menu Items in ContextMenu for Angular

The data structure of the context menu contains several fields:

  • appRef - holds a reference of the application component to which the context menu is added during run-time
  • autoClose - determines whether the menu will close whenever an option is selected
  • items - holds a reference to an array or menu items

Note The appRef field is important in order to dynamically create the menu and show it.

Each menu item can have an icon (local or remote), label or custom content. In most cases, icon and label are sufficient, but if you want to add an option that requires additional functionality, the best is to use a template.

@ViewChild('application', {read: ViewContainerRef}) applicationRef: ViewContainerRef;

public menuSettings: any = {
    appRef: null,
    items: []
}


ngAfterViewInit(){
    this.menuSettings = {
        appRef: this.applicationRef,
        autoClose: false,
        items: [
            { id: 1, text: "Bold", icon: 'cmnu-tpl-icons-medium check-mark', checked: true },
            { id: 2, text: "Italic", icon: 'cmnu-tpl-icons-medium empty' },
            { id: 3, text: "Strikethrough", icon: 'cmnu-tpl-icons-medium empty' },
            { id: 4, type: "separator" },
            { 
                id: 5, text: "Font Size", value: 18,
                buttons: [
                    { icon: 'cmnu-tpl-icons-medium cmnu-tpl-font-decrease' },
                    { icon: 'cmnu-tpl-icons-medium cmnu-tpl-font-increase' }
                ]
            },
            { id: 6, type: "separator" },
            { 
                id: 7, text: "Edit",
                buttons: [
                    { text: 'Cut' },
                    { text: 'Copy' },
                    { text: 'Paste' }
                ]
            },
            { id: 8, type: "separator" },
            { id: 9, text: "Exit" }
        ]
    }
}                            
<div class="cmnu-tpl-app">
    <div #application class="cmnu-tpl-block" [iuiContextMenu]="menuSettings" (menuClick)="menuItemClick($event)" [ngStyle]="{ 'font-weight': fontWeight, 'font-style': fontStyle, 'font-size': fontSize + 'px', 'text-decoration': textDecoration }">
        <ng-template let-item [iuiTemplate]="{ type: 'menu-item' }">
            <span [ngSwitch]="item.id">
                <span *ngSwitchCase="5"> <!-- FONT MENU ITEM -->
                    <span>{{item.text}}</span>
                    <div class="cmnu-tpl-item-font">
                        <div><span [ngClass]="item.buttons[0].icon" (mousedown)="onFontDecrease($event, item)"></span></div>
                        <span>{{item.value}}</span>
                        <div><span [ngClass]="item.buttons[1].icon" (mousedown)="onFontIncrease($event, item)"></span></div>
                    </div>
                </span>
                <span *ngSwitchCase="7"> <!-- EDIT MENU ITEM -->
                    <div class="cmnu-tpl-item-edit">
                        <button (mousedown)="onEditButtonClicked($event, 0)">{{item.buttons[0].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 1)">{{item.buttons[1].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 2)">{{item.buttons[2].text}}</button>
                    </div>
                </span>
                <span *ngSwitchDefault>
                    <span [ngClass]="item.icon"></span> 
                    <span>{{item.text}}</span>
                </span>
            </span>
        </ng-template>
        <span>Right click to open the context menu</span>
    </div>
</div>
                            
.cmnu-tpl-app .iui-contextmenu
{
    width: 225px !important;
}
.cmnu-tpl-app .iui-menuitem-root
{
    padding-right: 0 !important;
}
.cmnu-tpl-block
{
    background: white;
    border: thin solid gray;
    width: 800px;
    height: 300px;
}
.cmnu-tpl-block span
{
    color: #808080;
    cursor: default;
    display: block;
    margin: 140px auto;
    text-align: center;
}
.cmnu-tpl-icons-medium
{
    background: url(app/integralui/resources/icons-x24.png) no-repeat 0 0;
    display: inline-block;
    overflow: hidden;
    padding: 0;
    margin: 0 7px 0 1px;
    width: 24px;
    height: 24px;
    vertical-align: middle;
}                            

Depending on id of the menu item, you can change the template content. In this example, there are two menu items with custom content:

  • Font size - increase or decrease the size of the element font
  • Edit options - represented by three buttons placed in line

The font menu item has the following template:

onFontDecrease(e: any, item: any){
    if (e.which == 1){
        item.value--;
        this.fontSize = item.value;
    }

    e.stopPropagation();
}

onFontIncrease(e: any, item: any){
    if (e.which == 1){
        item.value++;
        this.fontSize = item.value;
    }

    e.stopPropagation();
}
                        
<div class="cmnu-tpl-app">
    <div #application class="cmnu-tpl-block" [iuiContextMenu]="menuSettings" (menuClick)="menuItemClick($event)" [ngStyle]="{ 'font-weight': fontWeight, 'font-style': fontStyle, 'font-size': fontSize + 'px', 'text-decoration': textDecoration }">
        <ng-template let-item [iuiTemplate]="{ type: 'menu-item' }">
            <span [ngSwitch]="item.id">
                <span *ngSwitchCase="5"> <!-- FONT MENU ITEM -->
                    <span>{{item.text}}</span>
                    <div class="cmnu-tpl-item-font">
                        <div><span [ngClass]="item.buttons[0].icon" (mousedown)="onFontDecrease($event, item)"></span></div>
                        <span>{{item.value}}</span>
                        <div><span [ngClass]="item.buttons[1].icon" (mousedown)="onFontIncrease($event, item)"></span></div>
                    </div>
                </span>
                <span *ngSwitchCase="7"> <!-- EDIT MENU ITEM -->
                    <div class="cmnu-tpl-item-edit">
                        <button (mousedown)="onEditButtonClicked($event, 0)">{{item.buttons[0].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 1)">{{item.buttons[1].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 2)">{{item.buttons[2].text}}</button>
                    </div>
                </span>
                <span *ngSwitchDefault>
                    <span [ngClass]="item.icon"></span> 
                    <span>{{item.text}}</span>
                </span>
            </span>
        </ng-template>
        <span>Right click to open the context menu</span>
    </div>
</div>
                            
/* Font Menu Item */
.cmnu-tpl-font-decrease
{
    background-position: -24px -120px;
    margin: 0;
}
.cmnu-tpl-font-increase
{
    background-position: 0 -120px;
    margin: 0;
}
.check-mark
{
    background-position: -192px -120px;
}

.cmnu-tpl-item-font
{
    display: inline-block;
    float: right;
    margin: -4px 0 0 0;
}
.cmnu-tpl-item-font > div
{
    border: thin solid transparent;
    display: inline-block;
}
.cmnu-tpl-item-font > div:hover
{
    border: thin solid transparent;
    background: #cecece;
}
.cmnu-tpl-item-font > span
{
    display: inline-block;
    margin: 0 5px;
}                            

As you can see, there is a label to describe the menu option followed by +/- buttons and a label showing the current font size. Whenever the increase or decrease button is clicked, a corresponding method is called which changes the value of fontSize variable set in the ngStyle expression of the block element.

Note Because the autoClose field from context menu settings is set to false, the menu will remain open during font size changes.

In case of edit menu option, there are three buttons for Cut, Copy and Paste operations. To keep this example simple, there is no actual editing functionality, only when button is clicked a popup message will appear.

onEditButtonClicked(e: any, index: number){
    switch (index){
        case 0:
            alert("Cut button clicked!");
            break;
        case 1:
            alert("Copy button clicked!");
            break;
        case 2:
            alert("Paste button clicked!");
            break;
    }

    e.stopPropagation();
}                        
<div class="cmnu-tpl-app">
    <div #application class="cmnu-tpl-block" [iuiContextMenu]="menuSettings" (menuClick)="menuItemClick($event)" [ngStyle]="{ 'font-weight': fontWeight, 'font-style': fontStyle, 'font-size': fontSize + 'px', 'text-decoration': textDecoration }">
        <ng-template let-item [iuiTemplate]="{ type: 'menu-item' }">
            <span [ngSwitch]="item.id">
                <span *ngSwitchCase="5"> <!-- FONT MENU ITEM -->
                    <span>{{item.text}}</span>
                    <div class="cmnu-tpl-item-font">
                        <div><span [ngClass]="item.buttons[0].icon" (mousedown)="onFontDecrease($event, item)"></span></div>
                        <span>{{item.value}}</span>
                        <div><span [ngClass]="item.buttons[1].icon" (mousedown)="onFontIncrease($event, item)"></span></div>
                    </div>
                </span>
                <span *ngSwitchCase="7"> <!-- EDIT MENU ITEM -->
                    <div class="cmnu-tpl-item-edit">
                        <button (mousedown)="onEditButtonClicked($event, 0)">{{item.buttons[0].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 1)">{{item.buttons[1].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 2)">{{item.buttons[2].text}}</button>
                    </div>
                </span>
                <span *ngSwitchDefault>
                    <span [ngClass]="item.icon"></span> 
                    <span>{{item.text}}</span>
                </span>
            </span>
        </ng-template>
        <span>Right click to open the context menu</span>
    </div>
</div>
                            
/* Edit Menu Item */
.cmnu-tpl-item-edit
{
    display: block;
    margin: 0;
    padding: 0;
    white-space: nowrap;
}
.cmnu-tpl-item-edit button
{
    background: transparent;
    border: 0;
    border-left: thin solid #cecece;
    height: 26px;
    margin-right: -4px !important;
    padding: 3px 0;
    width: 33%;
}
.cmnu-tpl-item-edit button:hover
{
    background: #cecece;
}
.cmnu-tpl-item-edit button:first-child
{
    border-left: 0;
}                            

You can find more information about editing options in this article: Context Menu with Edit Options

Other menu options changes other settings in ngStyle epxression of the block element. Clicks from these menu items are handled in menuClick event:

 menuItemClick(e: any){
    if (e.item){
        if (e.item.id < 5)
            e.item.checked = e.item.checked != undefined ? !e.item.checked : true;
        else
            e.item.checked = true;

        switch (e.item.id){
            case 1: 
                this.fontWeight = e.item.checked != false ? 'bold' : 'normal';
                break;
            case 2: 
                this.fontStyle = e.item.checked != false ? 'italic' : 'normal';
                break;
            case 3: 
                this.textDecoration = e.item.checked != false ? 'line-through' : 'none';
                break;
        }

        if (e.item.id < 4)
            e.item.icon = e.item.checked != false ? 'cmnu-tpl-icons-medium check-mark' : 'cmnu-tpl-icons-medium empty';
    }
}                        
<div class="cmnu-tpl-app">
    <div #application class="cmnu-tpl-block" [iuiContextMenu]="menuSettings" (menuClick)="menuItemClick($event)" [ngStyle]="{ 'font-weight': fontWeight, 'font-style': fontStyle, 'font-size': fontSize + 'px', 'text-decoration': textDecoration }">
        <ng-template let-item [iuiTemplate]="{ type: 'menu-item' }">
            <span [ngSwitch]="item.id">
                <span *ngSwitchCase="5"> <!-- FONT MENU ITEM -->
                    <span>{{item.text}}</span>
                    <div class="cmnu-tpl-item-font">
                        <div><span [ngClass]="item.buttons[0].icon" (mousedown)="onFontDecrease($event, item)"></span></div>
                        <span>{{item.value}}</span>
                        <div><span [ngClass]="item.buttons[1].icon" (mousedown)="onFontIncrease($event, item)"></span></div>
                    </div>
                </span>
                <span *ngSwitchCase="7"> <!-- EDIT MENU ITEM -->
                    <div class="cmnu-tpl-item-edit">
                        <button (mousedown)="onEditButtonClicked($event, 0)">{{item.buttons[0].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 1)">{{item.buttons[1].text}}</button>
                        <button (mousedown)="onEditButtonClicked($event, 2)">{{item.buttons[2].text}}</button>
                    </div>
                </span>
                <span *ngSwitchDefault>
                    <span [ngClass]="item.icon"></span> 
                    <span>{{item.text}}</span>
                </span>
            </span>
        </ng-template>
        <span>Right click to open the context menu</span>
    </div>
</div>
                            

Conclusion

In general, context menu displays a set of menu options represented by an icon and a label. In cases where you need a more specific menu with options that have custom elements, or they need to be arranged in different layout, you may need to use templates.

IntegralUI ContextMenu for Angular allows you to add custom content to each menu item separately. By customizing the menu on individual level via templates, you can create different kinds of context menus that are more suitable for specific task. In addition, you can also prevent the menu from closing if it is required to remain open, depending on the action.

The ContextMenu component is part of IntegralUI Web.

Did you Like this Article?


Enter your e-mail address below and you will receive latest articles as well as news on upcoming events and special offers.