Telerik MVC : Leveraging the Telerik MVC Framework to Create a Popup Menu Control

This website is no longer actively supported

Written by John DeVightJohn 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

dropdown-with-popup-menu-icon.png

DropDownList control with the Popup Menu displayed

dropdown-with-popup-menu-displayed.png

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());
        }
   }
}

Discussion Closed

Add a New Comment

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License