ASP.NET MVC jQuery Ajax - 从模态对话框

时间:2015-05-05 14:56:24

标签: javascript jquery ajax asp.net-mvc

MVC和jQuery都很新,所以我不知道如何让它工作。我用一个ajax回发拼凑了一个(有点)工作模式对话框。一直在寻找两天,没有找到伟大的MVC + jQuery示例。数据按预期插入,这只是我很难用的UI。

我有两个观点:索引和创建。索引列出普通表中的所有记录,使用Razor循环结果。 Create是插入表单,我将其加载到模态对话框中:

@model IEnumerable<MyProject.Models.StockIndex>

@{
    ViewBag.Title = "Admin: Stock Index Home";
    Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}

<h2>View Stock Indices</h2>

<p>
    <a href="#" id="createLink">Create New</a>
    <div id="createStockIndexForm"></div>
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Description)
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }

</table>

@section Scripts {
    <script>
        $('#createLink').on('click', function () {
            $("#createStockIndexForm").dialog({
                autoOpen: true,
                position: { my: "center", at: "top+350", of: window },
                width: 600,
                resizable: false,
                title: 'Create Stock Index',
                modal: true,
                open: function () {
                    $(this).load('@Url.Action("Create", "AdminStockIndex")');
                }
            });

            return false;
        });
    </script>
}

控制器操作:

    public ActionResult Create()
    {
        var model = new StockIndexEditViewModel()
        {
            StockIndices = GetIndices()
        };

        return View(model);
    }

    [HttpPost]
    public ActionResult Create(StockIndexEditViewModel model)
    {

        if (ModelState.IsValid)
        {
            ...
        }

        return PartialView(model);
    }

加载到对话框中的“创建”表单(上图):

@model MyProject.Models.StockIndexEditViewModel

@{
    ViewBag.Title = "CreatePartial";
    Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}

<h2>CreatePartial</h2>


@using (Html.BeginForm("Create", "AdminStockIndex", FormMethod.Post, new { id = "createForm" }))
{
    @Html.AntiForgeryToken()

    <div id="result"></div>
    <div class="form-horizontal">
        <h4>StockIndexEditViewModel</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            @Html.LabelFor(model => model.SelectedParentId, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DropDownListFor(model => model.SelectedParentId, Model.StockIndices, "- Select One -", new { @class = "form-control" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" id="createButton" class="btn btn-default" />
            </div>
        </div>
    </div>
}

@section Scripts {
    <script>
        $("#createForm").on("submit", function (event) {
            event.preventDefault();

            $.ajax({
                url: this.action,
                type: this.method,
                async: true,
                data: $(this).serialize(),
                success: function (data) {
                    if (data) {
                        var createForm = $("#createStockIndexForm").dialog();
                        createForm.dialog("close");
                    }
                    else {
                        $("#result").append("Something went fail.");
                    }
                }
            });
        });
    </script>
}

模态对话框始终为空白,而不是关闭。在使用Firebug在Firefox中进行测试时,我偶尔会看到此错误,但不是每次都看到错误:

  

InvalidAccessError:不支持参数或操作   基础对象

在搜索时,我发现这是一个CORS问题,其中FF正在执行规则而其他浏览器可能不是。令人沮丧的是,错误并不总是发生 - 似乎是随机的。在Chrome中表现相同但不会在JS控制台中引发错误。

首先,我该如何解决这个问题?其次,有没有办法通过ajax刷新父页面上的表,没有插件?

更新

在Eckert的帮助下,我取得了一些进展。

我正在努力避免使用MVC Ajax助手并坚持使用“纯粹的”jQuery方法。我用一个div替换了Index上的记录列表,其中包含一个PartialView:

<div id="stockIndices">
    @Html.Partial("_StockIndices", Model)
</div>

我通过使用jQuery对话框的close属性重新加载div来使基础表刷新工作:

    $('#createLink').on('click', function () {
        $("#createStockIndexForm").dialog({
            autoOpen: true,
            position: { my: "center", at: "top+400", of: window },
            width: 600,
            resizable: false,
            title: 'Create Stock Index',
            modal: true,
            open: function () {
                $(this).load('@Url.Action("Create", "AdminStockIndex")');
            },
            close: function () {
                $("#stockIndices").load('@Url.Action("GetStockIndices", "AdminStockIndex")');
            }
        });

        return false;
    });

手动关闭模态对话框后,div会重新加载我想要的内容。大!现在如果我可以在表单发布时关闭对话框,我会被设置。这不起作用:

$("#createStockIndexForm").dialog("close");

Firebug报告错误:

  

错误:在初始化之前无法调用对话框上的方法;   试图调用方法'关闭'

3 个答案:

答案 0 :(得分:1)

  

模态对话框始终为空白,而不是关闭。

它可能表现不正确,因为您根据对象的对话方法创建变量,而不是对象本身。试试这个:

if (data) {
  $("#createStockIndexForm").dialog("close");
}
  

其次,有没有办法刷新父页面上的表格   ajax,没有插件?

确实如此,但可能需要您更改一些内容,包括关闭对话框的代码。以下是我将如何处理它:

1)您的记录表应该是主索引视图中的部分视图。这样做的原因是,当我们提交表单时,我们将使用ajax-option&#34; InsertionMode&#34;与target-id结合使用表单中更新的旧记录表替换旧记录表。

2)我们不是在提交方法中处理对话框关闭代码,而是使用ajax-option&#34; OnSuccess&#34;要做到这一点(以及&#34; OnFailure&#34;处理你的事情&#39;事情失败&#39;错误)。

所以,这是你的新索引视图:

@model IEnumerable<MyProject.Models.StockIndex>

@{
  ViewBag.Title = "Admin: Stock Index Home";
  Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}

<h2>View Stock Indices</h2>

<p>
  <a href="#" id="createLink">Create New</a>
  <div id="createStockIndexForm"></div>
</p>

<div id="recordsDiv">
  @Html.Partial("RecordsPartial", model)
</div>

// all your script stuff can still go at the end

大多数索引视图都没有改变,但我们现在使用包含局部视图的视图除外。该部分视图将包括记录表,在此处编码:

创建名为&#34; RecordsPartial&#34;:

的新部分视图
@model IEnumerable<MyProject.Models.StockIndex>

<table class="table">
<tr>
    <th>
        @Html.DisplayNameFor(model => model.Name)
    </th>
    <th>
        @Html.DisplayNameFor(model => model.Description)
    </th>
    <th></th>
</tr>

@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Description)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
            @Html.ActionLink("Details", "Details", new { id = item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.Id })
        </td>
    </tr>
}

现在你的&#34;创建&#34;视图将更新为使用mvc-ajax助手而不是使用所有其他javascript代码:

@model MyProject.Models.StockIndexEditViewModel

@{
ViewBag.Title = "CreatePartial";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}

<h2>CreatePartial</h2>

@using (Ajax.BeginForm("CreateRecord", "AdminStockIndex", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "recordsDiv", OnSuccess = "postSuccess", OnFailure = "postFailed" })
{
  @Html.AntiForgeryToken()

  <div id="result"></div>
    <div class="form-horizontal">
      <h4>StockIndexEditViewModel</h4>
      <hr />
      @Html.ValidationSummary(true, "", new { @class = "text-danger" })

      /* form fields here */

      <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
          <input type="submit" value="Create" id="createButton" class="btn btn-default" />
        </div>
      </div>
  </div>
}

我们将您的表单更改为ajax-post,并且我们已添加了ajax-options来处理表单发布后发生的情况。在帖子之后返回的数据(我们更新的记录部分)将替换target-id&#34; recordsDiv&#34;的当前内容。 OnSuccess功能&#34; postSuccess&#34;将处理关闭对话框。 OnFailure功能&#34; postFailed&#34;会报告发生了一件坏事。最后要提到的是,我们改变了后期行动,从#34;创建&#34;到&#34; CreateRecord&#34;。在使用ajax-data返回时,我更喜欢使用唯一的动作名称。这只是一种更清洁的方法。

在我们定义新的&#34; CreateRecord&#34;之前在行动之后,我们需要实施我们的成功和失败功能。只需在主&#34;索引&#34;中的脚本部分块的底部创建它们。视图:

@section Scripts {
<script>
  // ... other stuff that was already here ...

  function postSuccess() {
    $("#createStockIndexForm").dialog("close");
  }

  function postFailed() {
    alert("Failed to post");  // you can define your own error
  }
</script>
}

最后,我们创建&#34; CreateRecord&#34; post-action,它将处理我们的表单并返回更新的&#34;记录部分&#34;视图:

[HttpPost]
public ActionResult CreateRecord(StockIndexEditViewModel model)
{
  if (ModelState.IsValid)
  {
    ... create record here ...
  }

  var records = db.Indexes.ToList(); // or whatever your table name is

  return PartialView("RecordsPartial", records);
}

这是重复你现有的一些&#34;创建&#34;行动。我们只是处理我们的帖子数据,然后我们得到一个新的更新记录列表,最后我们将记录列表返回给我们的&#34; RecordsPartial&#34;视图,将被插入我们的&#34; recordsDiv&#34;正如我们在ajax-options中指定的那样。

非常简洁的解决方案。如果您有任何疑问,请随时提出。

答案 1 :(得分:1)

在主索引视图中,不是要求将您的“创建”视图插入到视图中,而是让它最初出现在视图加载中,并将其隐藏在div中:

<div id="createStockIndexForm">
  @Html.Action("Create", "AdminStockIndex")
</div>

在您的索引脚本部分,我们正在创建您的点击事件外的对话框。我们也将“autoOpen”值变为false,因此div隐藏在视图加载状态。

索引脚本部分:

@section Scripts {
<script>
    $("#createStockIndexForm").dialog({
            autoOpen: false,
            position: { my: "center", at: "top+350", of: window },
            width: 600,
            resizable: false,
            title: 'Create Stock Index',
            modal: true
    });

    $('#createLink').on('click', function () {
        $("#createStockIndexForm").show();
    });
</script>

}

此外,当您使用PreventDefault()命令时,它似乎干扰了您的模态关闭命令(在我自己的一些测试之后)。我建议您更改表单的“创建”按钮以键入=“按钮”而不是“提交”,然后使用按钮的ID来处理您的ajax-post方法。

<input type="button" id="createButton" value="Create" class="btn btn-default" />

将其添加到底部的主索引脚本部分:

$("#createButton").on("click", function () {

        $.ajax({
            url: this.action,
            type: this.method,
            async: true,
            data: $(this).serialize(),
            success: function (data) {
                if (data) {
                    $("#createStockIndexForm").dialog("close");
                }
                else {
                    $("#result").append("Something went fail.");
                }
            }
        });
    });

确保将“关闭”命令指向对话框对象本身。

这是我创建的小提琴,它向您展示了它应该是什么的要点: http://jsfiddle.net/ch5aLezg/

因此,回顾一下脚本的内容,你的“创建”部分应该没有任何脚本。所有脚本都进入主索引视图,正如我在这个答案中详述的那样。

答案 2 :(得分:1)

我找到了一种方法来获得我想要的一切,同时保留了纯粹的&#34; Eckert建议使用jQuery Ajax方法,而不是求助于MVC Ajax助手。然而,他的建议引导我找到解决方案。非常感谢!

我在控制器中创建了一个PartialView:

    public ActionResult GetStockIndices()
    {
        _service = new StockIndexService();
        var data = _service.GetAll();

        return PartialView("_StockIndices", data);
    }

......及其观点:

@model IEnumerable<MyApp.Models.StockIndex>

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Description)
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }

</table>

然后我更改了模态对话框脚本以在关闭时加载局部视图。对于子孙后代,这里是整个索引视图:

@model IEnumerable<MyApp.Models.StockIndex>

@{
    ViewBag.Title = "Admin: Stock Index Home";
    Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}

<h2>View Stock Indices</h2>

<p>
    <a href="#" id="createLink">Create New</a>
    <div id="createStockIndexForm"></div>
</p>
<div id="stockIndices">
    @Html.Partial("_StockIndices", Model)
</div>

@section Scripts {
    <script>
        var _dialog;

        $('#createLink').on('click', function () {
            _dialog = $("#createStockIndexForm").dialog({
                autoOpen: true,
                position: { my: "center", at: "top+400", of: window },
                width: 600,
                resizable: false,
                title: 'Create Stock Index',
                modal: true,
                open: function () {
                    $(this).load('@Url.Action("Create", "AdminStockIndex")');
                },
                close: function () {
                    $("#stockIndices").load('@Url.Action("GetStockIndices", "AdminStockIndex")');
                }
            });

            return false;
        });
    </script>
}

注意全球&#34; _dialog&#34;变量。这使我可以从“创建”表单访问该对话框,因此可以将其关闭:

<script>
    $("#createForm").on("submit", function (event) {
        event.preventDefault();

        $.ajax({
            url: this.action,
            type: this.method,
            async: true,
            data: $(this).serialize(),
            success: function (data) {
                if (data) {
                    if (_dialog) {
                        _dialog.dialog("close");
                    }
                }
                else {
                    $("#result").append("Error! Record could not be added.");
                }
            },
            error: function (error) {
                console.error(error);
            }
        });
    });
</script>