سفارشی سازی عناصر صفحات پویای افزودن و ویرایش رکوردهای jqGrid در ASP.NET MVC
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هفت دقیقه

پیشنیاز این بحث مطالعه‌ی مطالب «صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC» و «فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC» است و در اینجا جهت کوتاه شدن بحث، صرفا به تغییرات مورد نیاز جهت اعمال بر روی مثال‌ها اکتفاء خواهد شد.


صورت مساله

    public class Product
    {
        public int Id { set; get; }
        public DateTime AddDate { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
    }
در اینجا تعریف محصول، شامل خاصیت‌های تاریخ ثبت، نام و قیمت آن است.
می‌خواهیم زمانیکه فرم‌های پویای ویرایش یا افزودن رکوردها ظاهر شدند، در حین تکمیل نام، یک auto complete ظاهر شود:


در حین ورود تاریخ، یک date picker شمسی جهت سهولت ورود اطلاعات نمایش داده شود:


همچنین در قسمت ورود مبلغ و قیمت، به صورت خودکار حرف سه رقم جدا کننده هزارها، نمایش داده شوند تا کاربران در حین ورود مبالغ بالا دچار اشتباه نشوند.



پیشنیازها

- برای نمایش auto complete از همان امکانات توکار jQuery UI که به همراه jqGrid عرضه می‌شوند، استفاده خواهیم کرد.
- برای نمایش date picker شمسی از مطلب «PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند» کمک خواهیم گرفت.
- جهت اعمال خودکار حرف سه رقم جدا کننده هزارها از افزونه‌ی Price Format جی‌کوئری استفاده می‌کنیم.

تعریف و الحاق این پیشنیازها، فایل layout برنامه را به شکل زیر تغییر خواهد داد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>

    <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
    <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" />
    <link href="~/Content/PersianDatePicker.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/Scripts/jquery-1.7.2.min.js"></script>
    <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
    <script src="~/Scripts/i18n/grid.locale-fa.js"></script>
    <script src="~/Scripts/jquery.jqGrid.min.js"></script>
    <script src="~/Scripts/PersianDatePicker.js"></script>
    <script src="~/Scripts/jquery.price_format.2.0.js"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>


تغییرات مورد نیاز سمت کلاینت، جهت اعمال افزونه‌های جی‌کوئری و سفارشی سازی عناصر دریافت اطلاعات

الف) نمایش auto complete در حین ورود نام محصولات
                colModel: [
                    {
                        name: 'Name', index: 'Name', align: 'right', width: 100,
                        editable: true, edittype: 'text',
                        editoptions: {
                            maxlength: 40,
                            dataInit: function (elem) {
                                // http://jqueryui.com/autocomplete/
                                $(elem).autocomplete({
                                    source: '@Url.Action("GetProductNames","Home")',
                                    minLength: 2,
                                    select: function (event, ui) {
                                        $(elem).val(ui.item.value);
                                        $(elem).trigger('change');
                                    }
                                });
                            }
                        },
                        editrules: {
                            required: true
                        }
                    }           
     ],
برای اعمال هر نوع افزونه‌ی جی‌کوئری به عناصر فرم‌های خودکار ورود اطلاعات در jqGrid، تنها کافی است که رویداد dataInit یک ستون را بازنویسی کنیم. در اینجا توسط elem، المان جاری را در اختیار خواهیم داشت. سپس از این المان جهت اعمال افزونه‌ای دلخواه استفاده می‌کنیم. برای مثال در اینجا از متد autocomplete استفاده شده‌است که جزئی از jQuery UI استاندارد است.
برای پردازش سمت سرور آن و مقدار دهی url آن، یک چنین اکشن متدی را می‌توان تدارک دید:
        public ActionResult GetProductNames(string term)
        {
            var list = ProductDataSource.LatestProducts
                .Where(x => x.Name.StartsWith(term))
                .Select(x => x.Name)
                .Take(10)
                .ToArray();
            return Json(list, JsonRequestBehavior.AllowGet);
        }
مقدار term، عبارتی است که کاربر وارد کرده است. توسط متد StartsWith، کلیه نام‌هایی را که با این عبارت شروع می‌شوند (البته 10 مورد از آن‌ها را) بازگشت می‌دهیم.

ب) نمایش date picker شمسی در حین ورود تاریخ
                colModel: [
                    {
                        name: 'AddDate', index: 'AddDate', align: 'center', width: 100,
                        editable: true, edittype: 'text',
                        editoptions: {
                            maxlength: 10,
                            // https://www.dntips.ir/post/1382
                            onclick: "PersianDatePicker.Show(this,'@today');"
                        },
                        editrules: {
                            required: true
                        }
                    }
                ],
Date picker مورد استفاده، وابستگی خاصی به jQuery ندارد. مطابق مستندات آن باید در رویدادگردان onclick، این تقویم شمسی را فعال کرد. بنابراین در قسمت onclick دقیقا این مورد را اعمال می‌کنیم.

 @{
ViewBag.Title = "Index";
var today = DateTime.Now.ToPersianDate();
}
مقدار today آن در ابتدای View به نحو فوق تعریف شده‌است. کدهای کامل متد کمکی ToPersianDate در پروژه‌ی پیوست موجود است.

ج) اعمال حروف سه رقم جدا کننده هزارها در حین ورود قیمت
                colModel: [
                    {
                        name: 'Price', index: 'Price', align: 'center', width: 100,
                        formatter: 'currency',
                        formatoptions:
                        {
                            decimalSeparator: '.',
                            thousandsSeparator: ',',
                            decimalPlaces: 2,
                            prefix: '$'
                        },
                        editable: true, edittype: 'text',
                        editoptions: {
                            dir: 'ltr',
                            dataInit: function (elem) {
                                // http://jquerypriceformat.com/
                                $(elem).priceFormat({
                                    prefix: '',
                                    thousandsSeparator: ',',
                                    clearPrefix: true,
                                    centsSeparator: '',
                                    centsLimit: 0
                                });
                            }
                        },
                        editrules: {
                            required: true,
                            minValue: 0
                        }
                    }
                ],
افزونه‌ی price format نیز یک افزونه‌ی جی‌کوئری است. بنابراین دقیقا مانند حالت auto complete آن‌را در dataInit فعال سازی می‌کنیم و همچنین یک سری تنظیم ابتدایی مانند مشخص سازی  thousandsSeparator آن‌را مقدار دهی خواهیم کرد.


یک نکته

همین تعاریف را دقیقا به فرم‌های جستجو نیز می‌توان اعمال کرد. در اینجا برای حالات ویرایش و افزودن رکوردها، editoptions مقدار دهی شده‌است؛ در مورد فرم‌های جستجو باید searchoptions و برای مثال dataInit آن‌را مقدار دهی کرد.



مشکل مهم!

با تنظیمات فوق، قسمت UI بدون مشکل کار می‌کند. اما اگر در سمت سرور، مقادیر دریافتی را بررسی کنیم، نه تاریخ و نه قیمت، قابل دریافت نیستند. زیرا تاریخ ارسالی به سرور شمسی است و مدل برنامه DateTime میلادی می‌باشد. همچنین به دلیل وجود حروف سه رقم جدا کننده هزارها، عبارت دریافتی قابل تبدیل به عدد نیستند و مقدار دریافتی صفر خواهد بود.
برای رفع این مشکلات، نیاز به تغییر model binder توکار ASP.NET MVC است. برای تاریخ‌ها از کلاس PersianDateModelBinder می‌توان استفاده کرد. برای اعداد decimal از کلاس ذیل:
using System;
using System.Globalization;
using System.Threading;
using System.Web.Mvc;

namespace jqGrid05.CustomModelBinders
{
    /// <summary>
    /// How to register it in the Application_Start method of Global.asax.cs
    /// ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder());
    /// </summary>
    public class DecimalBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType == typeof(decimal) || bindingContext.ModelType == typeof(decimal?))
            {
                return bindDecimal(bindingContext);
            }
            return base.BindModel(controllerContext, bindingContext);
        }

        private static object bindDecimal(ModelBindingContext bindingContext)
        {
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (valueProviderResult == null)
                return null;
            
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
            decimal value;
            var valueAsString = valueProviderResult.AttemptedValue == null ?
                                        null : valueProviderResult.AttemptedValue.Trim();
            if (string.IsNullOrEmpty(valueAsString))
                return null;
            
            if (!decimal.TryParse(valueAsString, NumberStyles.Any, Thread.CurrentThread.CurrentCulture, out value))
            {
                const string error ="عدد وارد شده معتبر نیست";
                var ex = new InvalidOperationException(error, new Exception(error, new FormatException(error)));
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
            return value;
        }
    }
}
در اینجا عبارت ارسالی به سرور به صورت یک رشته دریافت شده و سپس تبدیل به یک عدد decaimal می‌شود. در آخر به سیستم model binding بازگشت داده خواهد شد. به این ترتیب دیگر مشکلی با پردازش حروف سه رقم جدا کننده هزارها نخواهد بود.

برای ثبت و معرفی این کلاس‌ها باید به نحو ذیل در فایل global.asax.cs برنامه عمل کرد:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using jqGrid05.CustomModelBinders;

namespace jqGrid05
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder());
            ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder());
        }
    }
}


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid05.zip
 
  • #
    ‫۱۰ سال و ۳ ماه قبل، چهارشنبه ۱۸ تیر ۱۳۹۳، ساعت ۱۶:۱۶
    با تشکر اگر نیاز باشه که یک button به صورت custom  مثلا برای Redirect کردن به صفحه دیگه اضافه کنیم باید به چه صورت عمل کنیم
    • #
      ‫۱۰ سال و ۳ ماه قبل، چهارشنبه ۱۸ تیر ۱۳۹۳، ساعت ۱۶:۲۴
      - از formatterها برای سفارشی سازی و تغییر اطلاعات نهایی نمایش داده شده در یک سلول استفاده کنید.
      - از callback ایی به نام beforeShowForm برای اضافه کردن عناصر سفارشی به فرم‌های ویرایش و افزودن رکوردها می‌شود استفاده کرد:
      $.extend($.jgrid.edit, {
          bSubmit: "Save and Close",
          bCancel: "Cancel",
          width: 370,
          recreateForm: true,
          beforeShowForm: function () {
              $('<a href="#">Save and New<span class="ui-icon ui-icon-disk"></span></a>')
                  .click(function() {
                      alert("click!");
                  }).addClass("fm-button ui-state-default ui-corner-all fm-button-icon-left")
                    .prependTo("#Act_Buttons>td.EditButton");
          }
      });
  • #
    ‫۱۰ سال و ۳ ماه قبل، چهارشنبه ۱۸ تیر ۱۳۹۳، ساعت ۱۹:۰۶
    باسلام؛ اگر بخواهیم در مودال مورد نظر، 10 قلم اطلاعاتی را بگیریم ولی در گرید 5 تای آن را نشان بدهیم چه باید کرد؟ ممنون 
    • #
      ‫۱۰ سال و ۳ ماه قبل، چهارشنبه ۱۸ تیر ۱۳۹۳، ساعت ۱۹:۴۱
      از تنظیمات hidden ستون‌ها استفاده کنید:
      colModel:[
          {
             name:'providerUserId',
             index:'providerUserId', 
             width:100,
             editable:true, 
             editrules:{
                 required:true, 
                 edithidden:true
             }, 
             hidden:true,
             editoptions:{ 
                dataInit: function(element) { 
                   $(element).attr("readonly", "readonly"); 
                } 
            }
      },
      //...
      ]
      در اینجا فیلد فوق در گرید نمایش داده نمی‌شود (hidden:true)، در حین ویرایش نمایان خواهد شد (edithidden:true)، همچنین در قسمت dataInit (البته در صورت نیاز)، به صورت readonly تنظیم شده‌است.
  • #
    ‫۱۰ سال و ۳ ماه قبل، پنجشنبه ۱۹ تیر ۱۳۹۳، ساعت ۱۷:۳۲
    با سلام و تشکر فراوان
    لطفا در مورد امکان افزودن و البته ویرایش Inline که بسیار مهم هستند توضیح بدین. همچنین اگر امکان Export هم داشته باشه که افزونه‌ی خیلی خوب است
    بنده در حال حاضر از افزونه ی jTable استفاده کنم. هر چند مجبور شدم فایل‌های js اون رو خیلی تغییر بدم. ولی افزونه‌ی خوبی است.
      • #
        ‫۱۰ سال و ۲ ماه قبل، دوشنبه ۲۳ تیر ۱۳۹۳، ساعت ۱۸:۵۲
        با تشکر
        منظور امکان افزودن بود. یعنی وقتی دکمه افزودن رو میزنه یک سطر جدید واشه توگرید همونجا بنویسه مشکل اصلی من با jTable هم همینه و این یک مورد خیلی مهمه برا کاربرا که نخواد برا افزودن فرم مودال واشه براش.
        مثلا من صفحه‌ی MVC دارم که کاربر محصولات و خدماتشو وارد می‌کنه و مدیریت. موردی که هست ممکن کالاهای هر صاحب شغلی حداقل حدود 1000 و به بالا است. و برای ورود اولی خیلی اذیت میشه و ممکن نیست. اگر این افروزنه هم داشته باشه و معرفی کنید ممنون میشم
        در هر صورت بازم ازتون تشکر می‌کنم
  • #
    ‫۱۰ سال و ۳ ماه قبل، جمعه ۲۰ تیر ۱۳۹۳، ساعت ۲۳:۲۵
    من از گرید شما استفاده کردم ولی متأسفانه وقتی تعداد سطرها زیاد باشه و صفحه اسکرول پیدا کنه، برای حذف و یا تغییرات عناصر انتهایی، فرم تغییرات در قسمت بالای صفحه ظاهر میشه که نمود چندان جالبی نداره
    برای رفع این ایراد چه میشه کرد؟
    • #
      ‫۱۰ سال و ۳ ماه قبل، شنبه ۲۱ تیر ۱۳۹۳، ساعت ۰۱:۰۸
      در مطلب فعال سازی این فرم‌ها، متد centerDialog و نحوه‌ی اعمال آن به رخداد beforeShowForm عنوان شده. ایده‌ای هست در مورد نحوه‌ی مشخص سازی محل نمایش این فرم‌ها در صفحه.
  • #
    ‫۱۰ سال و ۲ ماه قبل، شنبه ۲۸ تیر ۱۳۹۳، ساعت ۱۹:۰۲
    با سلام؛ چطور می‌توانیم به تابع refresh گرید دسترسی داشته باشم تا بعد از انجام عملیات‌های مختلف , آن را فراخوانی کنم.
    • #
      ‫۱۰ سال و ۲ ماه قبل، شنبه ۲۸ تیر ۱۳۹۳، ساعت ۱۹:۰۸
       $("#list").trigger("reloadGrid");
      ولی در عمل نیازی به این‌کار به صورت دستی نیست؛ چون اگر به مطلب «فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC» دقت کنید، تنظیمات reloadAfterSubmit: false است که می‌شود آن‌ها را true کرد تا به صورت خودکار به همین نتیجه رسید.
      • #
        ‫۱۰ سال و ۲ ماه قبل، شنبه ۲۸ تیر ۱۳۹۳، ساعت ۲۳:۱۴
        با سلام و تشکر
        من در گرید عملیات‌های اضافه کردن و ویرایش کردن و پاک کردن را به صورت inline انجام میدهم در صورتیکه راهنمایی‌های شما جهت کار با pop up  می باشد. محبت بفرمایید برای عملیات‌های inline  راهنمایی نمایید.
  • #
    ‫۴ سال و ۱۰ ماه قبل، دوشنبه ۲۰ آبان ۱۳۹۸، ساعت ۱۷:۱۵
    با سلام؛
    1. در حالت auto complete من اطلاعات رو از جدول دیگری واکشی میکنم. اگه بخواهم بجای تکست اتوکامپلیت، ID رو سمت سرور بفرستم چیکار باید بکنم (مثل حالت drop down) .
    2. اگه بخوام بجای اتوکامپلیت یک گرید ظاهر بشه و اطلاعات رو از اون گرید سلکت کنم چیکار باید کنم؟