【问题标题】:ASP.NET MVC jQuery Ajax - close and refresh parent table from modal dialogASP.NET MVC jQuery Ajax - 从模式对话框关闭并刷新父表
【发布时间】:2015-05-07 06:08:22
【问题描述】:

对于 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刷新父页面上的表格,无需插件?

更新:

在埃克特的帮助下,我取得了一些进展。

我试图避免使用 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 报告错误:

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

【问题讨论】:

    标签: javascript jquery ajax asp.net-mvc


    【解决方案1】:

    模态对话框总是空白,而不是关闭。

    它的行为可能不正确,因为您是基于对象的对话框方法创建变量,而不是对象本身。试试这个:

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

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

    当然可以,但可能需要您更改一些内容,包括关闭对话框的代码。以下是我的处理方式:

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

    2) 我们不会在 on-submit 方法中处理您的对话框关闭代码,而是使用 ajax 选项“OnSuccess”来执行此操作(以及“OnFailure”来处理您的“Something went failed”错误)。

    这是您的新索引视图:

    @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
    

    大多数索引视图都没有改变,除了我们现在使用包含部分视图的 a。此部分视图将包括记录表,代码如下:

    创建一个名为“RecordsPartial”的新局部视图:

    @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>
    }
    

    现在您的“创建”视图将更新为使用 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 来处理您的表单发布后发生的情况。发布后返回的数据(我们更新的部分记录)将替换目标 ID“recordsDiv”的当前内容。 OnSuccess 函数“postSuccess”将处理关闭对话框。 OnFailure 函数“postFailed”将报告发生了不好的事情。最后要提到的是,我们将后期操作从“Create”更改为“CreateRecord”。在处理 ajax-data 返回时,我更喜欢使用唯一的操作名称。这只是一种更清洁的方法。

    在我们定义新的“CreateRecord”后操作之前,我们需要实现我们的成功和失败函数。只需在主“索引”视图中的脚本部分块的底部创建它们:

    @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>
    }
    

    最后,我们创建“CreateRecord”后操作,它将处理我们的表单并返回更新的“部分记录”视图:

    [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);
    }
    

    这是重复您现有的一些“创建”操作。我们简单地处理我们的帖子数据,然后我们得到一个新的更新记录列表,最后我们将该记录列表返回到我们的“RecordsPartial”视图,该视图将插入到我们在 ajax-options 中指定的“recordsDiv”中.

    非常干净和简单的解决方案。如果您有任何问题,请随时提出。

    【讨论】:

    • 谢谢@Eckert。当我尝试按照您的建议关闭对话框时,我收到错误“错误:在初始化之前无法调用对话框上的方法;试图调用方法'close'” - 这就是我尝试先初始化它的原因。我会处理你的部分观点和 Ajax 助手建议,并让你知道。欣赏!
    • 最后一点有问题。 CreateRecord 发布操作正在向局部视图发送一种 StockIndexEditViewModel,而不是 IEnumerable,这当然会引发异常。
    • 对不起,我误读了你的一些代码。只需以与初始表相同的方式用您的记录列表填充“记录”变量。我将编辑我的帖子以尝试使其更清晰。另外,我相信您在关闭该对话框时遇到的错误应该通过我提供的 ajax 代码更改自行解决,因为“OnSuccess”函数包含在您的主索引视图中。
    • 想了很多,然后重新查询它们,然后将它们传递进去。还是有问题。模态对话框中的表单现在发布并重定向到 /AdminStockIndex/Create(尚未重命名) - 它只显示“RecordsPartial”视图 - 而不是整个视图。我想这是有道理的,因为这就是 post 方法返回的内容……但这不是我想要的效果。我对 AjaxOptions 进行了三重检查,它们与您编写的完全一样。它没有像预期的那样替换 div...只是重定向到它。
    • 一个问题...您的索引视图中是否包含 jquery.unobtrusive-ajax 脚本?我忘了提到这个脚本是 ajax 组件正常工作所必需的。如果你的项目中没有这个脚本,你可以下载 nuget 包(Microsoft jQuery Unobtrusive Ajax)。
    【解决方案2】:

    在您的主索引视图中,与其调用将您的创建视图插入到您的视图中,不如让它最初在视图加载时出现,并将其隐藏在一个 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() 命令时,它似乎会干扰您的 modal-close 命令(经过我自己的一些测试)。我建议您将表单的“创建”按钮更改为 type="button" 而不是“提交”,然后使用按钮的 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/

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

    【讨论】:

    • 我无法让客户端验证正常工作,尝试这种方式。我更改了对表单选择器(相对于按钮)的所有“this”引用,但这是不行的。表单从不提交......有或没有任何数据。我已经按照我发布的方式进行了所有工作,所以我坚持下去。再次感谢您的所有帮助,无论如何...非常感谢!
    • 对不起,我回答得太快了。我的代码中的“this”引用是从您的代码中复制粘贴的。我没有调整它以满足标准。 url:'@Url.Action("CreateRecord", "AdminStockIndex")',类型:"POST",数据:$("form").serialize()
    • 非常感谢你们在这方面所做的工作!!!!几年后,你们真的帮助了我。我把它翻译成 mvc 核心风格......花了我太长时间,但谢谢!
    【解决方案3】:

    我找到了一种方法来获得我想要的一切,同时保留“纯”jQuery Ajax 方法,而不是求助于 Eckert 建议的 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>
    }
    

    注意全局“_dialog”变量。这使我可以从 Create 表单访问对话框,因此可以将其关闭:

    <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>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多