مطالب
صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid
پس از آشنایی مقدماتی با Kendo UI DataSource، اکنون می‌خواهیم از آن جهت صفحه بندی، مرتب سازی و جستجوی پویای سمت سرور استفاده کنیم. در مثال قبلی، هر چند صفحه بندی فعال بود، اما پس از دریافت تمام اطلاعات، این اعمال در سمت کاربر انجام و مدیریت می‌شد.



مدل برنامه

در اینجا قصد داریم لیستی را با ساختار کلاس Product در اختیار Kendo UI گرید قرار دهیم:
namespace KendoUI03.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


پیشنیاز تامین داده مخصوص Kendo UI Grid

برای ارائه اطلاعات مخصوص Kendo UI Grid، ابتدا باید درنظر داشت که این گرید، درخواست‌های صفحه بندی خود را با فرمت ذیل ارسال می‌کند. همانطور که مشاهده می‌کنید، صرفا یک کوئری استرینگ با فرمت JSON را دریافت خواهیم کرد:
 /api/products?{"take":10,"skip":0,"page":1,"pageSize":10,"sort":[{"field":"Id","dir":"desc"}]}
سپس این گرید نیاز به سه فیلد، در خروجی JSON نهایی خواهد داشت:
{
"Data":
[
{"Id":1500,"Name":"نام 1500","Price":2499.0,"IsAvailable":false},
{"Id":1499,"Name":"نام 1499","Price":2498.0,"IsAvailable":true}
],
"Total":1500,
"Aggregates":null
}
فیلد Data که رکوردهای گرید را تامین می‌کنند. فیلد Total که بیانگر تعداد کل رکوردها است و Aggregates که برای گروه بندی بکار می‌رود.

می‌توان برای تمام این‌ها، کلاس و Parser تهیه کرد و یا ... پروژه‌ی سورس بازی به نام  Kendo.DynamicLinq نیز چنین کاری را میسر می‌سازد که در ادامه از آن استفاده خواهیم کرد. برای نصب آن تنها کافی است دستور ذیل را صادر کنید:
 PM> Install-Package Kendo.DynamicLinq
Kendo.DynamicLinq به صورت خودکار System.Linq.Dynamic را نیز نصب می‌کند که از آن جهت صفحه بندی پویا استفاده خواهد شد.


تامین کننده‌ی داده سمت سرور

همانند مطلب کار با Kendo UI DataSource ، یک ASP.NET Web API Controller جدید را به پروژه اضافه کنید و همچنین مسیریابی‌های مخصوص آن‌را به فایل global.asax.cs نیز اضافه نمائید.
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Kendo.DynamicLinq;
using KendoUI03.Models;
using Newtonsoft.Json;

namespace KendoUI03.Controllers
{
    public class ProductsController : ApiController
    {
        public DataSourceResult Get(HttpRequestMessage requestMessage)
        {
            var request = JsonConvert.DeserializeObject<DataSourceRequest>(
                requestMessage.RequestUri.ParseQueryString().GetKey(0)
            );

            var list = ProductDataSource.LatestProducts;
            return list.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter);
        }
    }
}
تمام کدهای این کنترلر همین چند سطر فوق هستند. با توجه به ساختار کوئری استرینگی که در ابتدای بحث عنوان شد، نیاز است آن‌را توسط کتابخانه‌ی JSON.NET تبدیل به یک نمونه از DataSourceRequest نمائیم. این کلاس در Kendo.DynamicLinq تعریف شده‌است و حاوی اطلاعاتی مانند take و skip کوئری LINQ نهایی است.
ProductDataSource.LatestProducts صرفا یک لیست جنریک تهیه شده از کلاس Product است. در نهایت با استفاده از متد الحاقی جدید ToDataSourceResult، به صورت خودکار مباحث صفحه بندی سمت سرور به همراه مرتب سازی اطلاعات، صورت گرفته و اطلاعات نهایی با فرمت DataSourceResult بازگشت داده می‌شود. DataSourceResult نیز در Kendo.DynamicLinq تعریف شده و سه فیلد یاد شده‌ی Data، Total و Aggregates را تولید می‌کند.

تا اینجا کارهای سمت سرور این مثال به پایان می‌رسد.


تهیه View نمایش اطلاعات ارسالی از سمت سرور

اعمال مباحث بومی سازی
<head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Language" content="fa" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <title>Kendo UI: Implemeting the Grid</title>

    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <!--شیوه نامه‌ی مخصوص راست به چپ سازی-->
    <link href="styles/kendo.rtl.min.css" rel="stylesheet" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.all.min.js" type="text/javascript"></script>

    <!--محل سفارشی سازی پیام‌ها و مسایل بومی-->
    <script src="js/cultures/kendo.culture.fa-IR.js" type="text/javascript"></script>
    <script src="js/cultures/kendo.culture.fa.js" type="text/javascript"></script>
    <script src="js/messages/kendo.messages.en-US.js" type="text/javascript"></script>

    <style type="text/css">
        body {
            font-family: tahoma;
            font-size: 9pt;
        }
    </style>

    <script type="text/javascript">
        // جهت استفاده از فایل: kendo.culture.fa-IR.js
        kendo.culture("fa-IR");
    </script>
</head>
- در اینجا چند فایل js و css جدید اضافه شده‌اند. فایل kendo.rtl.min.css جهت تامین مباحث RTL توکار Kendo UI کاربرد دارد.
- سپس سه فایل kendo.culture.fa-IR.js، kendo.culture.fa.js و kendo.messages.en-US.js نیز اضافه شده‌اند. فایل‌های fa و fa-Ir آن هر چند به ظاهر برای ایران طراحی شده‌اند، اما نام ماه‌های موجود در آن عربی است که نیاز به ویرایش دارد. به همین جهت به سورس این فایل‌ها، جهت ویرایش نهایی نیاز خواهد بود که در پوشه‌ی src\js\cultures مجموعه‌ی اصلی Kendo UI موجود هستند (ر.ک. فایل پیوست).
- فایل kendo.messages.en-US.js حاوی تمام پیام‌های مرتبط با Kendo UI است. برای مثال«رکوردهای 10 تا 15 از 1000 ردیف» را در اینجا می‌توانید به فارسی ترجمه کنید.
- متد kendo.culture کار مشخص سازی فرهنگ بومی برنامه را به عهده دارد. برای مثال در اینجا به fa-IR تنظیم شده‌است. این مورد سبب خواهد شد تا از فایل kendo.culture.fa-IR.js استفاده گردد. اگر مقدار آن‌را به fa تنظیم کنید، از فایل kendo.culture.fa.js کمک گرفته خواهد شد.

راست به چپ سازی گرید
تنها کاری که برای راست به چپ سازی Kendo UI Grid باید صورت گیرد، محصور سازی div آن در یک div با کلاس مساوی k-rtl است:
    <div class="k-rtl">
        <div id="report-grid"></div>
    </div>
k-rtl و تنظیمات آن در فایل kendo.rtl.min.css قرار دارند که در ابتدای head صفحه تعریف شده‌است.

تامین داده و نمایش گرید

در ادامه کدهای کامل DataSource و Kendo UI Grid را ملاحظه می‌کنید:
    <script type="text/javascript">
        $(function () {
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    parameterMap: function (options) {
                        return kendo.stringify(options);
                    }
                },
                schema: {
                    data: "Data",
                    total: "Total",
                    model: {
                        fields: {
                            "Id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                            "Name": { type: "string" },
                            "IsAvailable": { type: "boolean" },
                            "Price": { type: "number" }
                        }
                    }
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                pageSize: 10,
                sort: { field: "Id", dir: "desc" },
                serverPaging: true,
                serverFiltering: true,
                serverSorting: true
            });

            $("#report-grid").kendoGrid({
                dataSource: productsDataSource,
                autoBind: true,
                scrollable: false,
                pageable: true,
                sortable: true,
                filterable: true,
                reorderable: true,
                columnMenu: true,
                columns: [
                    { field: "Id", title: "شماره", width: "130px" },
                    { field: "Name", title: "نام محصول" },
                    {
                        field: "IsAvailable", title: "موجود است",
                        template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>'
                    },
                    { field: "Price", title: "قیمت", format: "{0:c}" }
                ]
            });
        });
    </script>
- با تعاریف مقدماتی Kendo UI DataSource پیشتر آشنا شده‌ایم و قسمت read آن جهت دریافت اطلاعات از سمت سرور کاربرد دارد.
- در اینجا ذکر contentType الزامی است. زیرا ASP.NET Web API بر این اساس است که تصمیم می‌گیرد، خروجی را به صورت JSON ارائه دهد یا XML.
- با استفاده از parameterMap، سبب خواهیم شد تا پارامترهای ارسالی به سرور، با فرمت صحیحی تبدیل به JSON شده و بدون مشکل به سرور ارسال گردند.
- در قسمت schema باید نام فیلدهای موجود در DataSourceResult دقیقا مشخص شوند تا گرید بداند که data را باید از چه فیلدی استخراج کند و تعداد کل ردیف‌ها در کدام فیلد قرار گرفته‌است.
- نحوه‌ی تعریف model را نیز در اینجا ملاحظه می‌کنید. ذکر نوع فیلدها در اینجا بسیار مهم است و اگر قید نشوند، در حین جستجوی پویا به مشکل برخواهیم خورد. زیرا پیش فرض نوع تمام فیلدها string است و در این حالت نمی‌توان عدد 1 رشته‌ای را با یک فیلد از نوع int در سمت سرور مقایسه کرد.
- در اینجا serverPaging، serverFiltering و serverSorting نیز به true تنظیم شده‌اند. اگر این مقدار دهی‌ها صورت نگیرد، این اعمال در سمت کلاینت انجام خواهند شد.

پس از تعریف DataSource، تنها کافی است آن‌را به خاصیت dataSource یک kendoGrid نسبت دهیم.
- autoBind: true سبب می‌شود تا اطلاعات DataSource بدون نیاز به فراخوانی متد read آن به صورت خودکار دریافت شوند.
- با تنظیم scrollable: false، اعلام می‌کنیم که قرار است تمام رکوردها در معرض دید قرارگیرند و اسکرول پیدا نکنند.
- pageable: true صفحه بندی را فعال می‌کند. این مورد نیاز به تنظیم pageSize: 10 در قسمت DataSource نیز دارد.
- با sortable: true مرتب سازی ستون‌ها با کلیک بر روی سرستون‌ها فعال می‌گردد.
- filterable: true به معنای فعال شدن جستجوی خودکار بر روی فیلدها است. کتابخانه‌ی Kendo.DynamicLinq حاصل آن‌را در سمت سرور مدیریت می‌کند.
- reorderable: true سبب می‌شود تا کاربر بتواند محل قرارگیری ستون‌ها را تغییر دهد.
- ذکر columnMenu: true اختیاری است. اگر ذکر شود، امکان مخفی سازی انتخابی ستون‌ها نیز مسیر خواهد شد.
- در آخر ستون‌های گرید مشخص شده‌اند. با تعیین "{format: "{0:c سبب نمایش فیلدهای قیمت با سه رقم جدا کننده خواهیم شد. مقدار ریال آن از فایل فرهنگ جاری تنظیم شده دریافت می‌گردد. با استفاده از template تعریف شده نیز سبب نمایش فیلد bool به صورت یک checkbox خواهیم شد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI03.zip
پاسخ به بازخورد‌های پروژه‌ها
فضای نام MVC
- البته وابستگی‌های این پروژه چون پوشه nuget. رو به همراه داره، درصورت اتصال به اینترنت (مهم) در اولین build انجام و دریافت می‌شود (کمی طول می‌کشد؛ حدود 48 مگابایت است این وابستگی‌ها). کار دیگری لازم نیست انجام دهید.
- فقط پروژه Iris.Web یک وابستگی اضافه داره که باید حذف شود (چون جزو پروژه است):
in Iris.Web.csproj
    <Reference Include="MyBlog.HttpCompress">
      <HintPath>C:\Users\Mehdi\Desktop\MyBlog.HttpCompress.dll</HintPath>
      <Private>True</Private>
    </Reference>
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت هشتم - تنظیمات پروژه‌های React برای کار با Mobx decorators
تا اینجا دو مثالی را که از Mobx بررسی کردیم (مثال ورود متن و مثال کامپوننت شمارشگر)، به عمد به همراه decoratorهای @ دار آن نبودند. برای مثال در قسمت قبل، یک کلاس را با یک خاصیت ایجاد کردیم که روش مزین سازی خاصیت value آن را با  observable decorator، توسط متد decorate انجام دادیم و این هم یک روش کار با MobX است؛ بدون اینکه نیاز به تنظیمات خاصی را داشته باشد:
import { decorate } from "mobx";

class Count {
  value = 0;
}

decorate(Count, { value: observable });
const count = new Count();
اما اگر همان مثال بسیار ساده‌ی ورود متن را بخواهیم توسط decoratorهای @ دار MobX پیاده سازی کنیم ... پروژه‌ی استاندارد React ما کامپایل نخواهد شد که در این قسمت، روش رفع این مشکل را بررسی می‌کنیم.


بازنویسی مثال ورود متن و نمایش آن با Mobx decorators

در اینجا یک text-box، به همراه دو div در صفحه رندر خواهند شد که قرار است با ورود اطلاعاتی در text-box، یکی از آن‌ها (text-display) این اطلاعات را به صورت معمولی و دیگری (text-display-uppercase) آن‌را به صورت uppercase نمایش دهد. روش کار انجام شده هم مستقل از React است و به صورت مستقیم با استفاده از DOM API عمل شده‌است. این مثال را پیشتر در اولین قسمت بررسی MobX، ملاحظه کردید. اکنون اگر بخواهیم بجای شیءای که توسط متد observable کتابخانه‌ی MobX محصور شده‌است:
const text = observable({
  value: "Hello world!",
  get uppercase() {
    return this.value.toUpperCase();
  }
});
از یک کلاس ES6 به همراه Mobx decorators استفاده کنیم، به یک چنین پروژه‌ی جدیدی خواهیم رسید:
ابتدا یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part3
> cd state-management-with-mobx-part3
> npm start
در ادامه کتابخانه‌ی mobx را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save mobx
در ادامه، ابتدا فایل public\index.html را جهت نمایش دو div و یک text-box، ویرایش می‌کنیم:
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>MobX Basics, part 3</title>
    <meta charset="UTF-8" />
    <link href="src/styles.css" />
  </head>

  <body>
    <main>
      <input id="text-input" />
      <p id="text-display"></p>
      <p id="text-display-uppercase"></p>
    </main>

    <script src="src/index.js"></script>
  </body>
</html>
سپس محتویات فایل src\index.js را نیز به نحو زیر تغییر می‌دهیم:
import { autorun, computed, observable } from "mobx";

const input = document.getElementById("text-input");
const textDisplay = document.getElementById("text-display");
const loudDisplay = document.getElementById("text-display-uppercase");

class Text {
  @observable value = "Hello World";
  @computed get uppercase() {
    return this.value.toUpperCase();
  }
}

const text = new Text();

input.addEventListener("keyup", event => {
  text.value = event.target.value;
});

autorun(() => {
  input.value = text.value;
  textDisplay.textContent = text.value;
  loudDisplay.textContent = text.uppercase;
});
تنها تفاوت این نگارش با نگارش قبلی آن، استفاده از کلاس Text که یک کلاس ES6 به همراه MobX Decorators است، بجای یک شیء ساده‌ی جاوا اسکریپتی می‌باشد. در اینجا خاصیت value به صورت observable@ تعریف شده و در نتیجه‌ی تغییر مقدار آن در کدهای برنامه، خاصیت محاسباتی وابسته‌ی به آن یا همان uppercase که با computed@ تزئین شده، به صورت خودکار به روز رسانی خواهد شد. متد autorun نیز به این تغییرات که حاصل فشرده شدن کلیدها هستند، واکنش نشان داده و متن دو div موجود در صفحه را به روز رسانی می‌کند.

اکنون اگر در همین حال، برنامه را با دستور npm start اجرا کنیم، با خطای زیر متوقف خواهیم شد:
./src/index.js
SyntaxError: \src\index.js: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (8:3):

   6 | 
   7 | class Text {
>  8 |   @observable value = "Hello World";
     |   ^
   9 |   @computed get uppercase() {
  10 |     return this.value.toUpperCase();
  11 |   }

راه حل اول: از Decorators استفاده نکنیم!

یک راه حل مشکل فوق این است که بدون هیچ تغییری در ساختار پروژه‌ی React خود، اصلا از decorator syntax استفاده نکنیم. برای مثال اگر یک کلاس متداول MobX ای چنین شکلی را دارد:
import { observable, computed, action } from "mobx";

class Timer {
  @observable start = Date.now();
  @observable current = Date.now();

  @computed
  get elapsedTime() {
    return this.current - this.start + "milliseconds";
  }

  @action
  tick() {
    this.current = Date.now();
  }
}
می‌توان آن‌را بدون استفاده از decorator syntax، به صورت زیر نیز تعریف کرد:
import { observable, computed, action, decorate } from "mobx";

class Timer {
  start = Date.now();
  current = Date.now();

  get elapsedTime() {
    return this.current - this.start + "milliseconds";
  }

  tick() {
    this.current = Date.now();
  }
}

decorate(Timer, {
  start: observable,
  current: observable,
  elapsedTime: computed,
  tick: action
});
نمونه‌ی این روش را در قسمت قبل با تعریف شیء شمارشگر مشاهده کرده‌اید. در اینجا با توجه به اینکه Decorators در جاوا اسکریپت چیزی نیستند بجز بیان زیبای higher-order functions و higher-order functions هم توابعی هستند که توابع دیگر را با ارائه‌ی قابلیت‌های بیشتری، محصور می‌کنند، به همین جهت هر کاری را که بتوان با تزئین کننده‌ها انجام داد، همان را با توابع معمولی جاوا اسکریپتی نیز می‌توان انجام داد. اینکار را در مثال فوق توسط متد decorate مشاهده می‌کنید. این متد ابتدا نوع کلاس خاصی را دریافت کرده و سپس در پارامتر دوم آن می‌توان شیءای را تعریف کرد که خواص آن، همان خواص کلاس پارامتر اول است و مقادیر این خواص، تزئین کننده‌هایی هستند که قرار است برای آن‌ها بکار گرفته شوند. مزیت این روش بدون تغییر باقی ماندن تعریف کلاس Timer در اینجا و همچنین انجام هیچگونه تغییری در ساختار پروژه‌ی React، بدون نیاز به نصب بسته‌های کمکی اضافی است.

همچنین در این حالت بجای استفاده از کامپوننت‌های کلاسی، باید از روش بکارگیری متد observer برای محصور کردن کامپوننت تابعی تعریف شده استفاده کرد (تا دیگر نیازی به ذکر observer class@ نباشد):
const Counter = observer(({ count }) => {
  return (
   // ...
  );
});


راه حل دوم: از تایپ‌اسکریپت استفاده کنید!

create-react-app امکان ایجاد پروژه‌های React تایپ‌اسکریپتی را با ذکر سوئیچ typescript نیز دارد:
> create-react-app my-proj1 --typescript
پس از ایجاد پروژه، فایل tsconfig.json آن‌را یافته و experimentalDecorators آن‌را به true تنظیم کنید:
{
  "compilerOptions": {
    // ...
    "experimentalDecorators": true
  }
}
این تنها تغییری است که مورد نیاز می‌باشد و پس از آن برنامه‌ی React جاری، بدون مشکلی می‌تواند با decorators کار کند.


فعالسازی MobX Decorators در پروژه‌های استاندارد React مبتنی بر ES6

MobX از legacy" decorators spec" پشتیبانی می‌کند. یعنی اگر پروژه‌ای از spec جدید استفاده کند، دیگر نخواهد توانست با MobX فعلی کار کند. این هم مشکل MobX نیست. مشکل اینجا است که باید دانست کلا decorators در زبان جاوااسکریپت هنوز در مرحله‌ی آزمایشی قرار دارند و تکلیف spec نهایی و تائید شده‌ی آن مشخص نیست.
برای فعالسازی decorators در یک پروژه‌ی React استاندارد مبتنی بر ES6، شاید کمی جستجو کنید و به نتایجی مانند افزودن فایل babelrc. به ریشه‌ی پروژه و نصب افزونه‌هایی مانند babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties@ برسید. اما ... این‌ها بدون اجرای دستور npm run eject کار نمی‌کنند و اگر این دستور را اجرا کنیم، در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است. به همین جهت نیاز به روشی را داریم تا بتوانیم تنظیمات Webpack و کامپایلر Babel را بدون اجرای دستور npm run eject، تغییر دهیم تا در نتیجه، decorators را در آن فعال کنیم و خوشبختانه پروژه‌ی react-app-rewired دقیقا برای همین منظور طراحی شده‌است.

بنابراین ابتدا بسته‌های زیر را نصب می‌کنیم:
> npm i --save-dev customize-cra react-app-rewired
بسته‌ی react-app-rewired، امکان بازنویسی تنظیمات webpack پروژه‌ی react را بدون eject آن میسر می‌کند. customize-cra نیز با استفاده از امکانات همین بسته، نگارش‌های جدیدتر create-react-app را پشتیبانی می‌کند.

پس از نصب این پیشنیازها، فایل جدید config-overrides.js را به ریشه‌ی پروژه، جائیکه فایل package.json قرار گرفته‌است، با محتوای زیر اضافه کنید تا پشتیبانی ازlegacy" decorators spec" فعال شوند:
const {
  override,
  addDecoratorsLegacy,
  disableEsLint
} = require("customize-cra");

module.exports = override(
  // enable legacy decorators babel plugin
  addDecoratorsLegacy(),

  // disable eslint in webpack
  disableEsLint()
);
در ادامه فایل package.json را گشوده و قسمت scripts آن‌را برای استفاده‌ی از react-app-rewired، به صورت زیر بازنویسی کنید تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود:
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },
پس از این تغییرات، نیاز است دستور npm start را یکبار دیگر از ابتدا اجرا کنید. اکنون برنامه بدون مشکل کامپایل شده و خروجی بدون خطایی در مرورگر نمایش داده خواهد شد.


تنظیمات ESLint مخصوص کار با decorators

فایل ویژه‌ی eslintrc.json. که در ریشه‌ی پروژه قرار می‌گیرد (این فایل بدون نام است و فقط از پسوند تشکیل شده)، برای پروژه‌های MobX، باید حداقل تنظیم زیر را داشته باشد تا ESLint بتواند legacyDecorators را نیز پردازش کند:
{
  "extends": "react-app",
  "parserOptions": {
    "ecmaFeatures": {
      "legacyDecorators": true
    }
  }
}
و یا یک نمونه‌ی غنی شده‌ی فایل eslintrc.json. مخصوص برنامه‌های React به صورت زیر است:
{
    "env": {
        "node": true,
        "commonjs": true,
        "browser": true,
        "es6": true,
        "mocha": true
    },
    "settings": {
        "react": {
            "version": "detect"
        }
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true,
            "legacyDecorators": true
        },
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [
        "babel",
        "react",
        "react-hooks",
        "react-redux",
        "no-async-without-await",
        "css-modules",
        "filenames",
        "simple-import-sort"
    ],
    "rules": {
        "no-const-assign": "warn",
        "no-this-before-super": "warn",
        "constructor-super": "warn",
        "strict": [
            "error",
            "safe"
        ],
        "no-debugger": "error",
        "brace-style": [
            "error",
            "1tbs",
            {
                "allowSingleLine": true
            }
        ],
        "no-trailing-spaces": "error",
        "keyword-spacing": "error",
        "space-before-function-paren": [
            "error",
            "never"
        ],
        "spaced-comment": [
            "error",
            "always"
        ],
        "vars-on-top": "error",
        "no-undef": "error",
        "no-undefined": "warn",
        "comma-dangle": [
            "error",
            "never"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ],
        "guard-for-in": "error",
        "no-eval": "error",
        "no-with": "error",
        "valid-typeof": "error",
        "no-unused-vars": "error",
        "no-continue": "warn",
        "no-extra-semi": "warn",
        "no-unreachable": "warn",
        "no-unused-expressions": "warn",
        "max-len": [
            "warn",
            80,
            4
        ],
        "react/prefer-es6-class": "warn",
        "react/jsx-boolean-value": "warn",
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn",
        "react/prop-types": "off",
        "react-redux/mapDispatchToProps-returns-object": "off",
        "react-redux/prefer-separate-component-file": "off",
        "no-async-without-await/no-async-without-await": "warn",
        "css-modules/no-undef-class": "off",
        "filenames/match-regex": [
            "off",
            "^[a-zA-Z]+\\.*\\b(typescript|module|locale|validate|test|action|api|reducer|saga)?\\b$",
            true
        ],
        "filenames/match-exported": "off",
        "filenames/no-index": "off",
        "simple-import-sort/sort": "error"
    },
    "extends": [
        "react-app",
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:react-redux/recommended",
        "plugin:css-modules/recommended"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly",
        "process": true
    }
}
البته برای اینکه این تنظیمات کار کند، باید افزونه‌های زیر را نیز به صورت محلی در ریشه‌ی پروژه‌ی جاری نصب کنید (این مورد از ESLint 6x به بعد اجباری است و از بسته‌های global استفاده نمی‌کند):
>npm i --save-dev eslint babel-eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-react eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort eslint-loader typescript
پس از آن می‌توان فایل config-overrides.js را به صورت زیر نیز بر اساس تنظیمات فوق، بهبود بخشید:
const {
  override,
  addDecoratorsLegacy,
  useEslintRc
} = require("customize-cra");

module.exports = override(
  addDecoratorsLegacy(),
  useEslintRc(".eslintrc.json")
);


رفع اخطار مرتبط با decorators در VSCode

تا اینجا کار تنظیم کامپایلر babel، جهت پردازش decorators انجام شد. اما خود VSCode نیز چنین اخطاری را در پروژه‌هایی که از decorates استفاده می‌کنند، نمایش می‌دهد:
Experimental support for decorators is a feature that is subject to change in a future release.
Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.ts(1219)
برای رفع آن، فایل جدید tsconfig.json را در ریشه‌ی پروژه ایجاد کرده و آن‌را به صورت زیر تکمیل کنید تا ادیتور تایپ‌اسکریپتی VSCode، دیگر خطاهای مرتبط با decorators را نمایش ندهد:
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "allowJs": true
    }
}

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part3.zip
مطالب
NiftyDotNet!

NiftyDotNet یک کنترل ASP.Net 2.0 است که امکان ساخت قطعاتی با گوشه‌های گرد را به سهولت میسر می‌سازد. این کنترل در حقیقت محصور کننده‌ی مجموعه Nifty Corners Cube است.

صفحه‌ی اصلی در گوگل کد
یک سری مثال کاربردی
دریافت سورس، مثالها و فایل بایناری آن از رپیدشیر

مثال:
گرد کردن گوشه‌های div ایی با id‌ مساوی box1 که توسط خاصیت Selectors این کنترل مشخص شده است (اگر class این div برای مثال مساوی cls1 بود این مقدار می‌بایست مساوی div.cls1 قرار می‌گرفت؛ شبیه به روش jQuery).
<%@ Register Assembly="NiftyDotNet" Namespace="NiftyDotNet" TagPrefix="cc1" %>

<div id="container">
<cc1:Nifty ID="Nifty1" runat="server" CornerSize="Big" Selectors="div#box1">
</cc1:Nifty>
<div id="box1" style="background: #e6e6e6; color: Black; width: 18em;">
<h1>
NiftyDotNet</h1>
<p>
Just drag & drop to round!</p>
</div>
</div>


مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت شانزدهم
در این قسمت می‌خواهیم به بحث Style دهی و Font‌ها در Xamarin Forms بپردازیم. در XF به دو روش می‌توان Style اعمال کرد؛ یکی با CSS و دیگری با Xaml. از هر روشی که استفاده کنیم، Styleها درون Resource‌ها قرار می‌گیرند. Resource، یک Dictionary است که درون آن هر چیزی می‌توان قرار داد؛ یک string یا Style یا عدد و ...
فایل App.xaml و همچنین تک تک صفحات، دارای Resources هستند که اگر چیزی درون App.xaml Resources قرار بگیرد، در کل برنامه می‌توان از آن استفاده کرد. برای مثال در صورت قرار دادن یک Style در App Resources، آن Style بر روی کل برنامه قابلیت اعمال پیدا می‌کند. همچنین اگر چیزی درون Resources یک صفحه قرار بگیرد، فقط برای کنترل‌های درون آن صفحه در دسترس است و ...
در روش Style دهی به سبک Xaml، می‌توانید از تمامی امکاناتی که تا اینجا یاد گرفته‌اید استفاده کنید؛ برای مثال Triggers و Binding و ... . هر Style یک Target Type دارد، اگر Style ای Target Type اش Label باشد، برای تمامی Property‌های Label می‌تواند تعیین تکلیف کند:
<Style TargetType="Label">
    <Setter Property="FontFamily" Value="Some font..." />
</Style>
هر Label مقدار FontFamily یا سایر Property‌های خودش را به شکل زیر حساب می‌کند:
1- اگر بر روی یک Label، صراحتا FontFamily مقدار دهی شود، آن مقدار معتبر در نظر گرفته می‌شود.
2- اگر در Resource‌های آن صفحه ای که Label درون آن قرار دارد، Style برای Label تعریف شود و در آن Style به FontFamily مقداری تخصیص داده شده باشد، آن مقدار ملاک قرار می‌گیرد.
3- اگر نه بر روی خود Label و نه در Resourceهای صفحه ای که Label در آن قرار دارد، مقدار FontFamily مشخص نشود، آنگاه بررسی می‌شود که آیا Style ای در App.xaml برای Label وجود دارد که به FontFamily مقداری داده باشد یا نه، که اگر وجود داشته باشد، آن عدد ملاک است.
4- در نهایت اگر هیچ جایی از FontFamily صحبتی نشده باشد، مقدار پیش فرض ارائه شده توسط تیم Xamarin Forms ملاک قرار می‌گیرد.

البته این آموزش همه جزئیات را شامل نمی‌شود تا از پیچیده شدن بحث جلوگیری کند و با همین مقدار از یادگیری نیز می‌توانید به خوبی کار خود را پیش ببرید.
حال اگر بخواهیم تمامی صفحات برنامه، Background آبی داشته باشند، می‌توان در App.xaml برای ContentPageها یک Style نوشت که BackgroundColor را آبی تعیین کرده باشد:
<Style TargetType="ContentPage" ApplyToDerivedTypes="True" >
    <Setter Property="BackgroundColor" Value="Blue" />
</Style>  
اگر دقت کنید، ApplyToDerivedTypes نیز مقدار True گرفته است. علت این است که صفحه لاگین که برای مثال قصد آبی کردن آن را به همراه تمامی صفحات دیگر داریم، از جنس ContentType نیست، بلکه از آن ارث بری کرده‌است. همچنین PopupPage‌ها نیز از ContentPage ارث بری کرده‌اند. با ApplyToDerivedTypes ما این Style را نه تنها بر روی ContentPage که بر روی کلاس‌هایی که از آن ارث بری کرده‌اند نیز اعمال می‌کنیم. صد البته که در پروژه ما خود ContentPage اصلا جایی مستقیما استفاده نشده‌است و تمامی صفحات ما، در واقع ارث بری ای از ContentPage محسوب می‌شوند. البته در مثال Label، ارث بری ای در کار نیست و همیشه مستقیما از Label استفاده می‌کنیم، نه اینکه از آن ارث بری کنیم.
حال ممکن است بخواهیم Style ای تعریف کنیم به نام Danger Button که نه کل دکمه‌های برنامه که چندتایی در صفحات مختلف می‌خواهند از آن استفاده کنند. اگر برای Button یک Style نوشته شود، تمامی Buttonها آن را خودکار می‌گیرند، پس راه حل چیست؟ راه حل این است که به Style ای که تعریف کرده‌ایم، یک x:Key دهیم. اگر Style ای Key داشته باشد، باید موقع استفاده در هر Button، نام آن Key ذکر شود.
<Style x:Key="DangerButton" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Red" />
    <Setter Property="FontAttributes" Value="Bold" />
</Style>  
سپس موقع استفاده داریم:
<Button Style="{StaticResource DangerButton}" />
وقتی از StaticResource استفاده می‌کنیم، در Resourceهای صفحه‌ای که Button در آن قرار دارد و یا در App.xaml دنبال یک Style با x:Key برابر با DangerButton می‌گردد.
برای استفاده از Binding/Trigger در Styleها مثالی را داریم که متن تمامی Entryها به صورت Bold می‌شود، وقتی که IsFocused آنها True است!
<Style TargetType="Entry">
    <Style.Triggers>
        <Trigger TargetType="Entry" Property="IsFocused" Value="True">
            <Setter Property="FontAttributes" Value="Bold" />
        </Trigger>
    </Style.Triggers>
</Style>
برای Style دهی به شکل CSS نیز مثال زیر را داریم که متن تمامی buttonها را bold می‌کند:
<StyleSheet>
<![CDATA[
    button {
        font-style: bold;
    }
]]>
</StyleSheet>
می‌توان cssها را در فایل‌هایی با پسوند css نیز نوشت و به پروژه اضافه کرد. همچنین هر کنترل، دو ویژگی StyleId و class را دارد که بتوان به آن class / id داد تا در css به صورت . یا # استایل داد. مواردی چون stackpanel label به معنی label هایی که درون stack panel هستند و ... نیز پشتیبانی می‌شوند.
در رابطه با فونت نیز شما باید ابتدا فونت یا فونت‌های مربوطه را به هر سه پروژه Android/iOS/Windows اضافه کنید و سپس در App.xaml برای Font Family کنترل‌های مختلفی چون Label و ... از آن فونت استفاده کنید.
برای این مهم، ابتدا فایل‌های فونتی را انتخاب کرده (مثلا Open Sans) را به پروژه Windows/Android/iOS اضافه کنید: (سه فایل ما OpenSansItalic.ttf/OpenSansBold.ttf /OpenSansRegular.ttf هستند) 
در ویندوز آنها را در فولدر Assets/Fonts و در Android در فولدر Assets/Fonts و در iOS در فولدر Resources/Fonts کپی کنید. در iOS علاوه بر این کار، این‌ها را نیز به Key Value‌های فایل info.plist نیز اضافه کنید:
<key>UIAppFonts</key>
<array>
    <string>Fonts/OpenSansItalic.ttf</string>
    <string>Fonts/OpenSansRegular.ttf</string>
    <string>Fonts/OpenSansBold.ttf</string>
</array>  

سپس کدهای زیر را استفاده کنید:

<bitView:OnPlatform
    x:Key="OpenSansRegular"
    x:TypeArguments="x:String"
    Value="{OnPlatform Android='Fonts/OpenSansRegular.ttf#Open Sans',
                        iOS='OpenSans-Regular',
                        UWP='Assets/Fonts/OpenSansRegular.ttf#Open Sans'}" />

<!--  Italic مشابه کد بالا برای -->
<!--  Bold مشابه کد بالا برای -->

چون آدرس و نحوه نام دهی FontFamily در سه پلتفرم متفاوت است، با استفاده از OnPlatform، یک String می‌سازیم با x:Key برابر با OpenSansRegular که در هر پلتفرم مقدار خود را دارد. سپس از این نام برای مقدار دهی FontFamily در کنترل‌های Label/Entry/Button و ... در حالت‌های None/Italic/Bold استفاده می‌کنیم. برای مثال:

<Style TargetType="Label">
    <Style.Triggers>
        <Trigger TargetType="Label" Property="FontAttributes" Value="Bold">
            <Setter Property="FontFamily" Value="{StaticResource OpenSansBold}" />
        </Trigger>
        <Trigger TargetType="Label" Property="FontAttributes" Value="Italic">
            <Setter Property="FontFamily" Value="{StaticResource OpenSansItalic}" />
        </Trigger>
        <Trigger TargetType="Label" Property="FontAttributes" Value="None">
            <Setter Property="FontFamily" Value="{StaticResource OpenSansRegular}" />
        </Trigger>
    </Style.Triggers>
</Style>

این کد می‌گوید زمانیکه FontAttributes یک Label برابر با Bold است، از OpenSansBold برای FontFamily اش استفاده شود و همینطور برای Italic و None (یا همان Regular)

در قسمتی‌که داشتیم برای اندروید و ویندوز، مسیر فایل فونت را مشخص می‌کردیم، از مقدار OpenSansRegular.ttf#Open Sans استفاده کردیم که OpenSansRegular.ttf نام فیزیکی فایل و Open Sans نام خود فایل است که با دو بار کلیک کردن روی فایل آن در ویندوز از طریق برنامه Font ویندوز قابل مشاهده است:

همچنین برای اینکه این سه فایل، سه بار برای سه پلتفرم در سورس کنترلر کپی نشوند، از روش Add as link در Visual Studio بهره گرفته‌ایم و فایل فیزیکی فونت‌ها فقط در پروژه UWP وجود دارند. البته این به معنای این نیست که در Apk نهایی Android و ipa نهایی iOS این فایلها وجود نخواهند داشت؛ بلکه به خاطر ماهیت Add as link، انگار که این فایل‌ها در هر سه پروژه هستند و پشت صحنه کپی می‌شوند.

مطالب
شروع به کار با Aurelia
در این مطلب قصد داریم نحوه‌ی راه اندازی و شروع به کار با Aurelia را توضیح دهیم. برای شروع ابتدا باید Git را نصب کنیم. زیرا می‌خواهیم از jspm برای نصب محتویات Aurelia استفاده کنیم و برای کار کردن با jspm باید Git بر روی سیستم نصب باشد. در صورتیکه Git نصب نباشد، زمان init کردن jspm با خطای زیر رو به رو می‌شویم :

پس از نصب Git اطمینان حاصل کنید که NodeJs ، npm و jspm نیز بر روی سیستم شما نصب باشند. در این قسمت گفته‌ایم که چگونه از این «اطمینان» آگاه شوید.

حال نوبت به ساخت اولین پروژه‌ی MVC ما می‌رسد. یک پروژه MVC جدید با نام دلخواه خودتان در مسیر دلخواه خودتان ایجاد کنید. با خط فرمان، در ریشه‌ی پروژه دستور زیر را اجرا کنید:

jspm init

بدون هیچ تغییری، به هیچ کدام از سوالات پاسخ ندهید و از دکمه‌ی enter استفاده کنید تا مقادیر پیشفرض اعمال شوند. اگر تصویر زیر را در خروجی مشاهده کردید یعنی تا بدین جای کار به درستی پیش رفته‌اید :

حالا نوبت به نصب محتویات Aurelia می‌باشد. برای این کار دستورات زیر را اجرا کنید : 

jspm install aurelia-framework
jspm install aurelia-bootstrapper
با صادر کردن دستورات بالا، jspm تمامی فایل‌های مورد نیاز Aurelia را در پوشه‌ی jspm_packages قرار می‌دهد.

توجه داشته باشید، اگر دستورات بالا به درستی اجرا و تکمیل شوند، باید پس از پایان هر دستور، پیام زیر را در انتهای خروجی مشاهده کنید:
 

پس از این کارها، فایل Layout را باز کنید و کدهای آن را به صورت زیر تغییر دهید:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Aurelia - www.dotnettips.info</title>
</head>
<body aurelia-app>
    <div>
        @RenderBody()
    </div>
    <script src="~/jspm_packages/system.js"></script>
    <script src="~/config.js"></script>
    <script>
        System.import("aurelia-bootstrapper");
    </script>
</body>
</html>
سپس در ریشه‌ی برنامه یک فایل جاوااسکریپت را با نام app ایجاد کنید و کدهای زیر را در آن قرار دهید:
export class App {
    
}
حالا به یک View، برای این کار نیاز داریم. در همان ریشه‌ی سایت یک فایل html را با نام app ایجاد کنید و کدهای زیر را در آن قرار دهید:
<template>
    <h3>www.dotnettips.info</h3>
</template>
پس از سپری کردن این 3 مرحله، برنامه‌ی خود را اجرا کنید. باید خروجی زیر را مشاهده کنید:

در بخش‌های بعدی در مورد کدهای فوق و همچنین به سایر مباحث دیگر Aurelia می‌پردازیم.

نکته‌ی تکمیلی

زمانیکه می‌خواهید از دستوارت jspm استفاده کنید، باید به ریشه‌ی برنامه مراجعه کنید. حال اگر پوشه‌های تودرتوی زیادی داشته باشید، این رفت و آمد‌ها زمانبر و خسته کننده خواهند شد. برای حل این مشکل کافیست روی پروژه، راست کلیک کنید و گزینه‌ی Open Folder in File Explorer را انتخاب کنید تا ریشه‌ی پروژه باز شود. حالا  کافیست در فضای خالی Windows Explorer کلید Shift را گرفته و راست کلیک کنید. حالا یک آیتم جدید با نام Open command window here ظاهر شده است. کافیست روی آن کلیک کنید. CMD در ریشه‌ی سایت باز خواهد شد.


دانلود پروژه جاری در مخزن گیت
مطالب
روش استفاده از Async&Await در Portable Class Library دات نت 4
چند وقت پیش زمانی که قصد داشتم از یک Portable Class Library  که تحت دات نت 4 بود توی پروژه ام استفاده کنم متوجه شدم که این نوع Class Library از فضای نام System.Threading.Task پشتیبانی نمی‌کنه. قصد داشتم که از این فضای نام برای بحث TPL توی پروژه ام استفاده کنم و چند تا متد Async بنویسم. زمانی که سعی کردم  با استفاده از Nuget؛ AsyncAwaitCTP رو نصب کنم با خطای زیر روبرو شدم.

دستور مورد نظر در Nuget :
PM> install-package AsyncAwaitCtp
و اما خطا
Install failed. Rolling back...
install-package : Could not install package 'AsyncAwaitCTP 1.0'. You are trying to install this package into a project that targets '.NETPortable,Version=v4.0,Profile=Profile3', 
but the package does not contain any assembly references that are compatible with that framework. For more information, contact the package author.
At line:1 char:1
دستور بالا برای نصب AsyncAwaitCTP در Class Library تحت دات نت 4.5 استفاده می‌شه.
خلاصه بعد از یکم جستجو متوجه شدم که باید از دستور زیر برای نصب TPL توی Portable Class Library تحت دات نت 4 استفاده کنم.
PM > install-Package Microsoft-BCL-Async -Pre
که بازم با خطای زیر مواجه شدم.
Install failed. Rolling back...
install-package : Could not install package 'Microsoft.Bcl 1.0.16-rc'. You are trying to install this package into a project that targets 
'.NETPortable,Version=v4.0,Profile=Profile3', but the package does not contain any assembly references that are compatible with that framework. For more information, contact the 
package author.
At line:1 char:1
+ install-package Microsoft.BCL.Async -Pre
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Install-Package], InvalidOperationException
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand
از اونجا که از دستور بالا مطمئن بودم و می‌دونستم که باید درست کار کنه فهمیدم اشکال کار از یه جای دیگه است. خلاصه بعد از یه جستجوی 25 دقیقه ای متوجه شدم که
Nuget نصب شده روی سیستم من Update نیست. برای همین به روش زیر عمل کردم .
از منوی Tools گزینه Extension And Updates رو انتخاب کردم. بعد از صفحه مورد نظر گزینه Updates روز از منوی سمت چپ انتخاب کردم و درنهایت گزینه Nuget Package Manager. مثل شکل زیر:


بعد از اتمام عملیات Update دوباره دستورات مورد نظر رو وارد کردم که به خوبی عملیات نصب CTP به اتمام رسید.





مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
jqGrid یکی از افزونه‌های بسیار محبوب jQuery جهت نمایش جدول مانند اطلاعات، در سمت کلاینت است. توانمندی‌های آن صرفا به نمایش ستون‌ها و ردیف‌ها خلاصه نمی‌شود. قابلیت‌هایی مانند صفحه بندی، مرتب سازی، جستجو، ویرایش توکار، تولید خودکار صفحات افزودن رکوردها، اعتبارسنجی داده‌ها، گروه بندی، نمایش درختی و غیره را نیز به همراه دارد. همچنین به صورت توکار پشتیبانی از راست به چپ را نیز لحاظ کرده‌است.
 مجوز استفاده از فایل‌های جاوا اسکریپتی آن MIT است؛ به این معنا که در هر نوع پروژه‌ای قابل استفاده است. مجوز استفاده از کامپوننت‌های سمت سرور آن که برای نمونه جهت ASP.NET MVC یک سری HTML Helper را تدارک دیده‌اند، تجاری می‌باشد. در ادامه قصد داریم صرفا از فایل‌های JS عمومی آن استفاده کنیم.


دریافت jqGrid

برای دریافت jqGrid می‌توانید به مخزن کد آن، در آدرس https://github.com/tonytomov/jqGrid/releases و یا از طریق NuGet اقدام کنید:
 PM> Install-Package Trirand.jqGrid
استفاده از NuGet بیشتر توصیه می‌شود، زیرا به صورت خودکار وابستگی‌های jQuery و همچنین jQuery UI آن‌را نیز به همراه داشته و نصب خواهد کرد.
از jQuery UI برای تولید صفحات جستجوی بر روی رکوردها و همچنین تولید خودکار صفحات ویرایش و یا افزودن رکوردها استفاده می‌کند. به علاوه آیکن‌ها، قالب و رنگ خود را نیز از jQuery UI دریافت می‌کند. بنابراین اگر قصد تغییر قالب آن‌را داشتید تنها کافی است یک قالب استاندارد دیگر jQuery UI را مورد استفاده قرار دهید.


تنظیمات اولیه فایل Layout سایت

پس از دریافت بسته‌ی نیوگت jqGrid، نیاز است فایل‌های مورد نیاز اصلی آن‌را به شکل زیر به فایل 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/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>

    @RenderSection("Scripts", required: false)
</body>
</html>
فایل jquery.ui.all.css شامل تمامی فایل‌های CSS مرتبط با jQuery UI است و نیازی نیست تا سایر فایل‌های آن‌را لحاظ کرد.
این گرید به همراه فایل زبان فارسی grid.locale-fa.js نیز می‌باشد که در کدهای فوق پیوست شده‌است. البته اگر فرصت کردید نیاز است کمی ترجمه‌های آن بهبود پیدا کنند.


تنظیمات ثانویه site.css

.ui-widget {
}

/*how to move jQuery dialog close (X) button from right to left*/
.ui-jqgrid .ui-jqgrid-caption-rtl {
    text-align: center !important;
}

.ui-dialog .ui-dialog-titlebar-close {
    left: .3em !important;
}

.ui-dialog .ui-dialog-title {
    margin: .1em 0 .1em .8em !important;
    direction: rtl !important;
    float: right !important;
}
احتمالا تنظیمات قلم‌های jQuery UI و یا jqGrid مدنظر شما نیستند و نیاز به تعویض دارند. در اینجا نحوه‌ی بازنویسی آن‌ها را ملاحظه می‌کنید.
همچنین محل قرار گیری دکمه‌ی بسته شدن دیالوگ‌ها و راست به چپ کردن عناوین آن‌ها نیز در اینجا قید شده‌اند.


مدل برنامه

در ادامه قصد داریم لیستی از محصولات را با ساختار ذیل، توسط jqGrid نمایش دهیم:
namespace jqGrid01.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


ساختار داده‌ای مورد نیاز توسط jqGrid

jqGrid مستقل است از فناوری سمت سرور. بنابراین هر چند در عنوان بحث ASP.NET MVC ذکر شده‌است، اما از ASP.NET MVC صرفا جهت بازگرداندن خروجی JSON استفاده خواهیم کرد و این مورد در هر فناوری سمت سرور دیگری نیز می‌تواند انجام شود.
using System.Collections.Generic;

namespace jqGrid01.Models
{
    public class JqGridData
    {
        public int Total { get; set; }

        public int Page { get; set; }

        public int Records { get; set; }

        public IList<JqGridRowData> Rows { get; set; }

        public object UserData { get; set; }
    }

    public class JqGridRowData
    {
        public int Id { set; get; }
        public IList<string> RowCells { set; get; }
    }
}
خروجی JSON مدنظر توسط jqGrid، یک چنین ساختاری را باید داشته باشد.
Total، نمایانگر تعداد صفحات اطلاعات است. عدد Page، شماره صفحه‌ی جاری است. عدد Records، تعداد کل رکوردهای گزارش را مشخص می‌کند. ساختار ردیف‌های آن نیز تشکیل شده‌است از یک Id به همراه سلول‌هایی که باید با فرمت string، بازگشت داده شوند.
UserData اختیاری است. برای مثال اگر خواستید جمع کل صفحه را در ذیل گرید نمایش دهید، می‌توانید یک anonymous object را در اینجا مقدار دهی کنید. خاصیت‌های آن دقیقا باید با نام خاصیت‌های ستون‌های متناظر، یکی باشند. برای مثال اگر می‌خواهید عددی را در ستون Id، در فوتر گرید نمایش دهید، باید نام خاصیت را Id ذکر کنید.


کدهای سمت کلاینت گرید

در اینجا کدهای کامل سمت کلاینت گرید را ملاحظه می‌کنید:
@{
    ViewBag.Title = "Index";
}

<div dir="rtl" align="center">
    <div id="rsperror"></div>
    <table id="list" cellpadding="0" cellspacing="0"></table>
    <div id="pager" style="text-align:center;"></div>
</div>

@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('#list').jqGrid({
                caption: "آزمایش اول",
                //url from wich data should be requested
                url: '@Url.Action("GetProducts","Home")',
                //type of data
                datatype: 'json',
                jsonReader: { 
                    root: "Rows",
                    page: "Page",
                    total: "Total",
                    records: "Records",
                    repeatitems: true,
                    userdata: "UserData",
                    id: "Id",
                    cell: "RowCells"
                },
                //url access method type
                mtype: 'GET',
                //columns names
                colNames: ['شماره', 'نام محصول', 'موجود است', 'قیمت'],
                //columns model
                colModel: [
                { name: 'Id', index: 'Id', align: 'right', width: 50, sorttype: "number" },
                { name: 'Name', index: 'Name', align: 'right', width: 300 },
                { name: 'IsAvailable', index: 'IsAvailable', align: 'center', width: 100, formatter: 'checkbox' },
                { name: 'Price', index: 'Price', align: 'center', width: 100, sorttype: "number" }
                ],
                //pager for grid
                pager: $('#pager'),
                //number of rows per page
                rowNum: 10,
                rowList: [10, 20, 50, 100],
                //initial sorting column
                sortname: 'Id',
                //initial sorting direction
                sortorder: 'asc',
                //we want to display total records count
                viewrecords: true,
                altRows: true,
                shrinkToFit: true,
                width: 'auto',
                height: 'auto',
                hidegrid: false,
                direction: "rtl",
                gridview: true,
                rownumbers: true,
                footerrow: true,
                userDataOnFooter: true,
                loadComplete: function() {
                    //change alternate rows color
                    $("tr.jqgrow:odd").css("background", "#E0E0E0");
                },
                loadError: function(xhr, st, err) {
                     jQuery("#rsperror").html("Type: " + st + "; Response: " + xhr.status + " " + xhr.statusText);
                }
                //, loadonce: true
            })
            .jqGrid('navGrid', "#pager",
            {
                edit: false, add: false, del: false, search: false,
                refresh: true
            })
            .jqGrid('navButtonAdd', '#pager',
            {
                caption: "تنظیم نمایش ستون‌ها", title: "Reorder Columns",
                onClickButton: function() {
                     jQuery("#list").jqGrid('columnChooser');
                }
            });
        });
    </script>
}
- برای نمایش این گرید، به یک جدول و یک div نیاز است. از جدول با id مساوی list جهت نمایش رکوردهای برنامه استفاده می‌شود. از div با id مساوی pager برای نمایش اطلاعات صفحه بندی و نوار ابزار پایین گرید کمک گرفته خواهد شد.
Div سومی با id مساوی rsperror نیز تعریف شده‌است که از آن جهت نمایش خطاهای بازگشت داده شده از سرور استفاده کرده‌ایم.
- در ادامه نحوه‌ی فراخوانی افزونه‌ی jqGrid را بر روی جدول list ملاحظه می‌کنید.
- خاصیت caption، عنوان نمایش داده شده در بالای گرید را مقدار دهی می‌کند:


- خاصیت url، به آدرسی اشاره می‌کند که قرار است ساختار JqGridData ایی را که پیشتر در مورد آن بحث کردیم، با فرمت JSON بازگشت دهد. در اینجا برای مثال به یک اکشن متد کنترلری در یک پروژه‌ی ASP.NET MVC اشاره می‌کند.
- datatype را برابر json قرار داده‌ایم. از نوع xml نیز پشتیبانی می‌کند.
- شیء jsonReader را از این جهت مقدار دهی کرده‌ایم تا بتوانیم شیء JqGridData را با اصول نامگذاری دات نت، هماهنگ کنیم. برای درک بهتر این موضوع، فایل jquery.jqGrid.src.js را باز کنید و در آن به دنبال تعریف jsonReader بگردید. به یک چنین مقادیر پیش فرضی خواهید رسید:
ts.p.jsonReader = $.extend(true,{
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: true,
cell: "cell",
id: "id",
userdata: "userdata",
subgrid: {root:"rows", repeatitems: true, cell:"cell"}
},ts.p.jsonReader);
برای مثال سلول‌ها را با نام cell دریافت می‌کند که در شیء JqGridData به RowCells تغییر نام یافته‌است. برای اینکه این تغییر نام‌ها توسط jqGrid پردازش شوند، تنها کافی است jsonReader را مطابق تعاریفی که ملاحظه می‌کنید، مقدار دهی کرد.
- در ادامه mtype به GET تنظیم شده‌است. در اینجا مشخص می‌کنیم که عملیات Ajax ایی دریافت اطلاعات از سرور توسط GET انجام شود یا برای مثال توسط POST.
- خاصیت colNames، معرف نام ستون‌های گرید است. برای اینکه این نام‌ها از راست به چپ نمایش داده شوند، باید خاصیت direction به rtl تنظیم شود.
- colModel آرایه‌ای است که تعاریف ستون‌ها را در بر دارد. مقدار name آن باید یک نام منحصربفرد باشد. از این نام در حین جستجو یا ویرایش اطلاعات استفاده می‌شود. مقدار index نامی است که جهت مرتب سازی اطلاعات، به سرور ارسال می‌شود. تنظیم sorttype در اینجا مشخص می‌کند که آیا به صورت پیش فرض، ستون جاری رشته‌ای مرتب شود یا اینکه برای مثال عددی پردازش گردد. مقادیر مجاز آن text (مقدار پیش فرض)، float، number، currency، numeric، int ، integer، date و datetime هستند.
- در ستون IsAvailable، مقدار formatter نیز تنظیم شده‌است. در اینجا توسط formatter، نوع bool دریافتی با یک checkbox نمایش داده خواهد شد.
- خاصیت pager به id متناظری در صفحه اشاره می‌کند.
- توسط rowNum مشخص می‌کنیم که در هر صفحه چه تعداد رکورد باید نمایش داده شوند.
- تعداد رکوردهای نمایش داده شده را می‌توان توسط rowList پویا کرد. در اینجا آرایه‌ای را ملاحظه می‌کنید که توسط اعداد آن، کاربر امکان انتخاب صفحاتی مثلا 100 ردیفه را نیز پیدا می‌کند. rowList به صورت یک dropdown در کنار عناصر راهبری صفحه در فوتر گرید ظاهر می‌شود.
- خاصیت sortname، نحوه‌ی مرتب سازی اولیه گرید را مشخص می‌کند.
- خاصیت sortorder، جهت مرتب سازی اولیه‌ی گردید را تنظیم می‌کند.
- viewrecords: تعداد رکوردها را در نوار ابزار پایین گرید نمایش می‌دهد.
- altRows: سبب می‌شود رنگ متن ردیف‌ها یک در میان متفاوت باشد.
- shrinkToFit: به معنای تنظیم خودکار اندازه‌ی سلول‌ها بر اساس اندازه‌ی داده‌ای است که دریافت می‌کنند.
- width: عرض گرید، که در اینجا به auto تنظیم شده‌است.
- height: طول گرید، که در اینجا به auto جهت محاسبه‌ی خودکار، تنظیم شده‌است.
- gridview: برای بالا بردن سرعت نمایشی به true تنظیم شده‌است. در این حالت کل ردیف یکباره درج می‌شود. اگر از subgird یا حالت نمایش درختی استفاده شود، باید این خاصیت را false کرد.
- rownumbers: ستون سمت راست شماره ردیف‌های خودکار را نمایش می‌دهد.
- footerrow: سبب نمایش ردیف فوتر می‌شود.
- userDataOnFooter: سبب خواهد شد تا خاصیت UserData مقدار دهی شده، در ردیف فوتر ظاهر شود.
- loadComplete : یک callback است که زمان پایان بارگذاری صفحه‌ی جاری را مشخص می‌کند. در اینجا با استفاده از jQuery سبب شده‌ایم تا رنگ پس زمینه‌ی ردیف‌ها یک در میان تغییر کند.
- loadError: اگر از سمت سرور خطایی صادر شود، در این callback قابل دریافت خواهد بود.
- در ادامه توسط فراخوانی متد jqGrid با پارامتر navGrid، در ناحیه pager سبب نمایش دکمه refresh شده‌ایم. این دکمه سبب بارگذاری مجدد اطلاعات گردید از سرور می‌شود.
- همچنین به کمک متد jqGrid با پارامتر navButtonAdd در ناحیه pager، سبب نمایش دکمه‌ای که صفحه‌ی انتخاب ستون‌ها را ظاهر می‌کند، خواهیم شد.



پیشنیاز کدهای سمت سرور jqGrid

اگر به تنظیمات گرید دقت کرده باشید، خاصیت index ستون‌ها، نامی است که به سرور، جهت اطلاع رسانی در مورد فیلتر اطلاعات و مرتب سازی مجدد آن‌ها ارسال می‌گردد. این نام، بر اساس کلیک کاربر بر روی ستون‌های موجود، هر بار می‌توان متفاوت باشد. بنابراین بجای if و else نوشتن‌های طولانی جهت مرتب سازی اطلاعات، می‌توان از کتابخانه‌ی معروفی به نام dynamic LINQ استفاده کرد.
 PM> Install-Package DynamicQuery
به این ترتیب می‌توان قسمت orderby را به صورت پویا و با رشته‌ای دریافتی، مقدار دهی کرد.


کدهای سمت سرور بازگشت اطلاعات به فرمت JSON

در کدهای سمت کلاینت، به اکشن متد GetProducts اشاره شده بود. تعاریف کامل آن‌را در ذیل مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
using jqGrid01.Models;
using jqGrid01.Extensions; // for dynamic OrderBy

namespace jqGrid01.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GetProducts(string sidx, string sord, int page, int rows)
        {
            var list = ProductDataSource.LatestProducts;

            var pageIndex = page - 1;
            var pageSize = rows;
            var totalRecords = list.Count;
            var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize);

            var products = list.AsQueryable()
                               .OrderBy(sidx + " " + sord)
                               .Skip(pageIndex * pageSize)
                               .Take(pageSize)
                               .ToList();

            var jqGridData = new JqGridData
            {
                UserData = new // نمایش در فوتر
                {
                    Name = "جمع صفحه",
                    Price = products.Sum(x => x.Price)
                },
                Total = totalPages,
                Page = page,
                Records = totalRecords,
                Rows = (products.Select(product => new JqGridRowData
                                                {
                                                    Id = product.Id,
                                                    RowCells = new List<string>
                                                    {
                                                        product.Id.ToString(CultureInfo.InvariantCulture),
                                                        product.Name,
                                                        product.IsAvailable.ToString(),
                                                        product.Price.ToString(CultureInfo.InvariantCulture)
                                                    }
                                                })).ToList()
            };
            return Json(jqGridData, JsonRequestBehavior.AllowGet);
        }
    }
}
- سطر ProductDataSource.LatestProducts چیزی نیست بجز لیست جنریکی از محصولات.
- امضای متد GetProducts نیز مهم است. دقیقا همین پارامترها با همین نام‌ها از طرف jqGrid به سرور ارسال می‌شوند که توسط آن‌ها ستون مرتب سازی، جهت مرتب سازی، صفحه‌ی جاری و تعداد ردیفی که باید بازگشت داده شوند، قابل دریافت است.
- در این کدها دو قسمت مهم وجود دارند:
الف) متد OrderBy نوشته شده، به صورت پویا عمل می‌کند و از کتابخانه‌ی Dynamic LINQ مایکروسافت بهره می‌برد.
به علاوه توسط Take و Skip کار صفحه بندی و بازگشت تنها بازه‌ای از اطلاعات مورد نیاز، انجام می‌شود.
ب) لیست جنریک محصولات، در نهایت باید با فرمت JqGridData به صورت JSON بازگشت داده شود. نحوه‌ی این Projection را در اینجا می‌توانید ملاحظه کنید.
هر ردیف این لیست، باید تبدیل شود به ردیفی از جنس JqGridRowData، تا توسط jqGrid قابل پردازش گردد.
- توسط مقدار دهی UserData، برچسبی را در ذیل ستون Name و مقداری را در ذیل ستون Price نمایش خواهیم داد.


برای مطالعه‌ی بیشتر

بهترین راهنمای جزئیات این Grid، مستندات آنلاین آن هستند: http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs
همچنین این مستندات را با فرمت PDF نیز می‌توانید مطالعه کنید: http://www.trirand.com/blog/jqgrid/downloads/jqgriddocs.pdf


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid01.zip
 

مثال‌های سری jqGrid تغییرات زیادی داشتند. برای دریافت آن‌ها به این مخزن کد مراجعه کنید. 
مطالب
به روز رسانی ارجاعات یک اسمبلی دارای امضای دیجیتال بدون کامپایل مجدد

سؤال: امروز NHibernate به روز شده اما Fluent NHibernate خیر! چکار باید کرد؟!

Fluent NHibernate کتابخانه‌ای است جهت رهایی برنامه نویس‌ها از نوشتن فایل‌های XML نگاشت کلاس‌ها به جداول به همراه قابلیت‌های دیگری مانند نگاشت خودکار و غیره. بنابراین این کتابخانه بدون NHibernate اصلی بدون کاربرد است. تیم توسعه آن هم با تیم اصلی NHibernate یکی نیست. عموما NHibernate به روز می‌شود اما Fluent NHibernate ممکن است تا دو ماه بعد از آن هم به روز نشود. در این مواقع چه باید کرد؟

دو کار را می‌توان انجام داد:

الف) سورس Fluent NHibernate را دریافت کنیم و سپس ارجاعات قبلی به NHibernate قدیمی را حذف و ارجاعاتی را به اسمبلی‌های جدید آن اضافه و پروژه را کامپایل کنیم.
Fluent NHibernate در طی این مدت به اندازه کافی رشد کرده و به قولی پخته است. کاری را هم که ادعا می‌کند به خوبی انجام می‌دهد. اما چون اسمبلی‌های اصلی NHibernate و همچنین Fluent NHibernate دارای امضای دیجیتال هستند، نمی‌توان از اسمبلی‌های جدید NHibernate به همراه Fluent NHibernate قدیمی‌ استفاده کرد. خطای حاصل شبیه به عبارات ذیل خواهد بود:

System.IO.FileLoadException: Could not load file or assembly ‘nameOfAssembly’,
Version=specificVersion, Culture=neutral, PublicKeyToken=publicKey’ or one of it's dependencies.
The located assembly’s manifest definition does not match the assembly reference.
(Exception from HRESULT: 0x80131040)

حذف ارجاعات به NHibernate قدیمی و افزودن مجدد ارجاعات به فایل‌های جدید و کامپایل نهایی پروژه یک راه حل است.

ب) راه حل دیگر استفاده از ویژگی bindingRedirect است بدون دریافت سورس، حذف و افزودن ارجاعات و کامپایل مجدد:
  <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="NHibernate"
publicKeyToken="aa95f207798dfdb4"
culture="neutral" />
<bindingRedirect oldVersion="3.0.0.4000"
newVersion="3.1.0.4000"/>
</dependentAssembly>
</assemblyBinding>
</runtime>


در این مثال، پس از افزودن تعاریف فوق به فایل config برنامه، به سادگی می‌توان از اسمبلی اصلی NHibernate دارای نگارش 3.1.0.4000 به جای اسمبلی قدیمی‌تر 3.0.0.4000 آن استفاده کرد (همان نگارشی که Fluent NHibernate ما بر اساس آن کامپایل شده) و دیگر نیازی هم به کامپایل مجدد پروژه‌ای که از یک اسمبلی قدیمی Fluent NHibernate استفاده می‌کند، نخواهد بود.

نظرات مطالب
Blogger auto poster
ساخت این فایل خودکار هست و در اولین بار دریافت اطلاعات از وب ساخته می‌شود و اگر ایجاد نشده یعنی برنامه دسترسی نوشتن و رایت در پوشه نصب شده را ندارد. در کل یک نمونه از این فایل رو می‌تونید اینجا پیدا کنید: (^)