مطالب
امکان ساخت قالب برای پروژه‌های NET Core.
یکی از قابلیت‌های ابزار خط فرمان dotnet، امکان تبدیل یک پروژه‌ی سفارشی سازی شده، به یک قالب نصب پروژه‌های جدید بر مبنای آن است. برای مثال فرض کنید می‌خواهیم پروژه‌ی DNTIdentity را تبدیل به یک قالب جدید کنیم تا به سادگی بتوان پروژه‌های جدید را بر مبنای آن ایجاد کرد.


ساخت پوشه‌ی مخصوص template.config.

اولین قدم جهت تبدیل یک پروژه‌ی از پیش موجود، به قالبی جدید، افزودن پوشه‌ی ویژه‌ای به نام template.config. به ریشه‌ی آن است. سپس فایل خالی template.json را با محتوای ذیل به آن اضافه کنید:


{ 
  "author": "VahidN <https://www.dntips.ir/>", 
  "classifications": [ "MVC", ".NET Core", "ASP.NET Core" ],  
  "name": "Empty DNT.Identity project", 
  "identity": "DNT.Identity", 
  "shortName": "dntidentity", 
  "tags": { 
    "language": "C#" 
  }, 
  "sourceName": "ASPNETCoreIdentitySample" 
}
توضیحات:
در اینجا متادیتای تعریف شده شامل موارد ذیل است:
Author: اطلاعات نویسنده است.
Classification: امکان جستجوی بهتر این قالب را فراهم می‌کند.
Name: توضیحاتی در مورد پروژه.
Identity: نام منحصربفرد پروژه.
ShortName: نامی است که از آن جهت تولید پروژه‌های جدید، استفاده می‌شود.
SourceName: مهم‌ترین تنظیم این گروه بوده و نام فضای نام اصلی پروژه‌است. زمانیکه پروژه‌ی جدیدی را ایجاد می‌کنید، این نام به صورت خودکار بر اساس نام جدید انتخابی اصلاح و جایگزین خواهد شد (در تمام پروژه‌های مربوط به solution جاری).


معرفی قالب تهیه شده به سیستم dotnet

پس از ساخت فایل template.config\template.json. در ریشه‌ی پروژه، اکنون از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور ذیل را صادر کنید:
 dotnet new -i %~dp0
در اینجا dp0~% با آدرس پوشه‌ی جاری جایگزین می‌شود. اگر نیاز است آن‌را به صورت دستی مقدار دهی کنید.
پس از نصب این پوشه به عنوان یک قالب جدید، یکبار از سیستم وجود آن‌را کوئری بگیرید:
 dotnet new --list
اگر به خروجی آن دقت کنید، یک سطر ذیل به آن اضافه شده‌است:
Templates                                         Short Name       Language          Tags                      
---------------------------------------------------------------------------------------------------------------
Console Application                               console          [C#], F#, VB      Common/Console            
Class library                                     classlib         [C#], F#, VB      Common/Library            
Empty DNT.Identity project                        dntidentity      [C#]              MVC/.NET Core/ASP.NET Core


نحوه‌ی ایجاد یک پروژه‌ی جدید بر اساس قالب نصب شده

پس از ساخت این قالب جدید و معرفی آن به سیستم، نحوه‌ی کار با آن به صورت ذیل است:
 dotnet new dntidentity -n MyNewProj
در اینجا dntidentity همان Short Name تنظیم شده‌است و پارامتر n، نام Solution جدید را مشخص می‌کند. پس از اجرای این دستور مشاهده خواهید کرد که این نام جدید بر روی نام پوشه‌ها و همچنین فضاهای نام تولیدی به صورت خودکار اعمال شده‌است و مقدار «ASPNETCoreIdentitySample» پیش‌فرض را بازنویسی کرده‌است.

پروژه‌ها
ادغام، اضافه کردن و جدا کردن صفحات PDF
این پروژه ساده را به صورت Windows Form Application و با زبان Python ساختم. می‌تواند مثالی باشد برای کسانی که میخواهند پایتون را در ویندوز یاد بگیرند و اجرا کنند. اگر کسی پروژه را امتحان کرد، خوشحال خواهم شد خطا‌ها و اشکالات را اینجا اعلام کند.
  1. ادغام چند فایل PDF به صورت فایل واحد (Merging)
  2. اضافه کردن یک فایل PDF به یک فایل موجود (Appending)
  3. جدا کردن صفحات دلخواه از یک فایل و ذخیره آنها در یک فایل جدا (Splitting)
نحوه اجرا: 
  1. در صورت نصب امکانات پایتون برای ویژوال استودیو مثل سایر پروژه‌های دات نت قابل اجرا و دیباگ است.
  2. از طریق خط فرمان  و دستور ipy که در ادامه مثالی میزنم.
  3. من خودم از طریق یک برنامه کنسول ساده پروژه را خارج از محیط توسعه اجرا میکنم که در ادامه مثالی میزنم.
 اجرا از خط فرمان:
  ipy C:\\PdfTools\\PdfTools.py

اجرا از برنامه کنسول:
var process = new Process();
var startInfo = new ProcessStartInfo
      {
            WindowStyle = ProcessWindowStyle.Hidden,
            FileName = "CMD.exe",
            Arguments = "/C ipy C:\\PdfTools\\PdfTools.py"
      };
process.StartInfo = startInfo;
process.Start();
نکته: در صورتی که بخواهید از برنامه کنسول برای اجرا استفاده کنید آیکن‌های برنامه باید در پوشه Debug وجود داشته باشند! 

نظرات مطالب
مبانی TypeScript؛ تهیه فایل‌های تعاریف نوع‌ها
نکته‌ای در مورد رفع مشکل «typings ERR! caused by connect ECONNREFUSED 10.10.34.36:443» پس از نصب TypeScript 2.0

پس از ارائه‌ی TypeScript 2.0، مایکروسافت کار مدیریت مخزن Typings را به عهده گرفته‌است و کار آن توزیع مخزن DefinitelyTyped به npm است و دیگر نیازی به استفاده از ابزارهای  typings و یا tsd نیست. برای مثال اینبار بجای اینکه دستور ذیل را صادر کنیم:
typings install lodash --ambient --save
که با خطای یاد شده متوقف می‌شود، می‌توان نوشت:
npm install --save @types/lodash
یعنی همه چیز مانند حالت نصب معمولی lodash است (npm install --save lodash)؛ اما یک types@ را در ابتدای آن بیشتر دارد. در این حالت فایل‌های d.ts. را در پوشه‌ی types@ ذیل node_modules خواهید یافت:

مطالب
صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک 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
مطالب
React 16x - قسمت 11 - طراحی یک گرید - بخش 1 - کامپوننت صفحه بندی
در طی چند قسمت قصد داریم مثال قسمت قبل را که کار نمایش لیستی از فیلم‌ها را انجام می‌دهد، تبدیل به یک کامپوننت گرید کنیم که دارای امکانات صفحه بندی، فیلتر کردن و مرتب سازی اطلاعات است.


بررسی ساختار کامپوننت Pagination

شبیه به کامپوننت Like که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت جدید Pagination نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\pagination.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import Pagination from "./common/pagination";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و پس از بسته شدن tag المان جدول، قصد داریم این کامپوننت صفحه بندی را نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
/>
این کامپوننت نیاز به تعداد کل آیتم‌های فیلم‌ها را دارد؛ به علاوه‌ی اندازه‌ی صفحه، یا همان تعداد ردیفی که قرار است در هر صفحه نمایش داده شود. به این ترتیب کامپوننت صفحه بندی می‌تواند تعداد صفحات مورد نیاز را محاسبه کرده و سپس آن‌ها را رندر کند.
  state = {
    movies: getMovies(),
    pageSize: 4
  };

به همین جهت دو ویژگی itemsCount و pageSize را پیش از هرکاری به تعریف المان صفحه بندی اضافه کرده‌ایم تا داده‌های ورودی آن مشخص شوند. ویژگی itemsCount از تعداد اعضای آرایه‌ی movies حاصل می‌شود و pageSize را به عنوان یک خاصیت جدید شیء منتسب به state تعریف و با عدد 4 مقدار دهی کرده‌ایم.
همچنین در لیست صفحاتی که توسط این کامپوننت رندر می‌شود، باید با کلیک بر روی هر کدام، اطلاعات آن صفحه رندر شود. به همین جهت در اینجا ویژگی onPageChange تعریف شده و به متد جدید handlePageChange در کامپوننت movies، متصل گردیده تا عدد page را دریافت کرده و به آن واکنش نشان دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
  };
تا اینجا اینترفیس کامپوننت صفحه بندی را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی‌ها و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


نمایش شماره صفحات گرید، در کامپوننت صفحه بندی

برای رندر کامپوننت صفحه بندی، از کلاس‌های مخصوص اینکار که در بوت استرپ تعریف شده‌اند، استفاده می‌کنیم که ساختار کلی آن به صورت زیر است و از یک المان nav که داخل آن ul ای با کلاس pagination و liهایی با کلاس page-item هستند، تشکیل می‌شود. هر li، به همراه یک anchor است؛ با کلاس page-link تا لینک به صفحه‌ای خاص را ارائه دهد که در اینجا بجای لینک، از کلیک بر روی آن‌ها استفاده خواهیم کرد:
import React, { Component } from "react";

class Pagination extends Component {
  render() {
    return (
      <nav>
        <ul className="pagination">
          <li className="page-item">
            <a className="page-link">1</a>
          </li>
        </ul>
      </nav>
    );
  }
}

export default Pagination;

تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، عدد 1 را در پایین جدول فیلم‌ها مشاهده خواهید کرد:


اکنون باید رندر این liها را بر اساس ورودی‌های این کامپوننت که پیشتر معرفی کردیم، یعنی pageSize و itemsCount، پویا کنیم. به همین جهت نیاز به آرایه‌ای داریم که بر اساس این ورودی‌ها، شماره‌ی صفحات مانند [1,2,3] را ارائه دهد تا بر روی آن متد Array.map را فراخوانی کرده و liهای مورد نیاز را به صورت پویا رندر کنیم:
class Pagination extends Component {
  // ...
  getPageNumbersArray() {
    const { itemsCount, pageSize } = this.props;
    const pagesCount = Math.ceil(itemsCount / pageSize);
    if (pagesCount === 1) {
      return null;
    }

    const pages = new Array();
    for (let i = 1; i <= pagesCount; i++) {
      pages.push(i);
    }
    return pages;
  }
}
در اینجا بر اساس ورودی‌ها، تعداد صفحات محاسبه شده و سپس بر اساس آن‌ها آرایه‌ای از این شماره صفحه‌ها تشکیل و بازگشت داده می‌شود. همچنین اگر تعداد صفحات 1 بود، می‌خواهیم این کامپوننت چیزی را رندر نکند. به همین جهت در اینجا null بازگشت داده شده‌است. دلیل استفاده‌ی از Math.ceil که کوچکترین عدد صحیح بزرگتر یا مساوی خروجی را بازگشت می‌دهد، نیز همین مورد است. توسط این متد، خروجی float دریافتی به integer تبدیل شده و سپس قابلیت مقایسه‌ی با 1 را پیدا می‌کند. برای مثال اگر تعداد ردیف‌های صفحه را به 10 تنظیم کنیم، خروجی این تقسیم در این مثال، 0.9 خواهد بود که شرط بررسی pagesCount === 1 را برآورده نمی‌کند. به همین جهت توسط متد Math.ceil، این خروجی به عدد 1 تقریب زده شده و سبب بازگشت نال از این متد خواهد شد.
سپس به کمک متد map، اعضای این آرایه را تبدیل به لیست liهای نمایش شماره صفحات می‌کنیم. در اینجا key هر li را نیز به شماره صفحه که منحصربفرد است، تنظیم کرده‌ایم:
class Pagination extends Component {
  render() {
    const pages = this.getPageNumbersArray();
    if (!pages) {
      return null;
    }

    return (
      <nav>
        <ul className="pagination">
          {pages.map(page => (
            <li key={page} className="page-item">
              <a className="page-link">{page}</a>
            </li>
          ))}
        </ul>
      </nav>
    );
  }

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


با داشتن 9 فیلم در آرایه‌ی movies و نمایش 4 فیلم به ازای هر صفحه، به 3 صفحه خواهیم رسید که به درستی در اینجا رندر شده‌است. یکبار هم برای آزمایش بیشتر، مقدار pageSize را در کامپوننت movies به 10 تنظیم کنید. در این حالت کامپوننت صفحه بندی نباید رندر شود.


مدیریت انتخاب شماره‌های صفحات

در این قسمت می‌خواهیم مدیریت onPageChange={this.handlePageChange} را که به تعریف المان صفحه بندی در کامپوننت movies اضافه کردیم، تکمیل کنیم. برای این منظور در کامپوننت صفحه بندی، قسمت anchor را به صورت زیر تغییر می‌دهیم تا با کلیک بر روی آن، رخداد onPageChange صادر شود:
<a
   onClick={() => this.props.onPageChange(page)}
   className="page-link"
   style={{ cursor: "pointer" }}
>
   {page}
</a>

تا اینجا اگر برنامه را آزمایش کنیم، با کلیک بر روی لینک‌های شماره صفحات، شماره صفحه‌ی انتخابی، در کنسول توسعه دهندگان مرورگر لاگ می‌شود.

اکنون می‌خواهیم اگر صفحه‌ای انتخاب شد، شماره‌ی آن صفحه با رنگی دیگر نمایش داده شود. در بوت استرپ برای اینکار تنها کافی است کلاس active را به className هر li اضافه کنیم و برعکس. یعنی اگر page ای مساوی صفحه‌ی جاری انتخاب شده بود (currentPage در اینجا)، آنگاه کلاس page-item active، به المان li اضافه شود. بنابراین در این کامپوننت نیاز است عدد currentPage را نیز دریافت کنیم. به همین جهت ویژگی currentPage را به تعریف المان Pagination در کامپوننت movies اضافه می‌کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
  currentPage={this.state.currentPage}
/>
این ویژگی نیز مقدار خودش را از state به روز شده دریافت می‌کند:
class Movies extends Component {
  state = {
    movies: getMovies(),
    pageSize: 4,
    currentPage: 1
  };
به روز رسانی state نیز در متد handlePageChange که به ویژگی onPageChange متصل است، بر اساس page دریافتی از کامپوننت صفحه بندی، رخ می‌دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
    this.setState({currentPage: page});
  };
بنابراین هرگاه که بر روی یک شماره صفحه در کامپوننت صفحه بندی کلیک می‌شود، رخ‌داد onPageChange متصل به تعریف المان Pagination درج شده‌ی در کامپوننت movies، روی داده و به همراه آن شماره صفحه‌ای، به متد رخ‌دادگران متصل به آن در کامپوننت movies که در اینجا handlePageChange نام دارد، ارسال می‌شود. در این متد state کامپوننت به روز شده و این امر سبب فراخوانی مجدد متد رندر می‌شود که در انتهای آن کامپوننت Pagination درج شده‌است. بنابراین به روز رسانی state، سبب رندر مجدد کامپوننت صفحه بندی با currentPage جدیدی که به آن رسیده‌است، خواهد شد.

پس از این تغییرات، به کامپوننت صفحه بندی مراجعه کرده و بر اساس currentPage دریافتی، کلاس active را به المان li اعمال می‌کنیم:
<li
  key={page}
  className={
    page === this.props.currentPage
    ? "page-item active"
    : "page-item"
  }
>
پس از اعمال این تغییرات، اکنون برنامه در مرورگر به صورت زیر به نظر می‌رسد:


در اولین بار نمایش برنامه، عدد 1 در حالت انتخاب شده قرار دارد؛ چون مقدار currentPage موجود در state، همان عدد 1 است. پس از آن با کلیک بر روی اعداد دیگر، با به روز رسانی state، مقدار currentPage تغییر کرده و کامپوننت صفحه بندی نسبت به آن واکنش نشان می‌دهد.


نمایش صفحه بندی شده‌ی اطلاعات

تا اینجا لیستی که نمایش داده می‌شود، حاوی تمام اطلاعات آرایه‌ی this.state.movies است و بر اساس شماره‌ی صفحه‌ی انتخابی، تغییر نمی‌کند. به همین جهت با استفاده از متد slice، تکه‌ای از آرایه‌ی movies را که بر اساس شماره صفحه‌ی انتخابی و تعداد ردیف‌ها در هر صفحه نیاز است نمایش داده شود، انتخاب کرده و بازگشت می‌دهیم:
  paginate() {
    const first = (this.state.currentPage - 1) * this.state.pageSize;
    const last = first + this.state.pageSize;
    return this.state.movies.slice(first, last);
  }
اکنون در متد رندر کامپوننت movies، بجای کار با کل آرایه‌ی this.state.movies، آرایه‌ی جدید slice شده را توسط متد paginate دریافت کرده:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const movies = this.paginate();
و سپس در همین متد رندر، فراخوانی this.state.movies.map را به movies.map تغییر می‌دهیم، تا تنها لیست مرتبط با هر صفحه‌ی انتخابی نمایش داده شود.

پس از ذخیره‌ی تغییرات و بارگذاری مجدد برنامه، اکنون می‌توان نمایش صفحه بندی شده‌ی اطلاعات را شاهد بود:



بررسی صحت نوع پارامترهای ارسالی به کامپوننت‌ها

تا اینجا فرض بر این است که مصرف کننده‌ی کامپوننت صفحه بندی، دقیقا همان ویژگی‌هایی را که ما طراحی کرده‌ایم، با همان نام‌ها و همان نوع‌ها را حتما به آن ارسال می‌کند. همچنین اگر افزونه‌ی eslint را هم در VSCode نصب کرده باشید، به همراه نصب وابستگی‌های زیر در خط فرمان:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha

 به ازای هر خاصیت props استفاده شده‌ی در کامپوننت صفحه بندی، اخطاری را مانند «'currentPage' is missing in props validation eslint(react/prop-types)» مشاهده خواهید کرد:



که عنوان می‌کند props validation این خاصیت استفاده شده، فراموش شده‌است.
در نگارش‌های قبلی React، امکانات بررسی نوع‌های ارسالی به کامپوننت‌ها، جزئی از بسته‌ی اصلی آن بود؛ اما از نگارش 15 به بعد، به بسته‌ی مستقلی منتقل شده‌است که باید به صورت جداگانه‌ای در ریشه‌ی پروژه نصب شود:
> npm i prop-types --save

البته اگر TypeScript را بر روی سیستم خود نصب کرده باشید، دیگر نیازی به نصب بسته‌ی npm فوق را ندارید و prop-types، جزئی از آن است که عموما در یک چنین مسیری قرار دارد و برای کار کردن با آن، تنها ذکر import مرتبط با PropType در ماژول‌های برنامه کافی بوده و برنامه در این حالت بدون مشکل کامپایل می‌شود:
 C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index

اکنون در ابتدای فایل کامپوننت صفحه بندی، تعریف زیر را اضافه می‌کنیم:
 import PropTypes from "prop-types";
سپس در انتهای این فایل، اعتبارسنجی props آن‌را تعریف خواهیم کرد:
Pagination.propTypes = {
  itemsCount: PropTypes.number.isRequired,
  pageSize: PropTypes.number.isRequired,
  currentPage: PropTypes.number.isRequired,
  onPageChange: PropTypes.func.isRequired
};

export default Pagination;
همانطور که مشاهده می‌کنید، در اینجا خاصیت جدید propTypes (دقیقا با همین نگارش؛ در غیراینصورت بررسی نوع‌ها کار نخواهد کرد)، به تعریف کلاس Pagination اضافه شده‌است (پس از تعریف کلاس کامپوننت به صورت مستقل اضافه می‌شود).
سپس مقدار این خاصیت جدید را به شیءای تنظیم می‌کنیم که نام خواص آن، دقیقا همان نام خواص و رویدادهای props استفاده شده‌ی در این کامپوننت است. در ادامه توسط PropTypes ای که در ابتدای ماژول import می‌شود، کار تعریف نوع این خواص و اجباری بودن آن‌ها را می‌توان مشخص کرد که برای مثال در اینجا سه خاصیت تعریف شده از نوع عددی و اجباری بوده و onPageChange، از نوع func است.
پس از اضافه کردن Pagination.propTypes و مقدار دهی آن، خطاهای eslint ای که در تصویر فوق مشاهده کردید، برطرف می‌شوند. همچنین اگر فراخوان کامپوننت Pagination مثلا بجای itemsCount یک رشته‌ی فرضی را وارد کند (برای آزمایش آن در کامپوننت movies، در تعریف المان Pagination، مقدار itemsCount را یک رشته وارد کنید)، چنین خطایی در مرورگر ظاهر خواهد شد که عنوان می‌کند itemsCount یک عدد را می‌پذیرد و نوع ورودی آن اشتباه است:

البته این خطا فقط در حالت development مشاهده می‌شود و در حالت توزیع برنامه، خیر.

بنابراین تعریف propTypes یک best practice ایجاد کامپوننت‌های React است که نه فقط بررسی نوع‌ها را فعال می‌کند، بلکه می‌تواند به عنوان مستندات آن نیز در جهت تعیین props مورد نیاز، همچنین نوع و اجباری بودن آن‌ها، اطلاعات کاملی را ارائه کند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-11.zip
مطالب
اندکی به روز رسانی

لیست وبلاگ‌های آی تی ایرانی را به روز کردم که فایل OPML آن از اینجا و همچنین از طریق لینک آن در منوی سمت راست سایت قابل دریافت است.
این لیست بر اساس یافته‌های من از سایت‌های به اشتراک گذاری لینک‌های ایرانی است مانند : idevcenter.com و mohand.es . اگر آدرس وبلاگ شما در این لیست قرار ندارد لطفا یکبار در یکی از این دو سایت آدرس مطالب خود را منتشر کنید. با تشکر


مطالب
شروع به کار با بوت استرپ 4
روش‌های مختلف دریافت و نصب بوت استرپ 4

اولین کاری را که باید جهت شروع به کار با بوت استرپ 4 انجام داد، نصب و افزودن آن به صفحه‌ی HTML جاری است. روش‌های زیادی برای انجام اینکار وجود دارند:
الف) استفاده از نگارش SASS آن
بوت استرپ 4 در اصل مبتنی بر SASS توسعه یافته‌است و فایل‌های آن با فرمت scss. ارائه می‌شوند. مزیت کار با این روش، امکان سفارشی سازی بوت استرپ 4 و یا مشارکت در پروژه‌ی آن است و بدیهی است پس از آن باید SASS را به CSS کامپایل و مورد استفاده قرار داد.

ب) استفاده از CDN و یا Content delivery network
- مزیت آن بالا رفتن سرعت سایت با کش شدن آن در شبکه و یا شبکه‌های توزیع محتوا است.
- اما این روش محدودیت و الزام کار آنلاین با فایل‌های بوت استرپ را نیز به همراه دارد.
برای کار با CDN‌های بوت استرپ، مطابق راهنمای آن، تنها کافی است مدخل فایل css آن‌را به head صفحه و مداخل فایل‌های js ذیل را پیش از بسته شدن تگ body قرار دهید:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
در اینجا شاید نام دو فایل، برای شما تازگی داشته باشند:
- jquery-3.3.1.slim : در اینجا slim یک نگارش بسیار کوچک از jQuery می‌باشد که بوت استرپ 4 بر مبنای آن کار می‌کند. البته در یک پروژه‌ی واقعی احتمالا نیاز به نگارش کامل آن‌را خواهید داشت و یا اگر قصد حذف کردن جی‌کوئری را دارید، این نگارش، کم‌حجم‌ترین آن است.
- popper.min.js : برای نمونه Bootstrap dropdown برای کارکرد صحیح آن در نگارش 4، نیاز به این وابستگی جدید را دارد.

ج) استفاده از فایل‌های از پیش پردازش شده‌
فایل‌های از پیش آماده شده‌ی آن‌را می‌توان مستقیما از سایت بوت استرپ، با کلیک بر روی دکمه‌ی download واقع در منوی راهبری سایت آن، دریافت کرد. مزیت این روش، امکان کار و توسعه‌ی آفلاین صفحات مبتنی بر بوت استرپ است.
مشکل این روش عدم اطلاع رسانی خودکار از ارائه‌ی نگارش‌های جدید و نیاز به دریافت دستی مجدد این بسته، به ازای هر نگارش جدید آن می‌باشد.

د) استفاده از ابزارهای مدیریت بسته‌ها
روشی را که ما در اینجا از آن استفاده خواهیم کرد، دریافت و نصب وابستگی‌های مورد نیاز جهت کار با بوت استرپ 4، توسط npm است. به همین جهت یک فایل جدید package.json را با محتوای ذیل ایجاد کنید:
{
  "name": "bootstrap.4",
  "version": "1.0.0",
  "description": "client side resources of the project",
  "scripts": {},
  "author": "VahidN",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^4.1.3",
    "components-font-awesome": "5.0.6",
    "jquery": "^3.3.1",
    "popper.js": "^1.14.4"
  }
}
سپس از طریق خط فرمان به این پوشه وارد شده و دستور npm install را جهت دریافت این وابستگی‌ها صادر کنید. یکی از مزیت‌های مهم این روش، آگاه شدن خودکار از به روز رسانی‌های وابستگی‌های فوق، توسط افزونه‌هایی مانند Version Lens است.
در اینجا font-awesome را نیز مشاهده می‌کنید؛ از این جهت که بوت استرپ 4 برخلاف نگارش 3 آن، به همراه گلیف آیکن‌های پیش‌فرض آن نیست.


ایجاد قالب ابتدایی شروع به کار با بوت استرپ 4

پس از دریافت وابستگی‌های مورد نیاز جهت شروع به کار با بوت استرپ 4 که هم اکنون باید در پوشه‌ی node_modules واقع در ریشه‌ی پوشه‌ی جاری موجود باشند، در ادامه حداقل قالبی را که برای کار با آن نیاز است، مرور می‌کنیم:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="/node_modules/components-font-awesome/css/fa-solid.min.css">
    <link rel="stylesheet" href="/node_modules/components-font-awesome/css/fontawesome.min.css">
    <title>Bootstrap</title>
</head>
<body>
    <div class="container">

    </div>
    <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    <script src="/node_modules/popper.js/dist/umd/popper.min.js"></script>
    <script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
</body>
</html>
- در ابتدا سازگاری با edge و الزام به استفاده‌ی از آخرین نگارش IE نصب شده مشخص شده‌است.
- سپس viewport استاندارد، جهت تعیین اینکه این صفحه با ابزارهای موبایل نیز سازگار است، تعریف شده‌است.
- در قسمت head، مدخل فایل bootstrap.min.css تعریف شده‌است. همچنین مداخل مورد نیاز جهت کار با font-awesome را نیز مشاهده می‌کنید.
- پیش از بسته شدن تگ body، تعاریف jQuery، کتابخانه‌ی popper و سپس bootstrap.min.js قید شده‌اند. کتابخانه‌ی popper از مسیر umd آن دریافت شده‌است تا همه جا کار کند.


نکته‌ی مهم!
در نگارش نهایی برنامه‌ی شما، مسیرهای فایل‌های شروع شده‌ی با /node_modules/ نباید وجود داشته باشند. این فایل‌ها را بهتر است توسط ابزارهای bundling & minification یکی و سپس به صفحه اضافه کنید.


غنی سازی ویرایشگر VSCode برای کار ساده‌تر با بوت استرپ

VSCode یک ویرایشگر حرفه‌ای چندسکویی است که برای ویندوز، مک و لینوکس تهیه شده‌است. این ویرایشگر را می‌توان توسط افزونه‌های زیر برای کار ساده‌تر با بوت استرپ غنی کرد:
Bootstrap 4, Font awesome 4, Font Awesome 5 Free & Pro snippets: ساده سازی تشکیل تگ‌های بوت استرپ
Path Autocomplete: کار وارد کردن مسیر فایل‌ها و تصاویر را ساده می‌کند.
HTML CSS Support: کار آن غنی سازی intellisense این ویرایشگر جهت کار با ویژگی‌ها و همچنین کلاس‌های CSS است.
IntelliSense for CSS class names in HTML: انتخاب کلاس‌های CSS بوت استرپ را ساده‌تر می‌کند.
Live Server: کار آن راه اندازی یک وب سرور آزمایشی و سپس امکان مشاهده‌ی آنی تغییرات در برنامه و فایل HTML جاری، در مرورگر می‌باشد.


برای کار با آن، در حالیکه صفحه‌ی HTML جاری در VSCode باز است، بر روی دکمه‌ی Go Live اضافه شده‌ی در status bar آن کلیک کنید. پس از آن، یک وب سرور آزمایشی را بر روی پورت 5500 آغاز کرده و صفحه‌ی جاری را در آدرس http://127.0.0.1:5500/index.html در مرورگر پیش‌فرض سیستم نمایش می‌دهد. اکنون فایل HTML خود را در VSCode ویرایش کنید. ملاحظه خواهید کرد که بلافاصله این تغییرات در مرورگر قابل مشاهده هستند.


نگارش‌های راست به چپ بوت استرپ 4

قرار است بوت استرپ 4 نگارش رسمی راست به چپ نیز داشته باشد. به همین منظور می‌توانید در اینجا رای خود را اظهار کنید.
همچنین پروژه‌های زیر نیز چنین قابلیتی را ارائه می‌دهند:
  1. MahdiMajidzadeh/bootstrap-v4-rtl
  2. DediData/Bootstrap-RTL 
  3. GhalamborM/bootstrap4-rtl



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_01.zip
نظرات مطالب
آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت سوم
ممنونم.
البته که مقالات رو مطالعه کرده بودم.
 شاید استفاده از یک فایل تیبل برای اینگونه سناریو‌ها خیلی مفید باشد. به شکلی که یک فایل تیبل ایجاد میشود و  از آن صرفا به عنوان مخزنی که تمام فایل‌ها در آنجا قرار بگیرند، استفاده میشود. بعد از آن میشود خواندن و نوشتن در این پوشه رو با API‌های مرتبط با win32 انجام داد. در این صورت هم اگر معماری پروژه به گونه ای باشد که بخش ادمین و API هم از پروژه اصلی بیرون کشیده شده و جدا هاست خواهند شد، به صورت مشترک میتوانند به این فایل‌ها دسترسی داشته باشند.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت دوم - بررسی توابع Redux
همانطور که در مقدمه‌ی قسمت قبل نیز عنوان شد، در این سری ابتدا کتابخانه‌ی Redux را به صورت مجزایی از React بررسی می‌کنیم؛ چون در اصل، یک کتابخانه‌ی مدیریت حالت عمومی است و وابستگی خاصی را به React ندارد و در بسیاری از برنامه و فریم‌ورک‌های دیگر نیز قابل استفاده‌است.


ایجاد یک برنامه‌ی خالی React برای آزمایش توابع Redux

در اینجا برای بررسی توابع Redux، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> npm i -g create-react-app
> create-react-app state-management-redux-mobx
> cd state-management-redux-mobx
> npm start
در ادامه کتابخانه‌ی Redux را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save redux
البته برای کار با Redux، الزاما نیازی به طی مراحل فوق نیست؛ ولی چون این قالب، یک محیط آماده‌ی کار با ES6 را فراهم می‌کند، به سادگی می‌توان فایل index.js آن‌را خالی کرد و سپس شروع به کدنویسی و آزمایش Redux نمود.


معرفی سریع توابع Redux

Redux، کتابخانه‌ی کوچکی است و تنها از 5 تابع تشکیل شده‌است:
applyMiddleware: function()
bindActionCreators: function()
combineReducers: function()
compose: function()
createStore: function()
و سه مورد از آن‌ها بیشتر کمکی هستند. برای مثال تابع compose مانند متد flow و یا pipe در کتابخانه‌ی lodash است و اصلا به Redux مرتبط نیست. تابع combineReducers، اشیاء موجود در state را با هم ترکیب می‌کند. bindActionCreators نیز یک تابع کمکی است جهت ایجاد ساده‌تر ActionCreators. بنابراین کتابخانه‌ی Redux، آنچنان گسترده نیست.


بررسی تابع compose با یک مثال

پس از ایجاد پروژه‌ی React و افزودن کتابخانه‌ی Redux به آن، به فایل src\index.js این پروژه مراجعه کرده و محتویات آن‌را با قطعه کد ذیل، تعویض می‌کنیم:
import { compose } from "redux";

const makeLouder = text => text.toUpperCase();
const repeatThreeTimes = text => text.repeat(3);
const embolden = text => text.bold();

const makeLouderAndRepeatThreeTimesAndEmbolden = compose(
  embolden,
  repeatThreeTimes,
  makeLouder
);
console.log(makeLouderAndRepeatThreeTimesAndEmbolden("Hello"));
- در ابتدای این ماژول، تابع compose را از کتابخانه‌ی redux دریافت کرده‌ایم.
- سپس سه تابع ساده را برای ضخیم کردن، تکرار و با حروف بزرگ نمایش دادن یک متن ورودی، تعریف کرده‌ایم.
- اکنون با استفاده از متد compose کتابخانه‌ی redux، این سه متد را به صورت ترکیبی، بر روی متن ورودی Hello، اعمال کرده‌ایم.
- در آخر اگر برای مشاهده‌ی نتیجه‌ی اجرای console.log بر روی آن، به کنسول توسعه دهندگان مرورگر مراجعه کنیم، به خروجی زیر خواهیم رسید:
 <b>HELLOHELLOHELLO</b>
همانطور که مشاهده می‌کنید، متن Hello را سه بار با حروف بزرگ تکرار نموده و در نهایت آن‌را با تک b محصور کرده‌است.


بررسی تابع createStore با یک مثال

Store در Redux، جائی است که در آن state برنامه ذخیره می‌شود. تابع createStore که کار ایجاد store را انجام می‌دهد، یک پارامتر را دریافت می‌کند و آن‌هم تابع reducer است که در قسمت قبل معرفی شد و در ساده‌ترین حالت آن، به نحو زیر با یک متد خالی، قابل فراخوانی است:
import { createStore } from "redux";

createStore(() => {});
تابع reducer دو پارامتر را دریافت می‌کند: وضعیت فعلی برنامه (state در اینجا) و رخ‌دادی که واقع شده‌است (action در اینجا). خروجی آن نیز یک state جدید بر این اساس است:
import { createStore } from "redux";

const reducer = (state, action) => {
  return state;
};
const store = createStore(reducer);
console.log(store);
در این مثال، تابع reducer را طوری تعریف کرده‌ایم که بر اساس هر نوع رخ‌دادی که به آن برسد، همان وضعیت قبلی را بازگشت دهد. سپس بر اساس آن یک store را ایجاد کرده‌ایم. اگر به خروجی console.log بررسی محتوای این شیء دقت کنیم، به صورت زیر خواهد بود:


در شیء store، چهار متد dispatch, subscribe, getState, replaceReducer مشخص هستند:
- کارکرد متد replaceReducer مشخص است؛ یک reducer جدید را به آن می‌دهیم و reducer قبلی را جایگزین می‌کند.
- متد dispatch آن مرتبط است به مبحث dispatch actions که در قسمت قبل به آن پرداختیم. هدف آن تغییر state، بر اساس یک action رسیده‌است.
- متد getState، وضعیت فعلی state را باز می‌گرداند.
- متد subscribe با هر تغییری در state، سبب بروز رخ‌دادی می‌شود. یکی از کاربردهای آن می‌تواند به روز رسانی UI، بر اساس تغییرات state باشد. برای مثال کتابخانه‌ی دیگری به نام react redux، دقیقا همین کار را به کمک متد subscribe، انجام می‌دهد. در این قسمت، هدف ما بررسی پشت صحنه‌ی کتابخانه‌هایی مانند react redux است که چه متدهایی را محصور کرده‌اند و دقیقا چگونه کار می‌کنند.

در مثال زیر، مقدار ابتدایی پارامتر state متد reducer را به یک شیء که دارای خاصیت value و مقدار 1 است، تنظیم کرده‌ایم:
import { createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  return state;
};
const store = createStore(reducer);
console.log(store.getState());
سپس بر این اساس، store را ایجاد کرده و متد getState آن‌را فراخوانی کرده‌ایم. خروجی آن به صورت زیر است، که معادل وضعیت فعلی state در store می‌باشد:


در کامپوننت‌های React، این نوع خواص به صورت props ارسال می‌شوند. کل state در Redux ذخیره شده و سپس قابل دسترسی و خواندن خواهد شد.


بررسی متد dispatch یک store با مثال

در اینجا مثالی را از کاربرد متد dispatch ملاحظه می‌کنید که یک شیء را می‌پذیرد:
import { compose, createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  console.log("Something happened!", action);
  return state;
};
const store = createStore(reducer);
console.log(store.getState());
store.dispatch({ type: "ADD" });
متد reducer با یک state ابتدایی تنظیم شده‌ی به شیء { value: 1 } تعریف شده و سپس با ارسال آن به createStore و ایجاد store، اکنون می‌توانیم رخ‌دادهایی را به آن dispatch کنیم.
فرمت شیءای که به متد dispatch ارسال می‌شود، حتما باید به همراه خاصیت type باشد؛ در غیر اینصورت با صدور استثنائی، این نکته را گوشزد می‌کند. مقدار آن نیز یک رشته‌است که بیانگر وقوع رخدادی در برنامه می‌باشد؛ برای مثال کاربر درخواست دریافت اطلاعاتی را کرده‌است و یا کاربر از سیستم خارج شده‌است و امثال آن.

خروجی قطعه کد فوق، به صورت زیر است:


با اجرای برنامه، یک رخ‌داد درونی از نوع redux/INITo.2.g.b.y.i@@ صادر شده‌است که هدف آن آغاز و مقدار دهی اولیه‌ی store است. پس از آن زمانیکه متد store.dispatch فراخوانی شده‌است، مجددا console.log داخل متد reducer فراخوانی شده‌است که اینبار به همراه type ای است که ما مشخص کرده‌ایم.

در حالت کلی، اینکه شیء ارسالی توسط dispatch را چگونه طراحی می‌کنید، اختیاری است؛ اما الگوی پیشنهادی در این زمینه، redux standard actions نام دارد و به صورت زیر است که هدف از آن، یک‌دست شدن طراحی و پیاده سازی برنامه است:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
- نوع action باید همواره قید شود و عموما رشته‌ای است.
- سپس به خاصیت payload، تمام داده‌های مرتبط با آن اکشن، مانند نتیجه‌ی بازگشتی از یک API، به صورت یک شیء، انتساب داده می‌شود.
- خاصیت meta، مرتبط با متادیتا و اطلاعات اضافی است که عموما استفاده نمی‌شود.

اکنون که طراحی شیء ارسالی به پارامتر action متد reducer مشخص شد، می‌توان بر اساس خاصیت type آن، یک switch را طراحی کرد و نسبت به نوع‌های مختلف رسیده، واکنش نشان داد:
import { createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  console.log("Something happened!", action);

  if (action.type === "ADD") {
    const value = state.value;
    const amount = action.payload.amount;
    state.value = value + amount;
  }

  return state;
};
const store = createStore(reducer);
const firstState = store.getState();
console.log(firstState);
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
const secondState = store.getState();
console.log(secondState);
console.log("secondState === firstState", secondState === firstState);
در اینجا اکشن از نوع ADD، با یک payload با محتوای شیءای که دارای خاصیت amount با مقدار 2 است، به متد reducer مربوط به store، ارسال می‌شود. سپس در متد reducer، بر اساس مقدار action.type، تصمیم‌گیری خواهد شد. اگر این مقدار مساوی اکشن خاص ADD بود، آنگاه مقدار value شیء state را با مقدار amount جمع زده و به state.value انتساب می‌دهیم. اکنون پس از فراخوانی متد store.dispatch، اگر خروجی متد ()store.getState را بررسی کنیم، به صورت {value: 3} در آمده‌است ... و این کاری است که نباید انجام شود! نباید مقدار شیء state را به صورت مستقیم تغییر داد. چون در آخرین بررسی صورت گرفته که کار مقایسه‌ی بین state پیش و پس از فراخوانی store.dispatch است (secondState === firstState)، حاصل true است. یعنی اگر با این روش با React کار کنیم، نمی‌تواند محاسبه کند که چه چیزی در state تغییر کرده‌است، تا بر اساس آن UI را به روز رسانی کند.

یک روش حل این مشکل، حذف سطر state.value = value + amount و جایگزینی آن با یک return است که شیء state جدیدی را بازگشت می‌دهد:
return {
  value: value + amount
};
اکنون نتیجه‌ی مقایسه‌ی secondState === firstState دیگر true نخواهد بود.


بررسی متد subscribe یک store با مثال

در ادامه به store همان مثال فوق، متد subscribe را نیز اضافه می‌کنیم و سپس دوبار، کار store.dispatch را انجام خواهیم داد:
const store = createStore(reducer);

const unsubscribe = store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });

unsubscribe();
- هر بار که متد store.dispatch فراخوانی می‌شود، یکبار callback function ارسالی به متد store.subscribe، فراخوانی خواهد شد که در اینجا کار نمایش مقدار شیء state را به عهده دارد. در یک چنین حالتی مشترکین به store، متوجه خواهند شد که احتمالا state جدیدی توسط متد reducer بازگشت داده شده‌است و سپس بر اساس آن برای مثال تصمیم خواهند گرفت تا UI را به روز رسانی کنند.
- خروجی مستقیم متد store.subscribe نیز یک متد است که unsubscribe نام دارد و می‌توان در پایان کار، برای جلوگیری از نشتی‌های حافظه، آن‌را فراخوانی کرد.

خروجی حاصل از console.log منتسب به متد store.subscribe، با دوبار فراخوانی متد store.dispatch پس از آن، به صورت زیر است:
{value: 3}
{value: 5}


بررسی تابع combineReducers با یک مثال

اگر قرار باشد در متد reducer، صدها if action.type را تعریف کرد ... پس از مدتی از کنترل خارج می‌شود و غیرقابل نگهداری خواهد شد. تابع combineReducers به همین جهت طراحی شده‌است تا چندین متد مجزای reducer را با هم ترکیب کند. بنابراین می‌توان در برنامه صدها متد کوچک reducer را که قابلیت توسعه و نگهداری بالایی را دارند، پیاده سازی کرد و سپس با استفاده از تابع combineReducers، آن‌ها را یکی کرده و به متد createStore ارسال کرد. نکته‌ی مهم اینجا است که هرچند اینبار می‌توان تعداد زیادی reducer را طراحی کرد، اما توسط تابع combineReducers، مقدار action ارسالی، از تمام این reducerها عبور داده خواهد شد. به این ترتیب اگر نیاز بود می‌توان به یک action، در چندین متد مختلف reducer گوش فرا داد و بر اساس آن تصمیم گیری کرد. بنابراین بهتر است هر reducer تعریف شده در صورتیکه action.type آن با action رسیده تطابق نداشته باشد، همان state قبلی را بازگشت دهد تا بتوان زنجیره‌ی reducerها را تشکیل داد و بهتر مدیریت کرد.
برای نمونه اگر متد reducer فعلی را به calculatorReducer تغییر نام دهیم، روش معرفی آن توسط متد combineReducers به متد createStore به صورت زیر است:
import { combineReducers, createStore } from "redux";
  // ...

const calculatorReducer = (state = { value: 1 }, action) => {
  // ...
};
const store = createStore(
  combineReducers({
    calculator: calculatorReducer
  })
);
به شیء ارسالی به متد combineReducers، هر reducer موجود به صورت یک خاصیت جدید اضافه می‌شود.


بررسی تابع bindActionCreators با یک مثال

فرض کنید می‌خواهیم متد dispatch را چندین بار فراخوانی کنیم:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
بجای اینکه شیء ارسالی به آن‌را به این صورت ایجاد کنیم، می‌توان یک تابع را برای آن ایجاد کرد تا مقدار amount را گرفته و شیء action مدنظر را بازگشت دهد:
const createAddAction = amount => {
  return {
    type: "ADD",
    payload: {
      amount // = amount: amount
    },
    meta: {}
  };
};
و سپس می‌توان آن‌را به صورت زیر مورد استفاده قرار داد:
store.dispatch(createAddAction(2));
store.dispatch(createAddAction(2));
و یا توسط تابع bindActionCreators، می‌توان فراخوانی فوق را به صورت زیر نیز انجام داد و نتیجه یکی است:
import { bindActionCreators, combineReducers, compose, createStore } from "redux";

// ...
const dispatchAdd = bindActionCreators(createAddAction, store.dispatch);
dispatchAdd(2);
dispatchAdd(2);
همانطور که ملاحظه می‌کنید، bindActionCreators فقط یک تابع کمکی است تا کار dispatch action را ساده کند.


میان‌افزارها (Middlewares) در Redux

پس از اینکه یک اکشن، به سمت reducer ارسال شد و پیش از رسیدن آن به reducer، می‌توان کدهای دیگری را نیز اجرا کرد. برای مثال چون این توابع خالص هستند، نمی‌توان اعمالی را داخل آن‌ها اجرا کرد که به همراه اثرات جانبی مانند کار با یک API خارجی باشند. با استفاده از میان‌افزارها در این بین می‌توان کدهایی را که با دنیای خارج تعامل دارند نیز اجرا کرد.
یک میان‌افزار در Redux، همانند سایر قسمت‌های آن، تنها یک تابع ساده‌ی جاوا اسکریپتی است:
const logger = ({ getState }) => {
  return next => action => {
    console.log(
      'MIDDLEWARE',
      getState(),
      action
    );
    const value = next(action);
    console.log({value});
    return value;
  }
}
این تابع میان‌افزار، امکان دریافت وضعیت فعلی store را دارد و در نهایت یک تابع دیگر را باز می‌گرداند. داخل این تابع بازگشت داده شده می‌توان اعمال دارای اثرات جانبی را نیز اجرا کرد؛ مانند فراخوانی console.log و یا صدور یک درخواست Ajax ای. یکی دیگر از کاربردهای میان‌افزارها، انجام کارهای آماری بر روی اکشن‌های ارسالی است. بجای اینکه کدهای متناظر را داخل  reducerها قرار داد، می‌توان به رسیدن اکشن‌های خاصی در این بین گوش فرا داد و برای مثال آن‌ها را لاگ کرد و یا آماری را از آن‌ها تهیه نمود.
در پایان کار میان‌افزار باید متد next آن‌را فراخوانی کرد تا بتوان میان‌افزارهای متعددی را زنجیروار اجرا کرد.
در آخر برای معرفی آن به یک store می‌توان از تابع applyMiddleware استفاده کرد:
import { applyMiddleware, bindActionCreators, combineReducers, compose, createStore } from "redux";

// ...

const store = createStore(
  combineReducers({
    calculator: calculatorReducer
  }),
  applyMiddleware(logger)
);

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: state-management-redux-mobx-part02.zip