views:

931

answers:

6

I've been looking through a lot of tutorials on jQuery draggable/droppable, and trying to apply it to ASP.NET MVC, but I am really confused.

Most of the samples I keep finding seem pretty difficult to understand at least where it pertains to where things are wired. I'm basically trying to have a targetable box (a 'roster') and a list of units ('attendees'). The goal is to be able to drag any of the units into the box, and they are added to the roster in the database.

Does anyone know of some simpler samples that might shed some light on how to use this part of jQuery with ASP.NET MVC?

For instance, I've been looking at http://philderksen.com/2009/06/18/drag-and-drop-categorized-item-list-with-jquery-and-aspnet-mvc-part-1/ and it is pretty neat, but it just doesn't explain what I need. It doesn't make a lot of sense and most of the code is pretty strewn about, and I can't even trace where certain calls are being made to figure out how things are wired. (How is jQuery calling the Controller actions, for instance, to trigger when something is dropped? How do I get the ID of the item being dragged so I can add it to the target?)

+1  A: 

In jQuery UI droppable, there is an event "drop" that can take a function to execute. So this is where you will need to wire the call to your controller's action to execute something on a "drop". There are other events as well that you can hook into, such as "out", "hover" etc. See here under "Events" for more detail.

Here is an example in hooking/calling your controller's action via "drop":

$('#mylist').droppable({
   drop: function(event, ui) {
       var newItem = ui.droppable;
       var url = <% =Url.Action("Append", "MyController") %>;
       $.post(url, { newItemId: newItem[0].id });
   }
});
Johannes Setiabudi
+5  A: 

Stacey - I see you're referencing my blog and would love to help. I'm blogging on a larger jquery asp.net mvc drag and drop project so I split up my posts into parts and I'm only about halfway through (3 parts so far). Basically, the info you're looking for is not all there yet, but should be soon.

For starters, I debug events using Firebug's logging feature. Here's an example testing events with jQuery UI's sortable() method:

$("#mylist").sortable(
{
    ...
    start: function(event, ui)
    {
        console.log("-- start fired --");
        console.log($(ui.item));
    },

    update: function(event, ui)
    {
        console.log("-- update fired --");
        console.log($(ui.item));
    },

    deactivate: function(event, ui)
    {
        console.log("-- deactivate fired --");
        console.log($(ui.item));
    }
});

When an item is dropped using sortable(), it fires the update event. I use jQuery's AJAX post method to send the data to a controller.

$("#mylist").sortable(
{
    ...
    update: function(event, ui)
    {
        //Extract column num from current div id
        var colNum = $(this).attr("id").replace(/col_/, "");

        $.post("/Section/UpdateSortOrder",
            { columnNum: colNum, sectionIdQueryString: $(this).sortable("serialize") });

    }
});

The variable colNum is extracting the id by parsing the id attribute that's set in the view. See part 3 on my blog for how this is rendered. Then both the column number and set of section id's (serialized in jquery) are posted to the controller.

The controller method resides in /Controllers/SectionController.cs and only accepts posts:

    private SectionRepository secRepo = new SectionRepository();

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult UpdateSortOrder(int columnNum, string sectionIdQueryString)
    {
        string[] separator = new string[2] { "section[]=", "&" };
        string[] sectionIdArray = sectionIdQueryString.Split(separator, StringSplitOptions.RemoveEmptyEntries);

        secRepo.UpdateSortOrder(columnNum, sectionIdArray);
        secRepo.Save();

        return Content("Success");
    }

Hope that helps for now.

Phil Derksen
That's quite a handful. You're so far ahead of me that I'm pretty lost. But I'll see if I can keep up - so I basically want to store the ID of each item in the View? So you set the HTML id element to col_### where ## is the "id", and this is what you pass through jQuery?
Stacey
Please forgive me, Phil, I have tried your code out and I am still very confused. I am an extreme rookie with jQuery. I'm not really trying to change the order, I'm trying to basically add items to a list. Do you know when you might have more of this published so I can keep researching it?
Stacey
To answer your first question, yes I'm setting the id of each element to col_### with server-side code. Then when jQuery fires off the update event I'm grabbing that id attribute, parsing the id # from it, then passing the id # to the controller using jQuery's $.post().
Phil Derksen
A: 

Alright, I'm trying to follow Phil's instructions, this is what I have so far... I hope I am even on the right track. This is all very new to me. I'm trying and trying, but the 'update' stuff is never firing. . .

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Draggable.Item>>" %>

<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
    Index
</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <h2>
     Index</h2>
    <div style="float: left; width: 250px;">
     <ul id="sortable" class="itemBox">
      <% foreach (var item in Model)
      { %>
      <% Html.RenderPartial("Item", item); %>
      <% } %>
     </ul>
    </div>
    <div id="droppable" class="ui-widget-header">
     <p>
      Drop here</p>
    </div>
</asp:Content>
<asp:Content ContentPlaceHolderID="ScriptContent" runat="server">
    <style type="text/css">
     .draggable {
      width: 100px;
      height: 100px;
      padding: 0.5em;
      float: left;
      margin: 10px 10px 10px 0;
     }
     #droppable {
      width: 150px;
      height: 150px;
      padding: 0.5em;
      float: left;
      margin: 10px;
     }
    </style>

    <script type="text/javascript">
     $(function() {
      $("#sortable").sortable({
       update: function(event, ui) {
        //Extract column num from current div id
        var colNum = $(this).attr("id").replace(/item_/, "");

        $.post("UpdateSortOrder", { columnNum: colNum, sectionIdQueryString: $(this).sortable("serialize") });
       }
      });
      $("#droppable").droppable({
       drop: function(event, ui) {
        $(this).find('p').html('Dropped!');
        //Extract column num from current div id
        var colNum = $(this).attr("id").replace(/item_/, "");

        $.post("UpdateSortOrder", { columnNum: colNum, sectionIdQueryString: $(this).sortable("serialize") });
       }

      });
     });
    </script>

</asp:Content>

And the Item.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Draggable.Item>" %>

<li class="itemRow" id="item_<%= Model.ItemId %>">
    <p>Drag me to my target</p>
</li>

And the repository...

using System;
using System.Linq;

namespace Draggable
{
    public partial class ItemRepository
    {
     DatabaseDataContext database = new DatabaseDataContext();

     public IQueryable<Item> GetItems()
     {
      var items = from i in database.Items
         select i;
      return items;
     }
    }
}

And the controller

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

namespace Draggable.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Index/

        public ActionResult Index()
        {
      ItemRepository repository = new ItemRepository();

            return View("Index", repository.GetItems());
        }

     public ActionResult Item()
     {
      return View();
     }

    }
}
Stacey
So I want the second box (droppable) to represent a "ListId". And dropping something onto it will add the itemid to a table with the listid (entry table). Am I taking the right approach, or am I just way off base already?
Stacey
I guess I'm not quite understanding your UI. My sample only includes sortable(). Maybe you need that, or maybe you should just use draggable() and droppable() together and not sortable().Look through the sample code at http://jqueryui.com/demos/ and find something that's most similar to what you're doing.
Phil Derksen
A: 

Here, I made some changes - I apologize for the confusion. It still isn't quite working how I'm trying to get it to. Is it possible to make it not fire events if things are re-arranged in their original list, but only when dropped onto another list?

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Draggable.Item>>" %>

<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
    Index
</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <h2>
     Index</h2>
    <div style="float: left; width: 250px;">
     <ul class="itemBox">
      <% foreach (var item in Model)
      { %>
      <% Html.RenderPartial("Item", item); %>
      <% } %>
     </ul>
    </div>
    <div style="float: left; width: 250px;">
     <ul class="itemBox">
      <p>
       Drop here</p>
     </ul>
    </div>
</asp:Content>
<asp:Content ContentPlaceHolderID="ScriptContent" runat="server">
    <style type="text/css">
     #draggable {
      width: 100px;
      height: 100px;
      padding: 0.5em;
      float: left;
      margin: 10px 10px 10px 0;
     }
     #droppable {
      width: 150px;
      height: 150px;
      padding: 0.5em;
      float: left;
      margin: 10px;
     }
    </style>

    <script type="text/javascript">
     $(function() {
      $(".itemList").sortable({
       connectWith: ".itemList",
       containment: "document",
       cursor: "move",
       opacity: 0.8,
       placeholder: "itemRowPlaceholder",

       update: function(event, ui) {
        //Extract column num from current div id
        var colNum = $(this).attr("id").replace(/col_/, "");
        $.post("/Home/UpdateSortOrder", { columnNum: colNum, sectionIdQueryString: $(this).sortable("serialize") });
       }
      });
     });
    </script>

</asp:Content>
Stacey
A: 

This method gets the styling a lot closer to how your sample is ...but it really doesn't work. It doesn't get the id of the element - but making the elements themselves sortable doesn't seem to work either....

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Draggable.Item>>" %>

<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
    Index
</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <h2>
     Index</h2>
    <div class="itemBox">
     <ul class="itemList">
      <% foreach (var item in Model)
      { %>
      <% Html.RenderPartial("Item", item); %>
      <% } %>
     </ul>
    </div>
    <div class="itemBox">
     <ul class="itemList">
      <p>
       Drop here</p>
     </ul>
    </div>
</asp:Content>
<asp:Content ContentPlaceHolderID="ScriptContent" runat="server">
    <script type="text/javascript">
     $(function() {
      $(".itemList").sortable({
       connectWith: ".itemList",
       containment: "document",
       cursor: "move",
       opacity: 0.8,
       placeholder: "itemRowPlaceholder",

       update: function(event, ui) {
        //Extract column num from current div id
        var colNum = $(this).attr("id").replace(/col_/, "");
        alert(colNum);
        $.post("/Home/UpdateSortOrder", { columnNum: colNum, sectionIdQueryString: $(this).sortable("serialize") });
       }
      });
     });
    </script>

</asp:Content>
Stacey
Yeah, the big problem is that it won't find the item itself, but rather the $(this) section of the code gets the id for the overall container.Making the items themselves sortable doesn't really solve it, either, as that makes styling all strange...
Stacey
A: 

Zing! It has been done. The problem was $(this).attr("id"). It needed to be $(ui.item).attr("id"). This returns the element being dragged, rather than the sortable container. Thank you SO much for all of your help.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Draggable.Item>>" %>

<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
    Index
</asp:Content>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <ul id="sortable1" class="connectedSortable">
     <% foreach (var item in Model)
     { %>
     <% Html.RenderPartial("Item", item); %>
     <% } %>
    </ul>
    <ul id="sortable2" class="connectedSortable">
    </ul>
</asp:Content>
<asp:Content ContentPlaceHolderID="ScriptContent" runat="server">
    <style type="text/css">
     #sortable1, #sortable2 {
      list-style-type: none;
      margin: 0;
      padding: 0;
      float: left;
      margin-right: 10px;
     }
     #sortable2 {
      height: 400px;
      width: 140px;
      background: #ccc;
     }
     #sortable1 li, #sortable2 li {
      margin: 0 5px 5px 5px;
      padding: 5px;
      font-size: 1.2em;
      width: 120px;
     }
    </style>

    <script type="text/javascript">
     $(function() {
      $("#sortable1").sortable({
       connectWith: '.connectedSortable'
      }).disableSelection();
      $("#sortable2").sortable({
       connectWith: '.connectedSortable',
       receive: function(event, ui) {
        var colNum = $(ui.item).attr("id").replace(/col_/, "");
        $.post("/Home/UpdateSortOrder", { columnNum: colNum, sectionIdQueryString: $(this).sortable("serialize") });
       }
      }).disableSelection();
     });
    </script>
</asp:Content>
Stacey