سلام خدمت شما ، در این مقاله میخوام با استفاده از Modal بوت استرپ فرم های ویرایش ، اضافه ، و حذف را در همان صفحه باز کنیم بدون اینکه صفحه ای جدا طراحی کنیم. این ایده ی نمایش مشاهده ی ویرایش استاندارد با ویرایشگرهای طبقه بندی شده ی استاندارد برای اشیای master و آرگومان های آن با لیستی از ویرایشگر ها که اجازه ی ویرایش لیستی از اشیای فرزندان را در همان صفحه دارند , است.
مقدمه:
معمولا چیزی که ما از داخل جعبه از قالب های VS2013 ASP.NET MVC طبقه بندی شده خارج میکنیم مدل های ساده است.
به سرعت فهمیدم که این کار ساده ای نیست تا اینکه معجزه ی گسترش RenderAction Http را کشف کردم. من سعی کردم که نمونه های بسیار آسان را به آن الحاق کنم اما پایه ی دانش ASP.NET MVC مورد نیاز است .
کد های اولیه :
در ابتدا اجازه دهید که یک پروژه ی ASP.NET استاندارد و جدیدی بنام TestAjax با انتخاب قالب MVC با قراردادن مرجع اصلی MVC و بدون تصدیق بسازیم.
برای ساده نگه داشتن آن بیاید دو مدل بسازیم : اشخاص و لیست آدرس های مرتبط با هر شخص
namespace TestAjax.Models { public class Person { public int Id { get; set; } [Display(Name = "First Name")] [Required] [StringLength(255, MinimumLength = 3)] public string Name { get; set; } [Display(Name = "Last Name")] [Required] [StringLength(255, MinimumLength = 3)] public string Surname { get; set; } public virtual ICollection<Address> Addresses { get; set; } } } namespace TestAjax.Models { public class Address { public int Id { get; set; } [Required] [StringLength(255, MinimumLength = 3)] public string City { get; set; } [Display(Name = "Street Address")] public string Street { get; set; } [Phone] public string Phone { get; set; } public int PersonID { get; set; } public virtual Person Person { get; set; } } }
حال که مدل هایمان را سر جای خود دادیم کنترلر ها را میسازیم. بر روی فولدر کنترلر راست کلیک کنید و گزینه ی افزودن کنترلر را انتخاب کنید و از بخش دیالوگ ” MVC 5 Controller with views, using Entity Framework ” را انتخاب کنید. حال شخص را به عنوان کلاس مدل انتخاب میکنیم , یک DataDb جدید میسازیم و همه chechbox های داخل دیالوگ را انتخاب میکنیم. پروژه را بازسازی کنید و کنترلر دیگری برای انتخاب آدرس DataDb ساخته شده بیافزایید.
برای آماده سازی مرحله ی نهایی کنترلر خانه و فولدر خانه از بخش مشاهده را حذف کنید. در مشاهده _Layout اشتراک گذاری شده آیتم های منوی استاندارد را با کلمه ی Index تعویض کنید , درباره ی Contact با یکی که اشاره به People دارد :
@Html.ActionLink("People","Index","People"),
و در نهایت در App_Start\RouteConfig.cs ما باید کلمه Index را با People جایگزین کنیم که برنامه مان به طور پیش فرض به کنترلر People اشاره میکند.حال که پروژه مان تکمیل و اجرا شده ما , تغییرات ارائه خواهند شد.
آیکون های Bootstrap در دکمه ها
برای زیباسازی دکمه های نمونه من تصمیم گرفتم که دکور آنها را به صورت Bootstarp glyphicons دربیاورم. به خاطر سینتکس های glyphicon ها :
<button type="button" class="btn btn-default btn-lg"> <span class="glyphicon glyphicon-star"></span> Star </button>
من یک HtmlHelper نیز افزوده ام تا خروجی صحیح Html را از کمک کننده ی ActionLink بدست آورد.
در داخل پروژه فولدر های HtmlHelper بسازید و داخل آنها کلاس MyHelper.cs
کد کمک کننده :
// As the text the: "<span class='glyphicon glyphicon-plus'></span>" can be entered public static MvcHtmlString NoEncodeActionLink(this HtmlHelper htmlHelper, string text, string title, string action, string controller, object routeValues = null, object htmlAttributes = null) { UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext); TagBuilder builder = new TagBuilder("a"); builder.InnerHtml = text; builder.Attributes["title"] = title; builder.Attributes["href"] = urlHelper.Action(action, controller, routeValues); builder.MergeAttributes(new RouteValueDictionary(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes))); return MvcHtmlString.Create(builder.ToString()); }
حال برای دکمه ی glyphed خروجی با استفاده از ActionLink از این سینتکس استفاده میکنیم :
@Html.NoEncodeActionLink("<span class='glyphicon glyphicon-plus'></span>", "Add new Person", "Create", "People", routeValues: null, htmlAttributes: new { @class = "btn btn-primary" })
در داخل شاخص کنترلر مشاهده ی Pepalle@using TextAjax را بیافزایید. Helper ها ,هدایتگر بالای صفحه میباشند. بعد از آن جای @Html.ActionLink(“Create New” , “Create”) را با متن زیر تغییر دهید :
<div class="pull-right"> @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-plus'></span>", "Add new Person", "Create", "People", routeValues: null, htmlAttributes: new { @class = "btn btn-primary" }) </div>
ویرایش , جزئیات , ساختن ActionLink با :
<div class="pull-right"> @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-pencil'></span>", "Edit", "Edit", "People", routeValues: new { id = item.Id }, htmlAttributes: new { data_modal = "", @class = "btn btn-default" }) @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-search'></span>", "Details", "Details", "People", routeValues: new { id = item.Id }, htmlAttributes: new { data_modal = "", @class = "btn btn-default" }) @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-trash'></span>", "Delete", "Delete", "People", routeValues: new { id = item.Id }, htmlAttributes: new { data_modal = "", @class = "btn btn-danger" }) </div>
حال ما باید آیکون های خودمان را در جای مناسب قرار دهیم.
لیست ها
یک تصویر برای درک بهتر براتون قرار میدم.
RenderAction خروجی را از کار های مختلف بر مشاهده تزریق میکند.
تنها چیزی که باید به خاطر داشته باشیم این است که کنترلر فرزند نباید اجازه “فرار” از مشاهده داشته باشد. به همین این دلیل ما تمام مشاهدات فرزندان را به عنوان بخشهای ویرایش شده در مکان با مدل ها اجرا میکنیم.
برای شروع کنترلر ویرایش مشاهده ی Pepale را ویرایش میکنیم. درست قبل از “برگشت به لیست” تکه ی زیر را وارد نمایید :
<div class="row"> <div class="col-md-offset-2 col-md-10"> @{ Html.RenderAction("Index", "Addresses", new { id = Model.Id }); } </div> </div>
حال ما باید کنترلر آدرس را تعیین کنیم زیرا مشاهده ی استاندارد در شاخص Action را بازمیگرداند. چیزی که ما به جای آن نیاز داریم مشاهده ی بخشی است. پس من تلاش میکنم که این قطعه کد را برای شاخص Action وارد کنم :
// GET: Addresses [ChildActionOnly] public async Task<ActionResult> Index(int id) { ViewBag.PersonID = id; var addresses = db.Addresses.Where(a => a.PersonID == id); return PartialView("_Index", await addresses.ToListAsync()); }
بعد از راه اندازی این خطا را دریافت کردم “HttpServerUtility.Exexute was blocked by waiting for the end of an asynchronous operation.” بعد از کمی تحقیق و جستجو در اینترنت متوجه شدم که عملیات های ناهمگام در در اکشن فرزندان در ورژن MVC مجاز نمیباشد. پس به عنوان یه تعمیر سریع ,کنترلر آدرس و فولدر آدرس ها در بخش مشاهده را حذف کنید . سپس دوباره کنترلر آدرس ها را طبقه بندی کنید اما تیک بخش ” Use async controller action ” را اینبار بردارید. شاخص ویرایش شده حال باید به صورت زیر باشد :
[ChildActionOnly] public ActionResult Index(int id) { ViewBag.PersonID = id; var addresses = db.Addresses.Where(a => a.PersonID == id); return PartialView("_Index", addresses.ToList()); }
سپس باید نام “Index.cshtml ” را به “_Index.cshtml” در مشاهده\آدرس ها تغییر دهید سپس از آن اجرا بگیرید. من دوراسیون شاخص اکشن را با خاصیت “فقط اکشن فرزندان” قرار دادم زیرا نمیخواهیم این متد مستقیما فراخوانی شود . پارامتر ID به ما اجازه میدهد تا آدرسی که برای شخص داده شده است را دریافت کنیم. ViewBag.PersonID برای خواندن ساخت آدرس در شاخص مفید خواهد بود.
برنامه اکنون اجرا میشود اما ما هنوز ویرایش در مکان آدرس ها را اجرا نکرده ایم. پس کلیک بر روی ساخت لینک ما را به مشاهده ی دیگری میبرد. شما میتوانید با حذف موقتی خاصیت “تنها اکشن فرزندان” و کلیک بر لینکش آن را امتحان کنید. برای جلوگیری از فرار از مشاهده ما از مودال های Bootstrap با کمی کمک از جاوا سکریپت استفاده میکنیم.
مودال های Bootstrap
من اسکریپت های زیر را به مودال هایbootstrap تبدیل کردم.
// modalform.js $(function () { $.ajaxSetup({ cache: false }); $("a[data-modal]").on("click", function (e) { // hide dropdown if any $(e.target).closest('.btn-group').children('.dropdown-toggle').dropdown('toggle'); $('#myModalContent').load(this.href, function () { $('#myModal').modal({ /*backdrop: 'static',*/ keyboard: true }, 'show'); bindForm(this); }); return false; }); }); function bindForm(dialog) { $('form', dialog).submit(function () { $.ajax({ url: this.action, type: this.method, data: $(this).serialize(), success: function (result) { if (result.success) { $('#myModal').modal('hide'); //Refresh location.reload(); } else { $('#myModalContent').html(result); bindForm(dialog); } } }); return false; }); }
تابع اسکریپت به سه چیز احتیاج دارد :
۱- در مشاهده این ترتیب از مکان نگه دارنده ها :
<!-- modal placeholder--> <div id='myModal' class='modal fade in'> <div class="modal-dialog"> <div class="modal-content"> <div id='myModalContent'></div> </div> </div> </div>
2- در مشاهده ای که میخواهیم این نوع علامت را در جایش قرار دهیم :
<div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">Add new Address</h4> </div> @using (Html.BeginForm()) { <div class="modal-body"> // Place Form editors here </div> </div> <div class="modal-footer"> <button class="btn" data-dismiss="modal">Cancel</button> <input class="btn btn-primary" type="submit" value="Add" /> </div> }
3- مشاهده ی حمایتی اکشن در مدل باید Json(new { success = true}); را به جای View(); بازگزداند.
یادداشت : لطفا به یاد داشته باشید که اسکریپت به خاصیت مدل داده در ساخت اکشن لینک احتیاج دارد.
یادداشت : خوب است که یک خاصیت از نوع دکمه به دکمه ی منفصل کردن داده – شما از مشکلات با مودال هایbootstrap زمانی که کاربر دکمه ی کلید بازگشت را میفشارد برای دریافت دیالوگ . در بعضی از مودال های مرورگر ها بدون این خاصیت فقط بسته میشوند .
برای کامل کردن bootstrap مودال های ۳.۱.۱ در MVC 5 به نمونه دقت کنید.در پروژه ما من یک modalform.js به فولدر سکریپت و bundles.Add(new ScriptBundle(“~/bundles/modalform”).Include(“~/Scripts/modalform.js”)); اضافه کردم.خط به AppStart\BundleConfig.cs.حال با ویرایش ” “_Index.schtml به لایه زیر میرسیم :
@using TestAjax.Helpers @model IEnumerable<TestAjax.Models.Address> <!-- modal placeholder--> <div id='myModal' class='modal fade in'> <div class="modal-dialog"> <div class="modal-content"> <div id='myModalContent'></div> </div> </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <strong>Address List</strong> </div> <table class="table table-hover"> <tr> <th> @Html.DisplayNameFor(model => model.City) </th> <th> @Html.DisplayNameFor(model => model.Street) </th> <th> @Html.DisplayNameFor(model => model.Phone) </th> <th>@Html.NoEncodeActionLink("<span class='glyphicon glyphicon-plus'></span>", "Add", "Create", "Addresses", routeValues: new { PersonId = ViewBag.PersonID }, htmlAttributes: new { data_modal = "", @class = "btn btn-primary pull-right" })</th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.City) </td> <td> @Html.DisplayFor(modelItem => item.Street) </td> <td> @Html.DisplayFor(modelItem => item.Phone) </td> <td> <div class="pull-right"> @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-pencil'></span>", "Edit", "Edit", "Addresses", routeValues: new { id = item.Id }, htmlAttributes: new { data_modal = "", @class = "btn btn-default" }) @Html.NoEncodeActionLink("<span class='glyphicon glyphicon-trash'></span>", "Delete", "Delete", "Addresses", routeValues: new { id = item.Id }, htmlAttributes: new { data_modal = "", @class = "btn btn-danger" }) </div> </td> </tr> } </table> </div>
افزودن حمایت کننده برای مودال ها در “Edit.cshtml” Pepale در بخش متن ها:
@section Scripts { @Scripts.Render("~/bundles/jqueryval") @Scripts.Render("~/bundles/modalform") }
برای کار کردن با مودال ها در کنترلر آدرس ها ما باید متد ” Create ” را تغییر دهیم.
public ActionResult Create(int PersonID) { Address address = new Address(); address.PersonID = PersonID; return PartialView("_Create", address); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "Id,City,Street,Phone,PersonID")] Address address) { if (ModelState.IsValid) { db.Addresses.Add(address); db.SaveChanges(); return Json(new { success = true }); } return PartialView("_Create"); }
و در نهایت آدرس ” _Creat.chtml ” تغییر نام یافته باید به فرم زیر تبدیل شود :
@model TestAjax.Models.Address <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">Add new Address</h4> </div> @using (Html.BeginForm()) { <div class="modal-body"> @Html.AntiForgeryToken() <div class="form-horizontal"> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.City, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.City, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.City, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Street, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Street, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Street, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Phone, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Phone, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Phone, "", new { @class = "text-danger" }) </div> </div> </div> </div> <div class="modal-footer"> <button class="btn" data-dismiss="modal">Cancel</button> <input class="btn btn-primary" type="submit" value="Add" /> </div> }
حال ما میتوانیم خط آدرس جدید را بدون فرار از از قسمت ” مشاهده ی ویرایش شخص ” بیافزاییم.
از الگوی مشابه برای ساخت بخش های _Edit و _Delete استفاده میشود .
علاوه بر این میتوانیم یک بخش بنام _List بسازیم تا که در جزئیات مشاهده ی شخص نمایش داده شود.
برای سایر بخش ها لطفا به دانلود های الحاقی مراجعه کنید که شامل سورس کامل برای برای پروژه میباشد.
نکته : برای به روز رسانی بسته های Nuget در الحاقی ها برنامه را بازسازی کنید. شما باید هردو گزینه ی ” اجازه به Nuget برای دانلود بسته های گمشده ” و ” به صورت اتوماتیک به دنبال بسته های گمشده گشتن در طول ساخت در ویژوال استودیو ” را در تنظیمات NuGet تیک بزنید.
ممنون ای کاش منبع رو هم ذکر می کردین.
http://www.codeproject.com/Articles/786085/ASP-NET-MVC-List-Editor-with-Bootstrap-Modals