پیاده سازی دیتا گرید با jQuery و ASP.NET MVC به همراه عملیات CRUD ، قسمت دوم از مقاله دیتا گرید با AngularJs که در این مقاله دیتا گرید با jQuery و AngularJs کامل میکنیم و عملیات CRUD را پیاد سازی می کنیم.
پیش نیازها:
ما در قسمت اول : به پیاده سازی دیتا گرید با AngularJs و .NET Core پرداختیم و در این قسمت دیتا گرید با jQuery آموزش قبل را تکمیل میکنیم و سعی میکنیم امکانات جدیدی اضافه کنیم و آموزش را جذاب تر کنیم.
ایده کار به این شکل میباشد که برای نمایش اطلاعات به صورت جدولی با قابلیتهای مذکور، لازم است یک اکشن Index برای نمایش اولیه ایجاد کنیم
و همچنین صفحه اول اطلاعات صفحه بندی شده و اکشن متدی به نام List برای پاسخ به درخواستهای صفحه بندی، مرتب سازی، تغییر تعداد آیتمها در هر صفحه و همچنین جستجو، داشته باشیم
که این اکشن متد List، بعد از واکشی اطلاعات مورد نظر از منبع داده، آنها را به همراه اطلاعاتی که در کوئری استرینگ درخواست جاری وجود دارد در قالب یک PartialView به کلاینت ارسال کند.
ایجاد مدلهای پایه
برای پیاده سازی متدهای GetPagedList در ApplicationServiceها از الگوی Request/Response استفاده میکنیم. برای این منظور واسط و کلاسهای زیر را خواهیم داشت:
واسط IPagedQueryModel
public interface IPagedQueryModel { int Page { get; set; } int PageSize { get; set; } /// ;summary /// Expression of Sorting. /// ;/summary /// ;example /// Examples: /// "Name_ASC" /// ;/example string SortExpression { get; set; } }
این واسط قراردادی میباشد برای نوع و نام پارامترهایی که توسط کلاینت به سرور ارسال میشود. پراپرتی SortExpression آن، نام و ترتیب مرتب سازی را مشخص میکند
که این یک موضوع مهم در آموزش ساخت دیتا گرید با jQuery است؛
برای این منظور FieldName_ASC و FieldName_DESC به ترتیب برای حالات مرتب سازی صعودی و نزولی براساس FieldName مقدار دهی خواهد شد.
برای جلوگیری از تکرار این خصوصیات در مدلهای کوئری مربوط به موجودیتها، میتوان کلاس پایهای به شکل زیر در نظر گرفت که پیاده ساز واسط بالا میباشد:
public class PagedQueryModel : IPagedQueryModel, IShouldNormalize { public int Page { get; set; } public int PageSize { get; set; } /// summary /// Expression of Sorting. /// /summary /// example /// Examples: /// "Name_ASC" /// /example public string SortExpression { get; set; } public virtual void Normalize() { if (Page 1) Page = 1; if (PageSize 1) PageSize = 10; if (SortExpression.IsEmpty()) SortExpression = "Id_DESC"; } }
مدل بالا علاوه بر پیاده سازی واسط IPagedQueryModel، پیاده ساز واسط IShouldNormalize نیز میباشد.
پیاده سازی واسط IShouldNormalize باعث خواهد شد که قبل از اجرای خود متد، این نوع پارامترها با استفاده از یک Interceptor شناسایی شده و متد Normalize آنها اجرا شود.
کلاس PagedQueryResult
public class PagedQueryResultTModel { public PagedQueryResult() { Items = new ListTModel(); } public IEnumerableTModel Items { get; set; } public long TotalCount { get; set; } }
دلیل وجود کلاس بالا در پیاده سازی دیتا گرید با AngularJs و .NET Core توضیح داده شده است:
عموما ساختار اطلاعات صفحه بندی شده، شامل تعداد کل آیتمهای تمام صفحات (خاصیت TotalItems) و تنها اطلاعات ردیفهای صفحهی جاری درخواستی (خاصیت Items) است
چون در اینجا این Items از هر نوعی میتواند باشد، بهتر است آن را جنریک تعریف کنیم.
کلاس PagedListModel در پیاده سازی دیتا گرید با jQuery
همانطور که در اول بحث توضیح داده شد، لازم است اطلاعاتی را که کلاینت از طریق کوئری استرینگ برای صفحه بندی و … ارسال کرده و نیز به PartialView ارسال کنیم.
این قسمت کار ایده اصلی این روش را در بر میگیرد؛ اگر نخواهیم اطلاعات کوئری استرینگ دریافتی از کلاینت را دوباره به PartialView ارسال کنیم،
مجبور خواهیم بود تمام کارهای مربوط به تشخیص آیکن مرتب سازی ستونهای جدول، ریست کردن المنتهای مربوط به صفحه بندی و مرتب سازی را در در زمان انجام جستجو و یکسری کارهایی از این قبیل را در سمت کلاینت مدیریت کنیم.
public class PagedListModelTModel { public IPagedQueryModel Query { get; set; } public PagedQueryResultTModelResult { get; set; } }
پراپرتی Query در برگیرنده پارامتر ورودی اکشن متد List میباشد که پراپرتیهای آن با مقادیر موجود در کوئری استرینگ درخواست جاری مقدار دهی شدهاند؛
البته بدون وجود کلاس بالا نیز به کمک ViewBag میشود این اطلاعات ترکیبی را به ویو ارسال کرد که پیشنهاد نمیشود.
متد GetPagedListAsync موجود در CrudApplicationService
public abstract class CrudApplicationServiceTEntity, TModel, TCreateModel, TEditModel, TDeleteModel, TPagedQueryModel, TDynamicQueryModel : ApplicationService, ICrudApplicationServiceTModel, TCreateModel, TEditModel, TDeleteModel, TPagedQueryModel, TDynamicQueryModel where TEntity : Entity, new() where TCreateModel : class where TEditModel : class, IModel where TModel : class, IModel where TDeleteModel : class, IModel where TPagedQueryModel : PagedQueryModel, new() where TDynamicQueryModel : DynamicQueryModel { #region Properties protected IQueryableTEntity UnTrackedEntitySet = EntitySet.AsNoTracking(); public IUnitOfWork UnitOfWork { get; set; } public IMapper Mapper { get; set; } protected IDbSetTEntity EntitySet = UnitOfWork.SetTEntity(); #endregion #region ICrudApplicationService Members #region Methods public virtual async TaskPagedQueryResultTModel GetPagedListAsync(TPagedQueryModel model) { Guard.ArgumentNotNull(model, nameof(model)); var query = ApplyFiltering(model); var totalCount = await query.LongCountAsync().ConfigureAwait(false); var result = query.ProjectToTModel(Mapper.ConfigurationProvider); result = result.ApplySorting(model); result = result.ApplyPaging(model); return new PagedQueryResultTModel { Items = await result.ToListAsync().ConfigureAwait(false), TotalCount = totalCount }; } #endregion #endregion #region Protected Methods /// summary /// Apply Filtering To GetPagedList and GetPagedListAsync /// /summary /// param name="model"/param /// returns/returns protected virtual IQueryableTEntity ApplyFiltering(TPagedQueryModel model) { Guard.ArgumentNotNull(model, nameof(model)); return UnTrackedEntitySet; } #endregion }
در بدنه این متد، ابتدا عملیات جستجو توسط متد ApplyFiltering انجام میشود. این متد به صورت پیش فرض هیچ شرطی را بر روی کوئری ارسالی به منبع داده اعمال نمیکند؛
مگر اینکه توسط زیر کلاسها بازنویسی شود و فیلترهای مورد نیاز اعمال شوند. سپس تعداد کل آیتمهای فیلتر شده محاسبه شده و بعد از عملیات Projection، مرتب سازی و صفحه بندی انجام میگیرد.
برای مباحث مرتب سازی و صفحه بندی از دو متد زیر کمک گرفته شدهاست:
public static class QueryableExtensions { public static IQueryableTModelApplySortingTModel(this IQueryableTModelquery, IPagedQueryModel request) { Guard.ArgumentNotNull(request, nameof(request)); Guard.ArgumentNotNull(query, nameof(query)); return query.OrderBy(request.SortExpression.Replace('_', ' ')); } public static IQueryableTModelApplyPagingTModel(this IQueryableTModelquery, IPagedQueryModel request) { Guard.ArgumentNotNull(request, nameof(request)); Guard.ArgumentNotNull(query, nameof(query)); return request != null ? query.Page((request.Page - 1) * request.PageSize, request.PageSize) : query; } }
به منظور مرتب سازی از کتابخانه System.Liq.Dynamic کمک گرفته شدهاست.
نکته: مشخص است که این روش، وابستگی به وجود متد GetPagedListAsync ندارد و صرفا برای تشریح ارتباط مطالبی که قبلا منتشر شده بود، مطرح شد.
پیاده سازی اکشن متدهای Index و List
public partial class RolesController : BaseController { #region Fields private readonly IRoleService _service; private readonly ILookupService _lookupService; #endregion #region Constractor public RolesController(IRoleService service, ILookupService lookupService) { Guard.ArgumentNotNull(service, nameof(service)); Guard.ArgumentNotNull(lookupService, nameof(lookupService)); _service = service; _lookupService = lookupService; } #endregion #region Index / List [HttpGet] public virtual async TaskActionResult Index() { var query = new RolePagedQueryModel(); var result = await _service.GetPagedListAsync(query).ConfigureAwait(false); var pagedList = new PagedListModelRoleModel { Query = query, Result = result }; var model = new RoleIndexViewModel { PagedListModel = pagedList, Permissions = _lookupService.GetPermissions() }; return View(model); } [HttpGet, AjaxOnly, NoOutputCache] public virtual async TaskActionResult List(RolePagedQueryModel query) { var result = await _service.GetPagedListAsync(query).ConfigureAwait(false); var model = new PagedListModelRoleModel { Query = query, Result = result }; return PartialView(MVC.Administration.Roles.Views._List, model); } #endregion }
به عنوان مثال برای ساخت دیتا گرید با jQuery در بالا Controller مربوط به گروههای کاربری را مشاهده میکنید. به دلیل اینکه علاوه بر مباحث صفحه بندی و مرتب سازی، امکان جستجو بر اساس نام و دسترسیهای گروه کاربری را نیز نیاز داریم،
لازم است مدل زیر را ایجاد کنیم:
public class RolePagedQueryModel : PagedQueryModel { public string Name { get; set; } public string Permission { get; set; } }
در این مورد خاص لازم است لیست دسترسیهای موجود درسیستم به صورت لیستی برای انتخاب در فرم جستجو مهیا باشد. فرم جستجو در ویو مربوط به اکشن Index قرار میگیرد و قرار نیست به همراه پارشال ویو List_ در هر درخواستی از سرور دریافت شود
. لذا لازم است مدلی برای ویو Index در نظر بگیریم که به شکل زیر میباشد:
public class RoleIndexViewModel { public RoleIndexViewModel() { Permissions = new ListLookupItem(); } public IReadOnlyListLookupItemPermissions { get; set; } public PagedListModelRoleModelPagedListModel { get; set; } }
پراپرتی PagedListModel در برگیرنده اطلاعات مربوط به نمایش اولیه جدول اطلاعات میباشد و پراپرتی Permissions لیست دسترسیهای موجود درسیستم را به ویو منتقل خواهد کرد.
اگر ویو ایندکس دیتا گرید با jQuery شما به داده اضافه ای نیاز ندارد، از ایجاد مدل بالا صرف نظر کنید.
ویو Index.cshtml
@model RoleIndexViewModel @{ ViewBag.Title = L("Administration.Views.Role.Index.Title"); ViewBag.ActiveMenu = AdministrationMenuNames.RoleManagement; } div class="row" div class="col-md-12" div id="filterPanel" class="panel-collapse collapse" role="tabpanel" aria-labelledby="filterPanel" div class="panel panel-default margin-bottom-5" div class="panel-body" @using (Ajax.BeginForm(MVC.Administration.Roles.List(), new AjaxOptions { UpdateTargetId = "RolesList", HttpMethod = "GET" }, new { id = "filterForm", data_submit_on_reset = "true" })) { div class="row" div class="col-md-3" input type="text" name="Name" class="form-control" value="" placeholder="@L("Administration.Role.Fields.Name")" / /div div class="col-md-3" @Html.DropDownList("Permission", Model.Permissions.ToSelectListItems(), L("Administration.Views.Role.FilterBy.Permission"),new {@class="form-control"}) /div div class="col-md-3" button type="submit" role="button" class="btn btn-info" @L("Commands.Filter") /button button type="reset" role="button" class="btn btn-default" i class="fa fa-close"/i @L("Commands.Reset") /button /div /div } /div /div /div /div /div div class="row" div class="col-md-12" id="RolesList" @{Html.RenderPartial(MVC.Administration.Roles.Views._List, Model.PagedListModel);} /div /div
فرم جستجو باید دارای ویژگی data_submit_on_reset با مقدار “true” باشد. به منظور پاکسازی فرم جستجو و ارسال درخواست جستجو با فرمی خالی از داده، برای بازگشت به حالت اولیه از تکه کد زیر استفاده خواهد شد:
$(document).on("reset", "form[data-submit-on-reset]", function () { var form = this; setTimeout(function () { $(form).submit(); }); });
در ادامه پارشال ویو List_ با داده ارسالی به ویو Index، رندر شده و کار نمایش اولیه اطلاعات به صورت جدولی به اتمام میرسد.
پارشال ویو List.cshtml
@model PagedListModelRoleModel @{ Layout = null; var rowNumber = (Model.Query.Page - 1) * Model.Query.PageSize + 1; var refreshUrl = Url.Action(MVC.Administration.Roles.List().AddRouteValues(new RouteValueDictionary(Model.Query.ToDictionary()))); } div class="panel panel-default margin-bottom-5" table class="table table-bordered table-hover" id="RolesListTable" data-ajax-refresh-url="@refreshUrl" data-ajax-refresh-update="#RolesList" thead tr th style="width: 5%;" # /th th class="col-md-3 sortable" @Html.SortableColumn("Name", L("Administration.Role.Fields.Name"), Model.Query, "RolesList", routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary))) /th th class="col-md-3 sortable" @Html.SortableColumn("DisplayName", L("Administration.Role.Fields.DisplayName"), Model.Query, "RolesList", routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary))) /th th class="col-md-3 sortable" @Html.SortableColumn("IsDefault", L("Administration.Role.Fields.IsDefault"), Model.Query, "RolesList", routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary))) /th th style="width: 5%;"/th /tr /thead tbody @foreach (var role in Model.Result.Items) { tr td@(rowNumber++.ToPersianNumbers())/td td@role.Name/td td@role.DisplayName/td td class="text-center"@Html.DisplayFor(a = role.IsDefault)/td td class="text-center operations" div class="btn-group" span class="fa fa-ellipsis-h dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"/span ul class="dropdown-menu dropdown-menu-left" li a href="#" role="button" data-ajax="true" data-ajax-method="GET" data-ajax-update="#main-modal div.modal-content" data-ajax-url="@Url.Action(MVC.Administration.Roles.Edit(role.Id))" data-toggle="modal" data-target="#main-modal" i class="fa fa-pencil"/i @L("Commands.Edit") /a /li li a href="#" role="button" id="delete-@role.Id" data-delete-url="@Url.Action(MVC.Administration.Roles.Delete())" data-delete-model='{"Id":"@role.Id","RowVersion":"@Convert.ToBase64String(role.RowVersion)"}' i class="fa fa-trash"/i @L("Commands.Delete") /a /li /ul /div /td /tr } /tbody /table /div div class="row" div class="col-md-8" @Html.Pager(Model, "RolesList", routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary))) @Html.PageSize("RolesList", Model.Query, routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), new { @class = "margin-right-5" }, "filterForm") @Html.Refresh("RolesList", Model.Query, routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), L("Commands.Refresh")) /div /div
به ترتیب فایل بالا را بررسی میکنیم:
var refreshUrl = Url.Action(MVC.Administration.Roles.List().AddRouteValues(new RouteValueDictionary(Model.Query.ToDictionary())));
refreshUrl برای ارسال درخواست به اکشن متد List در نظر گرفته شدهاست که در کوئری استرینگ مربوط به خود، اطلاعاتی (مرتب سازی، شماره صفحه، اطلاعات جستجو و همچنین تعداد آیتمهای موجود در هر صفحه) را دارد که حالت فعلی گرید را میتوانیم دوباره از سرور درخواست کنیم.
var refreshUrl = Url.Action(MVC.Administration.Roles.List().AddRouteValues(new RouteValueDictionary(Model.Query.ToDictionary())));
دو ویژگی data-ajax-refresh-url و data-ajax-refresh-update برای جدولی که لازم است عملیات CRUD را پشتیبانی کند، لازم میباشد. در قسمت دوم با استفاده از این دو ویژگی در هنگام عملیات ثبت، ویرایش و حذف خواهیم پرداخت.
th class="col-md-3 sortable" @Html.SortableColumn("Name", L("Administration.Role.Fields.Name"), Model.Query, "RolesList", routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary))) /th
ستونی که امکان مرتب سازی را دارد باید th آن، کلاس sortable را داشته باشد. همچنین باید از Helper ی که پیاده سازی آن را در ادامه خواهیم دید، استفاده کنیم.
این Helper ، نام فیلد، عنوان ستون، مدل Query و همچین یک urlFactory را در قالب یک Func<RouteValueDictionary,string> دریافت میکند.
پیاده سازی Helper SortableColumn
public static MvcHtmlString SortableColumn(this HtmlHelper html, string columnName, string columnDisplayName, IPagedQueryModel queryModel, string updateTargetId, FuncRouteValueDictionary, stringurlFactory) { var dictionary = queryModel.ToDictionary(); var routeValueDictionary = new RouteValueDictionary(dictionary) { ["SortExpression"] = !queryModel.SortExpression.StartsWith(columnName) ? $"{columnName}_DESC" : queryModel.SortExpression.EndsWith("DESC") ? $"{columnName}_ASC" : queryModel.SortExpression.EndsWith("ASC") ? string.Empty : $"{columnName}_DESC" }; var url = urlFactory(routeValueDictionary); var aTag = new TagBuilder("a"); aTag.Attributes.Add("href", "#"); aTag.Attributes.Add("data-ajax", "true"); aTag.Attributes.Add("data-ajax-method", "GET"); aTag.Attributes.Add("data-ajax-update", updateTargetId.StartsWith("#") ? updateTargetId : $"#{updateTargetId}"); aTag.Attributes.Add("data-ajax-url", url); aTag.InnerHtml = columnDisplayName; var iconCssClass = !queryModel.SortExpression.StartsWith(columnName) ? "fa-sort" : queryModel.SortExpression.EndsWith("DESC") ? "fa-sort-down" : "fa-sort-up"; var iTag = new TagBuilder("i"); iTag.AddCssClass($"fa {iconCssClass}"); return new MvcHtmlString($"{aTag}\n{iTag}"); }
ابتدا مدل Query با متد الحاقی زیر تبدیل به دیکشنری میشود. این کار از این جهت مهم است که پراپرتیهای لیست موجود در مدل Query، لازم است به فرم خاصی به سرور ارسال شوند که در تکه کد زیر مشخص میباشد.
public static IDictionarystring, object ToDictionary(this object source) { return source.ToDictionaryobject(); } public static IDictionarystring, T ToDictionaryT(this object source) { if (source == null) throw new ArgumentNullException(nameof(source)); var dictionary = new Dictionarystring, T(); foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source)) { AddPropertyToDictionary(property, source, dictionary); } return dictionary; } private static void AddPropertyToDictionaryT(PropertyDescriptor property, object source, IDictionarystring, T dictionary) { var value = property.GetValue(source); var items = value as IEnumerable; if (items != null !(items is string)) { var i = 0; foreach (var item in items) { dictionary.Add($"{property.Name}[{i++}]", (T)item); } } else if (value is T) { dictionary.Add(property.Name, (T)value); } }
در متد بالا، از TypeDescriptor که یکی دیگر از ابزارهای دسترسی به متا دیتای انوع دادهای است، استفاده شده و خروجی نهایی آن یک دیکشنری با کلیدهایی با اسامی پراپرتیهای وهله ورودی میباشد.
در ادامه پیاده سازی Helper SortableColumn، از دیکشنری حاصل، یک وهله از RouteValueDictionary ساخته میشود.
در زمان رندر شدن PartialView لازم است مشخص شود که برای دفعه بعدی که بر روی این ستون کلیک میشود، باید چه مقداری با پارامتر SortExpression موجود در کوئری استرینگ ارسال شود.
از این جهت برای پشتیبانی ستون، از حالتهای مرتب سازی صعودی، نزولی و برگشت به حالت اولیه بدون مرتب سازی، کد زیر را خواهیم داشت:
var routeValueDictionary = new RouteValueDictionary(dictionary) { ["SortExpression"] = !queryModel.SortExpression.StartsWith(columnName) ? $"{columnName}_DESC" : queryModel.SortExpression.EndsWith("DESC") ? $"{columnName}_ASC" : queryModel.SortExpression.EndsWith("ASC") ? string.Empty : $"{columnName}_DESC" };
در ادامه urlFactory با routeValueDictionary حاصل، Invoke میشود تا url نهایی برای مرتب سازیهای بعدی از طریق یک لینک تزئین شده با data اتریبیوتهای Unobtrusive Ajax در th مربوطه قرار دهیم.
برای مباحث صفحه بندی، بارگزاری مجدد و تغییر تعداد آیتم ها در هر صفحه، از سه هلپر زیر کمک خواهیم گرفت:
div class="row" div class="col-md-8" @Html.Pager(Model, "RolesList", routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary))) @Html.PageSize("RolesList", Model.Query, routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), new { @class = "margin-right-5" }, "filterForm") @Html.Refresh("RolesList", Model.Query, routeValueDictionary = Url.Action(MVC.Administration.Roles.List().AddRouteValues(routeValueDictionary)), L("Commands.Refresh")) /div /div
پیاده سازی هلپر Pager
public static MvcHtmlString PagerTModel(this HtmlHelper html, PagedListModelmodel, string updateTargetId, FuncRouteValueDictionary, string urlFactory) { return html.PagedListPager( new StaticPagedListTModel(model.Result.Items, model.Query.Page, model.Query.PageSize, (int)model.Result.TotalCount), page = { var dictionary = model.Query.ToDictionary(); var routeValueDictionary = new RouteValueDictionary(dictionary) { ["Page"] = page }; return urlFactory(routeValueDictionary); }, PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing( new PagedListRenderOptions { DisplayLinkToFirstPage = PagedListDisplayMode.Always, DisplayLinkToLastPage = PagedListDisplayMode.Always, DisplayLinkToPreviousPage = PagedListDisplayMode.Always, DisplayLinkToNextPage = PagedListDisplayMode.Always, MaximumPageNumbersToDisplay = 6, DisplayItemSliceAndTotal = true, DisplayEllipsesWhenNotShowingAllPageNumbers = true, ItemSliceAndTotalFormat = $"تعداد کل: {model.Result.TotalCount.ToPersianNumbers()}", FunctionToDisplayEachPageNumber = page = page.ToPersianNumbers(), }, new AjaxOptions { AllowCache = false, HttpMethod = "GET", InsertionMode = InsertionMode.Replace, UpdateTargetId = updateTargetId })); }
پیاده سازی هلپر PageSize
public static MvcHtmlString PageSize(this HtmlHelper html, string updateTargetId, IPagedQueryModel queryModel, FuncRouteValueDictionary, string urlFactory, object htmlAttributes = null, string filterFormId = null, params int[] numbers) { if (numbers.Length == 0) numbers = new[] { 10, 20, 30, 50, 100 }; var dictionary = queryModel.ToDictionary(); var routeValueDictionary = new RouteValueDictionary(dictionary) { [nameof(IPagedQueryModel.Page)] = 1 }; routeValueDictionary.Remove(nameof(IPagedQueryModel.PageSize)); var url = urlFactory(routeValueDictionary); var formTag = new TagBuilder("form"); formTag.Attributes.Add("action", url); formTag.Attributes.Add("method", "GET"); formTag.Attributes.Add("data-ajax", "true"); formTag.Attributes.Add("data-ajax-method", "GET"); formTag.Attributes.Add("data-ajax-update", updateTargetId.StartsWith("#") ? updateTargetId : $"#{updateTargetId}"); formTag.Attributes.Add("data-ajax-url", url); if (htmlAttributes != null) formTag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); formTag.AddCssClass("form-inline inline"); var items = numbers.Select(number = new SelectListItem { Value = number.ToString(), Text = number.ToString().ToPersianNumbers(), Selected = queryModel.PageSize == number }); formTag.InnerHtml = html.DropDownList(nameof(IPagedQueryModel.PageSize), items, new { @class = "form-control page-size", onchange = "$(this.form).submit();" }).ToString(); if (filterFormId.IsEmpty()) return new MvcHtmlString($"{formTag}"); // ReSharper disable once MustUseReturnValue var scriptBlock = $"script type=\"text/javascript\" if(window.jQuery){{$('form#{filterFormId}').find('input[name=\"{nameof(IPagedQueryModel.PageSize)}\"]').remove();\n $('form#{filterFormId}').append(\"input type='hidden' name='{nameof(IPagedQueryModel.PageSize)}' value='{queryModel.PageSize}'/\")}}/script return new MvcHtmlString($"{formTag}\n{scriptBlock}"); }
ایده کار به این صورت است که یک المنت select، درون یک المنت form قرار میگیرد و در زمان change آن، فرم مربوطه submit میشود.
formTag.InnerHtml = html.DropDownList(nameof(IPagedQueryModel.PageSize), items, new { @class = "form-control page-size", onchange = "$(this.form).submit();" }).ToString();
در زمان تغییر تعداد نمایشی آیتم ها در هر صفحه، لازم است حالت فعلی گرید حفظ شود و صرفا پارامتر Page ریست شود.
نکته مهم: در این طراحی اگر فرم جستجویی دارید، در زمان جستجو هیچ یک از پارامترهای مربوط به صفحه بندی و مرتب سازی به سرور ارسال نخواهند شد (در واقع ریست میشوند)
و کافیست یک درخواست GET معمولی با ارسال محتویات فرم به سرور صورت گیرد؛ ولی لازم است PageSize تنظیم شده، در زمان اعمال فیلتر نیز به سرور ارسال شود.
از این جهت اسکریپتی برای ایجاد یک input مخفی در فرم جستجو نیز هنگام رندر شدن Partial View در صفحه تزریق میشود.
var scriptBlock = $"script type=\"text/javascript\" if(window.jQuery){{$('form#{filterFormId}').find('input[name=\"{nameof(IPagedQueryModel.PageSize)}\"]').remove();\n $('form#{filterFormId}').append(\"input type='hidden' name='{nameof(IPagedQueryModel.PageSize)}' value='{queryModel.PageSize}'/\")}}/script";
پیاده سازی هلپر Refresh
public static MvcHtmlString Refresh(this HtmlHelper html, string updateTargetId, IPagedQueryModel queryModel, FuncRouteValueDictionary, stringurlFactory, string label = null) { var dictionary = queryModel.ToDictionary(); var routeValueDictionary = new RouteValueDictionary(dictionary); var url = urlFactory(routeValueDictionary); var aTag = new TagBuilder("a"); aTag.Attributes.Add("href", "#"); aTag.Attributes.Add("role", "button"); aTag.Attributes.Add("data-ajax", "true"); aTag.Attributes.Add("data-ajax-method", "GET"); aTag.Attributes.Add("data-ajax-update", updateTargetId.StartsWith("#") ? updateTargetId : $"#{updateTargetId}"); aTag.Attributes.Add("data-ajax-url", url); aTag.AddCssClass("btn btn-default"); var iTag = new TagBuilder("i"); iTag.AddCssClass("fa fa-refresh"); aTag.InnerHtml = $"{iTag} {label}"; return new MvcHtmlString(aTag.ToString()); }
متد بالا نیز به مانند refreshUrl که پیشتر مطرح شد، برای بارگزاری مجدد حالت فعلی گرید استفاده میشود و از این جهت است که مقادیر مربوط به کلیدهای routeValueDictionary را تغییر ندادهایم.
ما در این آموزش به بررسی ساخت یک دیتا گرید با jQuery و عملیات های اضافه ویرایش حذف پرداختیم و مقاله قبلی با موضوع دیتاگرید با AngularJS را تکمیل کردیم.
هیچ دیدگاهی نوشته نشده است.