This website is no longer actively supported
Written by John DeVight on 31 Dec 2011 05:24
Download Sample Code
The following article is a draft. The final version will be completed soon.
Overview
I had a requirement to create a DropDownList control that contained a popup menu. Telerik has created a robust framework for Telerik Extensions for ASP.NET MVC, and since I am already using the Telerik Extensions for ASP.NET MVC to develop the application, I decided to leverage what Telerik has done to create a popup menu control. What I ended up with was the following:
DropDownList control with Icon next to it for the Popup Menu

DropDownList control with the Popup Menu displayed

Creating the Popup Menu JavaScript
I looked at the JavaScript that telerik created for thier controls and based much of my code on what telerik did for thier controls. I created a jQuery plugin that contains the popupMenu class and a function to create an instance of the popupMenu class.
Creating an Instance of the popupMenu Object
The popupMenu object is created using the tPopupMenu function. The tPopupMenu function stores the popupMenu object with the html element using the element's data function:
$.fn.tPopupMenu = function (options) { var cm = new $.popupMenu(this, options); this.data('tPopupMenu', cm); }
popupMenu Constructor
The popupMenu constructor stores the options passed into it and defines delegates to handle the mouseenter, mouseleave and click events on the popupMenu icon:
// Constructor. $.popupMenu = function (element, options) { this.element = element; this.options = options; // Add delegates for the popup menu "icon" element. $(this.element).delegate('.t-popup-menu', 'mouseenter', this, this.mouseenter) .delegate('.t-popup-menu', 'mouseleave', this, this.mouseleave) .delegate('.t-popup-menu', 'click', this, this.click); }
popupMenu Class
The popupMenu class contains the handlers for the mouseenter, mouseleave and click events. The clcik handler is where the popup menu is created and displayed. The popupMenu class has show, hide, enableItem and disableItem functions:
$.popupMenu.prototype = { // When the mouse enters the "icon" element, add the t-state-hover class. mouseenter: function (e) { e.stopPropagation(); var $li = $(e.currentTarget); $li.addClass('t-state-hover'); }, // When the mouse leaves the "icon" element, remove the t-state-hover class. mouseleave: function (e) { e.stopPropagation(); var $li = $(e.currentTarget); $li.removeClass('t-state-hover'); }, // When the "icon" element is clicked, display the popup menu. click: function (e) { e.stopPropagation(); var contextmenu = e.data; if (typeof console != "undefined") console.log('popupMenu', e); // Get the selelcted item. var $span = $(e.currentTarget); // Remove any contect menus that are still being displayed. var fx = $.telerik.fx.slide.defaults(); $('div#popupMenu').each(function (index) { $.telerik.fx.rewind(fx, $(this).find('.t-group'), { direction: 'bottom' }, function () { $(this).remove(); }); }); // default "slide" effect settings fx = $.telerik.fx.slide.defaults(); var menuItems = ''; $.each(contextmenu.options.menu, function (idx, item) { if (item.separator != undefined && item.separator == true) { menuItems += '<hr/>'; } else { menuItems += '<li class="t-item' + (item.disabled != undefined ? ' t-state-disabled' : '') + '"><a href="#" class="t-link">' + item.text + '</a></li>'; } }); // context menu definition - markup and event handling var $popupMenu = $('<div class="t-animation-container" id="popupMenu">' + '<ul class="t-widget t-group t-menu t-menu-vertical" style="display:none">' + menuItems + '</ul>' + '</div>') .css({ //positioning of the menu position: 'absolute', left: e.pageX, // x coordinate of the mouse top: e.pageY // y coordinate of the mouse }) .css({ width: contextmenu.options.width != undefined ? contextmenu.options.width : '100px' }) .appendTo(document.body) .find('.t-item') // select the menu items .mouseenter(function () { if ($(this).hasClass('t-state-disabled') == false) { // hover effect $(this).addClass('t-state-hover'); } }) .mouseleave(function () { // remove the hover effect $(this).removeClass('t-state-hover'); }) .click(function (e) { e.preventDefault(); var li = $(this); if (li.hasClass('t-state-disabled') == false) { $.each(contextmenu.options.menu, function (idx, item) { if (item.text == li.text() && item.onclick != undefined) { // dispatch the click item.onclick(li.text()); return false; } }); } }) .end(); // show the menu with animation $.telerik.fx.play(fx, $popupMenu.find('.t-group'), { direction: 'bottom' }); // handle globally the click event in order to hide the context menu $(document).click(function (e) { // hide the context menu and remove it from DOM $.telerik.fx.rewind(fx, $popupMenu.find('.t-group'), { direction: 'bottom' }, function () { $popupMenu.remove(); }); }); }, show: function () { $(this.element).css('visibility', 'visible'); }, hide: function () { $(this.element).css('visibility', 'hidden'); }, enableItem: function (itemText) { var contextmenu = this; $.each(contextmenu.options.menu, function (idx, item) { if (item.text == itemText) { delete item.disabled; return false; } }); }, disableItem: function (itemText) { var contextmenu = this; $.each(contextmenu.options.menu, function (idx, item) { if (item.text == itemText) { item.disabled = true; return false; } }); } };
Creating the MVC Extension for the Popup Menu
I created the PopupMenuExtension class to initialize and render the popup menu. I added a PopupMenu extension method to extend the Telerik ViewComponentFactory so that I could give the appearance that the PopupMenuExtension was part of the Telerik MVC Extensions:
namespace Telerik.Web.Mvc.UI { public static class TelerikMvcExtensions { public static PopupMenuExtension PopupMenu(this ViewComponentFactory factory) { return new PopupMenuExtension(factory); } } }
I created the IPopupMenuExtension interface:
namespace Telerik.Web.Mvc.UI { public interface IPopupMenuExtension { IPopupMenuExtension Name(string name); IPopupMenuExtension MenuItems(Action<IMenuItems> menuItemsAction); IPopupMenuExtension Visible(bool visible); void Render(); } }
I created the IMenuItems interface:
namespace Telerik.Web.Mvc.UI { public interface IMenuItems { IMenuItems Add(string text, string onClickHandler); IMenuItems Separator(); } }
I created the PopupMenuExtension class:
namespace Telerik.Web.Mvc.UI { public class PopupMenuExtension : IPopupMenuExtension, IMenuItems { private ViewComponentFactory Factory { get; set; } private string NameAttribute { get; set; } private bool VisibleAttribute { get; set; } private IList<string> MenuItemsList { get; set; } internal PopupMenuExtension(ViewComponentFactory factory) { this.Factory = factory; this.VisibleAttribute = true; this.MenuItemsList = new List<string>(); } public IPopupMenuExtension Name(string name) { this.NameAttribute = name; return this; } public IPopupMenuExtension Visible(bool visible) { this.VisibleAttribute = visible; return this; } public IPopupMenuExtension MenuItems(Action<IMenuItems> menuItemsAction) { menuItemsAction(this); return this; } public IMenuItems Add(string text, string onClickHandler) { this.MenuItemsList.Add(string.Format("{{text:'{0}',onclick:{1}}}", text, onClickHandler)); return this; } public IMenuItems Separator() { this.MenuItemsList.Add("{separator:true}"); return this; } public void Render() { TagBuilder div = new TagBuilder("div"); div.MergeAttribute("id", this.NameAttribute); div.MergeAttribute("style", string.Format("float:left;margin-top:2px;visibility:{0};", this.VisibleAttribute ? "visible" : "hidden")); TagBuilder span = new TagBuilder("span"); span.MergeAttribute("class", "t-icon t-arrow-next t-popup-menu"); div.InnerHtml = span.ToString(); this.Factory.HtmlHelper.ViewContext.Writer.Write(div.ToString()); StringBuilder sb = new StringBuilder(); sb.AppendFormat("jQuery('#{0}').tPopupMenu({{menu:[", this.NameAttribute); for (int idx = 0; idx < this.MenuItemsList.Count; idx++) { if (idx > 0) { sb.Append(","); } sb.Append(this.MenuItemsList[idx]); } sb.Append("]});"); this.Factory.ScriptRegistrar().DefaultGroup(group => group.Add(string.Format("{0}/{1}/telerik.common.min.js", WebAssetDefaultSettings.ScriptFilesPath, WebAssetDefaultSettings.Version)) ).OnDocumentReady(sb.ToString()); } } }