суббота, 29 декабря 2012 г.

Динамическое контекстное меню в ASP.NET MVC



Не так давно у меня появилась необходимость в создать контекстное меню с настраиваемым из code-behind числом элементов для моего ASP.NET MVC приложения. Потраченные полчаса на поиск готового решения не увенчались успехом, в связи чем пришлось заниматься созданием данной функциональности самостоятельно. О том что у меня получилось я хочу рассказать в своей статье.
Итак за основу для решения задачи был взят простенький JQuery-плагин (http://www.trendskitchens.co.nz/jquery/contextmenu/) который преобразует список div с находящимся в нем списком ul в контекстное меню. С динамической генерацией списка проблем нет, более сложным выглядел вопрос динамического создания байндинга обработчиков элементов меню. Но как оказалось это тоже вопрос пяти минут. В итоге получилось простое и достаточно эффективное решение.
Для демонстрации я создал простенькое приложение. При нажатии на зеленую область правой кнопкой мыши появляется меню:


При нажатии на один из элементов меню появляется всплывающее окно с сообщением. В случае представленном ниже мы выбрали меню “Save”.

Итак, пару слов о реализации.

Первым делом подключаем JQuery и плагин контекстного меню:
        <script src="~/Scripts/jquery-1.8.0.min.js" type="text/javascript"></script>
        <script src="~/Scripts/jquery-ui-1.8.23.custom.min.js" type="text/javascript"></script>

        <script src="~/Scripts/jquery.contextmenu.r2.packed.js" type="text/javascript"></script>

В контроллере инициализируем коллекцию элементов меню:
        public ActionResult Test()
        {
            ViewBag.MenuItems = new[] { "Open", "Save", "Edit", "Exit" };
            return View();
        }

В отображении создаем два слоя, первый слой – зеленая область, второй слой – контекстное меню, там генерируем список элементов, которые получили от контроллера:
@{   
    var mi = (IEnumerable<string>)ViewBag.MenuItems;
}

<div id="mainDiv">
    <p>Right click here</p>
</div>

<div class="contextMenu" id="myMenu1">
    <ul>
        @foreach (var item in mi)
        {
            <li id="@item"><img src="@Url.Content("~/Content/images/edit.png")" />@item</li>
        }
    </ul>
</div> 

Добавляем стиль для первого слоя в Site.css:
#mainDiv {
    width: 300px;
    height: 300px;
    background: limegreen;
}

Итак, все что теперь осталось это подключить плагин и настроить байндинг обработчиков. Суть моего решения заключается в том, что мы на лету для каждого элемента меню генерируем соответствующий элемент в байндинге. Обработчиком каждого из элементов будет функция которая просто выводит название элемента меню, который был выбран. Код представленный ниже демонстрирует реализацию.
<script type="text/javascript">

    $(function () {

        $('#mainDiv').contextMenu('myMenu1', {
            @if(mi.Any())
            {
                Html.ViewContext.Writer.WriteLine("bindings: {");
                int count = mi.Count();
                int i = 1;

                foreach(var item in mi)
                {
                    Html.ViewContext.Writer.WriteLine(string.Format("'{0}': function(t) {{ menuHandler('{0}'); }}{1}", item, i == count ? string.Empty : ","));
                    i++;
                }
                Html.ViewContext.Writer.WriteLine("}");
            }
        });
    });
   
    function menuHandler(obj) {
        alert('You can handle menu for "' + obj + '" as you wish');
    }

</script>

Вот и все. Потратив полчаса на реализацию я получил вполне хорошее решение, которое позволяет полностью решить все мои потребности. Надеюсь для вас оно окажется не менее полезным и так же сможет пригодится в случае необходимости реализации подобной функциональности.