سؤال

How to save nested sortable menus in database i had followed this http://www.mikesdotnetting.com/Article/219/Saving-jQuery-Sortables-In-ASP.NET-Razor-Web-Pages

i have searched in google but no help...i want to know what will be the logic and how i can update with nested menus structure

-My Database structure

Table1:Menus
[Columns] Menuid, MenuName, Parent, orderId
Table2:MenuItems
[columns]:Menuitemid, MenuitemName, Menuid(foreignkey), orderId

My Razor View

<div>
 <input type="button" id="getTech" style="width:100px;height:30px" value="Get Order" />
<div>
    <ol class="example">
        @if (ViewData["Menu"] != null)
        {
            foreach (var menu in ViewData["Menuitem"] as   List<DB.Entities.MenuItem>)
            {
                <li class="item" id="@menu.MenuItemId">
                    @menu.MenuItemName
                    <ul></ul>
                </li>
            }
        }
    </ol>
</div>
</div>
<div class="row">
<div class="col-md-2">
<button>@Html.ActionLink("Save Order", "Index")"</button>
</div>
</div>

<script>
$(function () {
    $("ol.example").sortable();
    $('button').on('click', function() {
        var ids = [];

        $('.item').each(function(index, value) {
            var id = $(value).prop('id');
            ids.push(id);
        });
        $.post(
            '/Sort',
            { Ids: JSON.stringify(ids) },

            function(response) {
                if ($('#message').hasClass('alert-info')) {
                    $('#message').text('Items re-ordered successfully.')
                        .removeClass('alert-info')
                        .addClass('alert-success');
                } else {
                    $('#message').effect("highlight", {}, 2000);
                }
            }
        );
    });
})

My Controller Action

public ActionResult Index()
{
try
{
   string menu =  (Request["ids"]);               
   var ids = new JavaScriptSerializer().DeserializeObject(menu) ;
}
catch (Exception ex)
{
}
return View();
}
هل كانت مفيدة؟

المحلول

Below is a example of what I was suggesting in my comments.

It does not include the actual menu logic/plugin as that is assumed to be provided already. It is pretty much everything you would need to add to a fresh MVC application step-by-step. If it is missing anything you need just ask. I will post this as a tutorial on my website at some point.

1. MenuItem class/table

You only need a single table to hold both menu items and sub menus. The only difference is whether they actually have any child items. Menu items are just are id's and text (you could add hyperlinks etc if you wanted).

Using Code-first I added the MenuItems table with this class:

public class MenuItem
{
    // Unique id of menu item
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int MenuItemId { get; set; }

    // Tesxt to display on menu item
    [Required]
    public virtual string MenuItemName { get; set; }

    // Sequential display order (within parent item)
    public virtual int DisplayOrder { get; set; }

    // Foreign key value
    public virtual int? ParentMenuItemId { get; set; }

    [ForeignKey("ParentMenuItemId")]
    // Parent menu item
    public virtual MenuItem ParentMenuItem { get; set; }

    // Child menu items
    public virtual ICollection<MenuItem> ChildItems { get; set; }
}

2. Add reorder action

This is the one that actually reorders the items. It is called via Ajax on the page using a URL like /menu/reorder/2?after=3 which will move item id 2 to after item id 3 in the database.

In the first version of my reordering I used to pass a position, item id and parent id, but found in production, due to complex parent relationships, that was not as useful as simply saying "which item id you want to move", and "which item id you want it placed after" (0 meaning place it first).

    /// <summary>
    /// Reorder the passed element , so that it appears after the specific element
    /// </summary>
    /// <param name="id">Id of element to move</param>
    /// <param name="after">Id of element to place after (or 0 to place first)</param>
    /// <returns>Unused</returns>
    public ContentResult Reorder( int id, int after )
    {
        var movedItem = this.context.MenuItem.Find(id);

        // Find all the records that have the same parent as our moved item
        var items = this.context.MenuItem.Where(x => x.ParentMenuItemId == movedItem.ParentMenuItemId).OrderBy(x => x.DisplayOrder);

        // Where to insert the moved item
        int insertionIndex = 1;

        // Display order starts at 1
        int displayOrder = 1;

        // Iterate all the records in sequence, skip the insertion value and update the display order
        foreach (var item in items)
        {
            // Skip the one to move as we will find it's position
            if (item.MenuItemId != id)
            {
                // Update the position
                item.DisplayOrder = displayOrder;
                if (item.MenuItemId == after)
                {
                    // Skip the insertion point for subsequent entries
                    displayOrder++;
                    // This is where we will insert the moved item
                    insertionIndex = displayOrder;
                }
                displayOrder++;
            }
        }

        // Now update the moved item
        movedItem.DisplayOrder = insertionIndex;

        this.context.SaveChanges();

        return Content(insertionIndex.ToString());
    }

3. View holding the top level menu

Index Action

    //
    // GET: /Menu/
    public ActionResult Index()
    {
        // Return the top level menu item
        var rootMenu = context.MenuItem.SingleOrDefault(x => x.ParentMenuItemId == null);
        return View(rootMenu);
    }

Index.cshtml

This contains the code for the sortable reordering via an Ajax call.

@model jQuery.Vero.Menu.MvcApplication.Models.MenuItem

<h2>Test Menu gets rendered below</h2>

<ul id="menu" class="menu">
    @foreach (var menuItem in Model.ChildItems.OrderBy(x=>x.DisplayOrder))
    {
        @Html.Action("Menu", new { id = menuItem.MenuItemId })
    }
</ul>
@section scripts{
    <script type="text/javascript">
        $(function () { 
            var $menu = $("#menu");
            // Connection menu plugin here
            ...
            // Now connect sortable to the items
            $menu.sortable({
                update: function(event, ui)
                {
                    var $item = $(ui.item);
                    var $itemBefore = $item.prev();
                    var afterId = 0;
                    if ($itemBefore.length)
                    {
                        afterId = $itemBefore.data('id');
                    }
                    var itemId = $item.data('id');
                    $item.addClass('busy');
                    $.ajax({
                        cache: false,
                        url: "/menu/reorder/" + itemId + "?after=" + afterId,
                        complete: function() {
                            $item.removeClass('busy');
                        }
                    });
                }
            });
        });
    </script>
}

4. Recursive Partial View

To display the menu item, I use a recursive action and view.

Menu Action

    /// <summary>
    /// Render one level of a menu. The view will call this action for each child making this render recursively.
    /// </summary>
    /// <returns></returns>
    public ActionResult Menu(int id)
    {
        var items = context.MenuItem.Find(id);
        return PartialView(items);
    }

Menu Partial View - Menu.cshtml

@model jQuery.Vero.Menu.MvcApplication.Models.MenuItem

<li id="Menu@(Model.MenuItemId)" class="menuItem" data-id="@(Model.MenuItemId)">
    @Model.MenuItemName
    @if (Model.ChildItems.Any())
    {
        <ul class="menu">
            @foreach (var menuItem in Model.ChildItems.OrderBy(x => x.DisplayOrder))
            {
                @Html.Action("Menu", new { id = menuItem.MenuItemId })
            }
        </ul>
    }
</li>

5. Additional Styles

These are the only styles I added to the test application. Note I add and remove a busy class on the dropped item so it can show progress etc while the Ajax call is being processed. I use a progress spinner in my own apps.

.menu {
list-style: none;
}

.menu .menuItem{
    border: 1px solid grey;
    display: block;
}

.menu .menuItem.busy{
    background-color: green;
}

6. Sample data

This is an example of the hierarchical menu items I entered (produces screenshot below).

enter image description here

7. Onscreen example of hierarchy

This shows the hierarchy the above code renders. You would apply a menu plugin to the top level UL.

enter image description here

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top