MVC3 Razor — редактирование списка переменной длины

Я следил за блогом Стивена Сандерсона — http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Когда я пытаюсь добавить еще одну строку элементов, а не добавлять строку, она отправляет меня на URL-адрес с возвращенным частичным представлением.

Как предотвратить это и, следовательно, добавить фактическую строку?

Мой код ниже вместе с путем (поскольку я использую Areas):

MyWebUI

Области/Клиент/MyMoveItems/Index.cshtml

@model IEnumerable<MovinMyStuff.Domain.Entities.MoveItem>
@using MovinMyStuff.WebUI.HtmlHelpers

@{
ViewBag.Title = "Index";
}

<h1>My Move Items</h1>
@using (Html.BeginForm())
{
<table class="move-item">
    <tr>
        <th>
            Item
        </th>
        <th class="dimension-header">
            L
        </th>
        <th class="dimension-header">
            W
        </th>
        <th class="dimension-header">
            H
        </th>
        <th class="weight-header">
            Wt
        </th>
        <th class="qty-header">
            Qty
        </th>
        <th>
            Addt'l Work
        </th>
    </tr>
    <tr>
        <td colspan="7">
            <div id="editorRows">
                @foreach (var item in Model)
                {
                    Html.RenderPartial("_MoveItemEditorRow", item);
                }
            </div>
        </td>
    </tr>
</table>
@Html.ActionLink("Add Item", "Add", new { area = "Client" }, new { id = "addItem" })
<input type="submit" value="Finished" />
}

Области/Клиент/MyMoveItems/_MoveItemEditorRow.cshtml

@model MovinMyStuff.Domain.Entities.MoveItem
@using MovinMyStuff.WebUI.HtmlHelpers

@using (Html.BeginCollectionItem("moveitems"))
{
    <div class="editorRow">
<tr>
    <td class="item-name">
        @Html.TextBoxFor(model => model.MoveItemType)
        @Html.ValidationMessageFor(model => model.MoveItemType)
</td>
<td class="item-dimension">
    @Html.EditorFor(model => model.Length)
    @Html.ValidationMessageFor(model => model.Length)
</td>
<td class="item-dimension">
    @Html.EditorFor(model => model.Width)
    @Html.ValidationMessageFor(model => model.Width)
</td>
<td class="item-dimension">
    @Html.EditorFor(model => model.Height)
    @Html.ValidationMessageFor(model => model.Height)
</td>
<td class="item-weight">
        @Html.EditorFor(model => model.Weight)
        @Html.ValidationMessageFor(model => model.Weight)
</td>
<td class="item-qty">
        @Html.EditorFor(model => model.Quantity)
        @Html.ValidationMessageFor(model => model.Quantity)
</td>
<td class="work-items-group">
    <table class="work-items">
        <tr>
            <td>Assembly</td>
            <td>
            @Html.EditorFor(model => model.Assemble)
            @Html.ValidationMessageFor(model => model.Assemble)
            </td>
        </tr>
    </table>
    <table class="work-items">
        <tr>
            <td>Glass</td>
            <td>
            @Html.EditorFor(model => model.HasGlass)
            @Html.ValidationMessageFor(model => model.HasGlass)
            </td>
        </tr>
    </table>
</td>
</tr>
</div>
}

Области/Клиент/Контроллеры/MyMoveItemsControllers.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MovinMyStuff.Domain.Entities;
using MovinMyStuff.Domain.Concrete;

namespace MovinMyStuff.WebUI.Areas.Client.Controllers
{ 
public class MyMoveItemsController : Controller
    {
    private EFDbContext db = new EFDbContext();

    //
    // GET: /Client/MyMoveItems/

    public ActionResult Index()
    {
        var moveitems = db.MoveItems.Include(m => m.Move);
        return View(moveitems);
    }

    [HttpPost]
    public ActionResult Index(IEnumerable<MoveItem> moveitems)
    {
        return View("Completed", moveitems);
    }

    public PartialViewResult Add()
    {
        return PartialView("_MoveItemEditorRow", new MoveItem());
    }
}
}

HtmlHelpers/HtmlPrefixScopeExtension.cs

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

namespace MovinMyStuff.WebUI.HtmlHelpers
{
public static class HtmlPrefixScopeExtensions
{
    private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }

    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
    {
        return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
    }

    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
    {
        // We need to use the same sequence of IDs following a server-side validation failure,  
        // otherwise the framework won't render the validation error messages next to each item.
        string key = idsToReuseKey + collectionName;
        var queue = (Queue<string>)httpContext.Items[key];
        if (queue == null) {
            httpContext.Items[key] = queue = new Queue<string>();
            var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
            if (!string.IsNullOrEmpty(previouslyUsedIds))
                foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                    queue.Enqueue(previouslyUsedId);
        }
        return queue;
    }

    private class HtmlFieldPrefixScope : IDisposable
    {
        private readonly TemplateInfo templateInfo;
        private readonly string previousHtmlFieldPrefix;

        public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
        {
            this.templateInfo = templateInfo;

            previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
            templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
        }

        public void Dispose()
        {
            templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
        }
    }
}
}

Скрипты/mms-custom.js

$("#addItem").click(function () {
$.ajax({
    url: this.href,
    cache: false,
    success: function (html) { $("#editorRows").append(html); }
});
return false;
});

$("a.deleteRow").live("click", function () {
$(this).parents("div.editorRow:first").remove();
return false;
});

Представления/Общие/_ClientLayout.cshtml

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-2.5.3.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/mms-custom.js")" type="text/javascript"></script>
@(Html.Telerik().StyleSheetRegistrar()
    .DefaultGroup(group => group.Add("telerik.common.css")
                                .Add("telerik.default.css"))
)
</head>
<body>
    <section>
    @RenderBody()
</section>
</body>
</html>

person J0NNY ZER0    schedule 08.07.2012    source источник
comment
Очень немногие из нас будут читать этот большой кусок кода, если вы не подскажете, какие его части важны. Пожалуйста, попробуйте несколько перефразировать свой вопрос, объяснив, с какими конкретными строками кода у вас возникли проблемы.   -  person Kirk Woll    schedule 09.07.2012
comment
@KirkWoll - Я думаю, те, кто относится к категории Очень немногие, решили это прочитать. Спасибо за мысли. ( :   -  person J0NNY ZER0    schedule 09.07.2012


Ответы (1)


Попробуй это:

Во-первых: убедитесь, что ваш код jQuery (Scripts/mms-custom.js) находится внутри $(document.ready — он не выглядит так, как если бы вы разместили весь файл выше.

Если это не исправит само по себе, попробуйте следующее:

Второе:

$("#addItem").click(function () {
    $.ajax({
        url: this.href,
        cache: false,
        success: function (data) { $("#editorRows").append(data);  return false;}
    });
    return false;
});

вместо вашего кода:

$("#addItem").click(function () {
$.ajax({
    url: this.href,
    cache: false,
    success: function (html) { $("#editorRows").append(html); }
});
return 

У меня была аналогичная проблема при использовании этого сообщения в блоге в качестве отправной точки.

person Ecnalyr    schedule 09.07.2012
comment
На самом деле я только что понял это! Можете ли вы поверить, что это было потому, что у меня был скрипт в теге head, и он ДЕЙСТВИТЕЛЬНО ДОЛЖЕН быть перед закрывающим тегом body. Это был настоящий B I T _ H человек. Стоил мне часов времени. - person J0NNY ZER0; 09.07.2012
comment
@HelloJonnyOh Вот почему вы всегда должны помещать их в функцию $document.ready, потому что это заставляет .js не начинать работу, пока не будет готов весь документ - когда содержащиеся инструкции будут иметь смысл. Как-то так ^^. Я рад, что это работает. - person Ecnalyr; 09.07.2012
comment
@HelloJonnyOh Также рассмотрите возможность отметить правильный ответ, если хотите. - person Ecnalyr; 09.07.2012