مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت اول - جمع آوری اطلاعات آماری کوئری‌های زنده
بسیاری از شرکت‌ها دارای نقشی مانند «مدیران بانک اطلاعاتی» نیستند؛ اما تعدادی «توسعه دهنده‌ی بانک‌های اطلاعاتی» را به همراه دارند که گاهی از اوقات از آن‌ها خواسته می‌شود تا کارآیی پایین کوئری‌های صورت گرفته را بررسی و رفع کنند و ... آن‌ها دقیقا نمی‌دانند که باید از کجا شروع کنند! فقط می‌دانند که یک کوئری، مدت زمان زیادی را برای اجرا به خود اختصاص می‌دهد؛ اما نمی‌دانند که چگونه باید به کوئری پلن آن دسترسی یافت و چگونه باید آن‌را تفسیر کرد. در این حالت حداکثر کاری را که ممکن است انجام دهند، افزودن یک ایندکس جدید است که ممکن است کار کند و یا خیر و حتی اگر کار کند، دقیقا نمی‌دانند که چگونه! هدف از این سری، بررسی مقدماتی روش‌های بهبود کارآیی کوئری‌ها در SQL Server، از دید یک «توسعه دهنده‌ی بانک‌های اطلاعاتی» است.


پیشنیازهای این سری

در این سری از بانک اطلاعاتی استاندارد مثال به همراه SQL Server 2016، به نام WideWorldImporters استفاده می‌کنیم. برای دریافت آن، به قسمت releases مثال‌های مایکروسافت مراجعه کرده و فایل WideWorldImporters-Full.bak را دریافت کنید. پس از دریافت این فایل، برای restore سریع آن، می‌توانید دستور زیر را اجرا کنید که در آن باید مسیر فایل bak دریافتی و همچنین مسیر ایجاد فایل‌های mdf/ldf/ndf را مطابق مسیرهای سیستم خودتان اصلاح نمائید (فقط مسیر پوشه‌ها را نیاز است تغییر دهید):
use master;

RESTORE DATABASE WideWorldImporters 
FROM disk='D:\path\WideWorldImporters-Full.bak'
WITH MOVE 'WWI_Primary' TO 'D:\SQL_Data\WideWorldImporters.mdf',
MOVE 'WWI_Log' TO 'D:\SQL_Data\WideWorldImporters_log.ldf',
MOVE 'WWI_UserData' TO 'D:\SQL_Data\WideWorldImporters_UserData.ndf',
MOVE 'WWI_InMemory_Data_1' TO 'D:\SQL_Data\WideWorldImporters_InMemory_Data_1'
همچنین صرفنظر از نگارش SQL Server ای که در حال استفاده‌ی از آن هستید (البته به حداقل SQL Server 2016 نیاز خواهید داشت)، بهتر است آخرین نگارش برنامه‌ی management studio را نیز به صورت مستقل دریافت و نصب کنید که در این زمان نگارش 18.1 است.


یافتن اطلاعاتی در مورد کوئری‌ها

SQL Server زمانیکه یک کوئری را اجرا می‌کند، اطلاعاتی را نیز به همراه آن تولید خواهد کرد که سبب ایجاد یک Query Plan می‌شود و در آن، اطلاعاتی مانند جداول مورد استفاده، نوع جوین‌ها، ایندکس‌های استفاده شده و غیره وجود دارند. علاوه بر آن، Query Statistics نیز قابل دسترسی هستند که در آن مدت زمان اجرای یک کوئری، میزان I/O صورت گرفته و میزان مصرف CPU کوئری، ذکر می‌شوند. برای دسترسی یافتن به این اطلاعات، می‌توان به اشیاء مختلف SQL Server مراجعه کرد؛ مانند dynamic management objects یا به اختصار DMO's، همچنین extended events، traces، query stores و یا حتی management studio. مهم‌ترین تفاوت این‌ها نیز در نحوه‌ی دسترسی به اطلاعات آن‌ها است که می‌تواند زنده (live) و یا ذخیره شده در جائی باشند. در اینجا تنها منبعی که امکان مشاهده‌ی این اطلاعات را به صورت زنده میسر می‌کند، management studio است. البته live در اینجا به معنای امکان مشاهده‌ی تمام اطلاعات مرتبط با یک کوئری، مانند آمار و کوئری پلن آن در داخل محیط management studio، پس از اجرای یک کوئری است. در این قسمت بیشتر به روش استخراج اطلاعات آماری کوئری‌های زنده می‌پردازیم و در قسمت‌های بعدی، سایر گزینه‌های نامبرده شده را نیز بررسی خواهیم کرد.


مشاهده‌ی زنده‌ی داده‌های مرتبط با اجرای یک کوئری در management studio

پس از restore بانک اطلاعاتی مثال WideWorldImporters که عنوان شد، در برنامه‌ی Microsoft SQL Server Management Studio، کوئری زیر را اجرا می‌کنیم:
USE [WideWorldImporters];
GO

SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [c].[CountryName] = 'United States';
GO
با اجرای آن، اگر به ذیل ردیف‌های بازگشت داده شده‌ی در Management Studio دقت کنیم، مشخص کرده‌است که این کوئری، 53 ردیف را بازگشت داده و همچنین کمتر از 1 ثانیه مدت زمان اجرای آن، طول کشیده‌است:


اینجا است که نیاز به اطلاعات بیشتری در مورد نحوه‌ی اجرای این کوئری داریم. برای استخراج این اطلاعات، اینبار گزینه‌های تولید و جمع آوری اطلاعات آماری IO و TIME را روشن می‌کنیم و سپس همان کوئری قبلی را اجرا خواهیم کرد:
USE [WideWorldImporters];
GO

SET STATISTICS IO ON;
GO
SET STATISTICS TIME ON;
GO

SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [c].[CountryName] = 'United States';
GO
ظاهر اجرای این کوئری با کوئری قبلی، تفاوت خاصی ندارد. اما اگر در همینجا به برگه‌ی messages، که در کنار برگه‌ی results و نمایش ردیف‌ها قرار دارد، مراجعه کنیم، یک چنین خروجی قابل مشاهده است:
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 504 ms.

(53 rows affected)
Table 'Countries'. Scan count 0, logical reads 118, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'StateProvinces'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 10 ms.
در اینجا اطلاعات آماری مدت زمان کامپایل و همچنین مدت زمان اجرای کوئری، ارائه شده‌اند. به علاوه در میانه‌ی این آمار، اطلاعات IO کوئری مانند logical reads درج شده‌اند.


استخراج اطلاعات Actual Execution Plan یک کوئری

کوئری را زیر با فرض IO ON و TIME ON حاصل از اجرای کوئری قبل، اجرا می‌کنیم:
USE [WideWorldImporters];
GO

SET STATISTICS XML ON;
GO

SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [c].[CountryName] = 'United States';
GO

SET STATISTICS XML OFF;
GO
با فعالسازی اطلاعات آماری XML (و خاموش کردن آن در انتهای کار)، اینبار در برگه‌ی messages، اطلاعات بیشتری ارائه شده‌اند:
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 7 ms.

(53 rows affected)
Table 'Countries'. Scan count 0, logical reads 118, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'StateProvinces'. Scan count 1, logical reads 43, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 15 ms,  elapsed time = 179 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
اگر دقت کنید اینبار زمان اجرا اندکی بیشتر شده‌است؛ چون درخواست تهیه‌ی query plan را داده‌ایم. این plan را در ذیل قسمت نتایج کوئری می‌توان مشاهده کرد:


اگر بر روی این XML کلیک کنیم، برگه‌ی جدید نمایش گرافیکی این plan ظاهر می‌شود:


با کلیک راست بر روی این برگه، می‌توان اطلاعات آن‌را جهت بررسی‌های بعدی و یا به اشتراک گذاری آن ذخیره کرد.
در این plan اگر اشاره‌گر ماوس را بر روی هر کدام از عناصر آن حرکت دهیم، اطلاعاتی مانند actual number of rows نیز مشاهده می‌شود، در کنار اطلاعات تخمینی؛ به همین جهت به آن Actual Execution Plan هم گفته می‌شود.


این یک روش دسترسی به Execution Plan است. روش دوم آن با استفاده از امکانات رابط کاربری خود Management Studio است؛ با فشردن دکمه‌های Ctrl+M و یا انتخاب گزینه‌ی Include actual execution plan از منوی Query آن. پس از آن کوئری زیر را اجرا کنید:
SET STATISTICS IO ON;
GO
SET STATISTICS TIME ON;
GO

SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [c].[CountryName] = 'United States';
GO
اینبار در برگه‌ی نتایج کوئری، برگه‌ی سوم Execution Plan قابل مشاهده‌است:




استخراج اطلاعات Estimated Execution Plan یک کوئری

تا اینجا نحوه‌ی استخراج اطلاعات Actual Execution Plan را بررسی کردیم که به همراه اطلاعات دقیق حاصل از اجرای کوئری نیز بود؛ مانند actual number of rows. نوع دیگری از Execution Planها را نیز می‌توان از SQL Server درخواست کرد که به آن‌ها Estimated Execution Plan گفته می‌شود و حاصل اجرای کوئری نیستند؛ بلکه تخمینی هستند از روش اجرای این کوئری توسط SQL Server. برای فعالسازی محاسبه‌ی آن، ابتدا کوئری زیر را در management studio انتخاب کنید:
USE [WideWorldImporters];
GO

SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [c].[CountryName] = 'United States';
GO
سپس از منوی Query، گزینه‌ی Display estimated execution plan را انتخاب نمائید و یا دکمه‌های Ctrl+L را فشار دهید. در این حالت برگه‌های حاصل، حاوی قسمت results نیستند؛ چون کوئری اجرا نشده‌است. اما هنوز برگه‌ی Execution Plan قابل مشاهده است:


همانطور که مشاهده می‌کنید، اینبار نتیجه‌ی حاصل، به همراه اطلاعاتی مانند actual number of rows نیست و صرفا تخمینی است از روش اجرای این کوئری، توسط SQL Server.


جمع آوری اطلاعات آماری کلاینت‌ها

در منوی Query، گزینه‌ای تحت عنوان Include client statistics نیز وجود دارد. با انتخاب آن، اگر کوئری زیر را اجرا کنیم:
USE [WideWorldImporters];
GO

SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [c].[CountryName] = 'United States';
GO
اینبار برگه‌ی جدید client statistics ظاهر می‌شود:


در اینجا مشخص می‌شود که آیا عملیات insert/update/delete انجام شده‌است. چه تعداد ردیف تحت تاثیر اجرای این کوئری قرار گرفته‌اند. چه تعداد تراکنش انجام شده‌است. همچنین اطلاعات آماری شبکه و زمان نیز در اینجا ارائه شده‌اند.
در همین حالت، کوئری جدید زیر را با تغییر قسمت where کوئری قبلی، اجرا کنید:
SELECT
    [s].[StateProvinceName],
    [s].[SalesTerritory],
    [s].[LatestRecordedPopulation],
    [s].[StateProvinceCode]
FROM [Application].[Countries] [c]
    JOIN [Application].[StateProvinces] [s]
    ON [s].[CountryID] = [c].[CountryID]
WHERE [s].[StateProvinceName] LIKE 'O%';
GO
نتیجه‌ی آن، ظاهر شدن ستون جدید trial 2 است که می‌تواند جهت مقایسه‌ی کوئری‌های مختلف با هم، بسیار مفید باشد:


در اینجا حداکثر 10 کوئری را می‌توان با هم مقایسه کرد و بیشتر از آن سبب حذف موارد قدیمی از لیست می‌شود.


عدم نمایش ردیف‌های بازگشت داده شده‌ی توسط کوئری در حین جمع آوری اطلاعات آماری

هربار اجرای یک کوئری در management studio، به همراه بازگشت و نمایش ردیف‌های مرتبط با آن کوئری نیز می‌باشد. اگر می‌خواهید در حین بررسی کارآیی کوئری‌ها از نمایش این ردیف‌ها صرف نظر کنید (تا بار این برنامه کاهش یابد)، می‌توانید از منوی Query، گزینه‌ی Query Options را انتخاب کرده و در قسمت Results، گزینه‌ی Grid آن، گزینه‌ی discard results after execution را انتخاب کنید تا دیگر برگه‌ی results نمایش داده نشود و وقت و منابع را تلف نکند. بدیهی است پس از پایان کار بررسی آماری، نیاز به عدم انتخاب این گزینه خواهد بود.
مطالب
آموزش فریم ورک Vuetify قسمت دوم - UI Components بخش اول

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

یک کامپوننت در واقع از دو بخش تشکیل شده‌است:

1 - اول اینکه چگونه به نظر می‌رسد ( UI ).

2 – دوم اینکه چگونه کار میکند ( UX ).

این عناصر رابط ( component ) شامل :

Input Controls : check boxes, radio buttons, drop down lists, list boxes, buttons, toggles, text fields, date field

Navigational Components : breadcrumb, slider, search field, pagination, slider, tags, icons

Informational Components : tool tips, icons, progress bar, notifications, message boxes, modal windows

Containers : accordion 

اما باید توجه داشت که فقط به این موارد محدود نمی‌شوند.

در این قسمت به طور مختصر با این دست از کامپوننت‌ها ( UI Component ) آشنا می‌شویم. 


کامپوننت v-alert 

کامپوننت V-Alert برای انتقال اطلاعات مهم به کاربر مورد استفاده قرار می‌گیرد. این کامپوننت چهار نوع اطلاعات را به کاربر گوشزد می‌کند که شامل موفقیت‌ها، اطلاعات، هشدارها و خطاها می‌باشد.

هشدارها می‌توانند یک رنگ خاص را داشته باشند که به طور پیش فرض نمایش داده نمی‌شوند. 

در مثال پایین، کامپوننت v-alert شامل دو مقدار است که برای آن تنظیم شده‌است. مقدار (value) که شامل یک مقدار Boolean است و مقدار (type) که مشخص کننده نوع هشدار است (موفقیت ، اطلاعات ، هشدار و خطا).

در قطعه کد پایین، این چهار نوع اطلاعات قابل نمایش به کاربر مشخص شده‌اند:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-alert :value="true" type="success">
        This is a success alert.
      </v-alert>
  
      <v-alert :value="true" type="info">
        This is a info alert.
      </v-alert>
  
      <v-alert :value="true" type="warning">
        This is a warning alert.
      </v-alert>
  
      <v-alert :value="true" type="error">
        This is a error alert.
      </v-alert>
    </div>
  </v-app>
</div>



برای کامپوننت V-Alert می‌توان properties‌های مختلفی را مشخص نمود که از جمله آنها می‌توان به موارد زیر اشاره کرد:

Color : به وسیله این property می‌توان رنگ پیغام را مشخص نمود. هم به وسیله نام رنگ میتوان رنگ مورد نظر را مشخص کرد و هم به وسیله‌ی کد RGB این کار را انجام داد. dismissible : این تنظیم مشخص می‌کند که پیغام، قابلیت بسته شدن را دارد یا خیر که حاوی یک مقدار Boolean است.

icon : مشخص کننده یک نماد خاص است که درون جعبه پیغام قرار می‌گیرد.

type :  مشخص کننده نوع پیام است که پیشتر در مورد آن توضیح داده شد. 


کامپوننت v-avatar 

کامپوننت v-avatar برای تغییر اندازه تصاویر مورد استفاده قرار می‌گیرد که معمولا جهت نمایش عکس پروفایل استفاده می‌شود.

طریقه استفاده :

avatar، دارای یک اندازه‌ی پویا است که می‌تواند برای هر وضعیتی تغییر کند.

برای این کامپوننت سه properties قابل تنظیم است:

color : به وسیله این property می‌توان رنگ دلخواهی را برای آواتار مشخص نمود. هم به وسیله نام رنگ می‌توان رنگ مورد نظر را مشخص کرد و هم به وسیله کد RGB این کار را صورت داد.

size : به طور پیش‌فرض برای avatar، سایز 48 تنظیم شده‌است که می‌توان این میزان را کم و یا زیاد کرد.

tile : همانند border radius در css عمل می‌کند. با تنظیم این گزینه میتوانیم یک آواتار گرد داشته باشیم.


کامپوننت v-badge

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

این کامپوننت نیز properties خاص خود را دارد که از جمله آن می‌توان به color , left , mode , overlab  و غیره اشاره کرد.

قطعه کد پایین نشان دهنده چگونگی عملکرد این کامپوننت است:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-badge color="purple" left overlap>
        <template v-slot:badge>
          <v-icon dark small>
            done
          </v-icon>
        </template>
        <v-icon color="grey lighten-1" large>
          account_circle
        </v-icon>
      </v-badge>
        <v-badge overlap color="orange">
        <template v-slot:badge>
          <v-icon dark small>
            notifications
          </v-icon>
        </template>
        <v-icon large color="grey darken-1">
          account_box
        </v-icon>
      </v-badge>
    </div>
  </v-app>
</div>

نتیجه قطعه کد بالا بدین ترتیب است:


کامپوننت v-bottom-nav 

این کامپوننت را می‌توان جایگزین sidebar‌ها نمود. این کامپوننت در درجه اول در موبایل مورد استفاده قرار میگیرد که می‌تواند شامل متن و یا آیکن باشد. 

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

این کامپوننت نیز properties خاص خود را دارد که از جمله آن می‌توان به active-sync (برای نشان دادن فعال یا غیر فعال بودن گزینه انتخاب شده)، fixed ( برای مشخص کردن موقعیت کامپوننت در صفحه) و موارد دیگر اشاره کرد.

تقسیم بندی اجزاء این کامپوننت به شرح زیر است:

1 - محل قرار گیری کامپوننت

2- آیکن غیر فعال

3- برچسب غیر فعال

4 - آیکن فعال

5- برچسب فعال

قطعه کد پایین نشان دهنده چگونگی یک bottom navbar است:

<div id="app">
  <v-app id="inspire">
    <v-card height="200px" flat>
      <div>
        Active: {{ bottomNav }}   // 
      </div>
      <v-bottom-nav :active.sync="bottomNav" :value="true" absolute color="transparent">
        <v-btn color="teal" flat value="recent">
          <span>Recent</span>
          <v-icon>history</v-icon>
        </v-btn>
  
        <v-btn color="teal" flat value="favorites">
          <span>Favorites</span>
          <v-icon>favorite</v-icon>
        </v-btn>
  
        <v-btn color="teal" flat value="nearby">
          <span>Nearby</span>
          <v-icon>place</v-icon>
        </v-btn>
      </v-bottom-nav>
    </v-card>
  </v-app>
</div>



کامپوننت v-btn  

این کامپوننت برای ایجاد یک دکمه چه به صورت متن و یا آیکن مورد استفاده قرار میگیرد. دکمه‌ها به کاربران این امکان را می‌دهند تا اقداماتی را انجام دهند و انتخاب‌های خود را تنها با یک کلیک انجام دهند.

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

کامپوننت v-btn نیز مانند سایر کامپوننت‌ها تنظیمات خاص خود را دارد که از جمله آن می‌توان به کوچکی و بزرگی دکمه، فعال یا غیر فعال بودن دکمه، نوع متن یا آیکن بودن دکمه اشاره نمود. 

حالت‌های مختلفی از دکمه‌ها وجود دارند که میتوانند به بهتر شدن UI برنامه ما کمک کنند. برای مثال می‌توان به موارد زیر اشاره کرد:

button drop-down variants : دکمه‌های کرکره‌ای که معمولا برای نظم و کم جا بودن در صفحه مورد استفاده قرار می‌گیرند.

icons : آیکن‌ها می‌توانند برای محتوای اصلی یک دکمه مورد استفاده قرار بگیرند تا ظاهر زیباتری را به دکمه ما بدهند.

floating : این دکمه‌ها حالت آیکن را دارند؛ با این تفاوت که آیکن مورد نظر، درون یک محتوا قرار می‌گیرد.

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

round : این نوع دکمه‌ها دقیقا کارآیی دکمه‌های معمولی را دارند؛ با این تفاوت که این دکمه‌ها دارای لبه‌هایی گرد هستند.

یک نمونه از ایجاد انواع دکمه‌ها در زیر آمده است:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-btn color="success">Success</v-btn>
      <v-btn color="error">Error</v-btn>
      <v-btn color="warning">Warning</v-btn>
      <v-btn color="info">Info</v-btn>
    </div>
  </v-app>
</div>




کامپوننت v-calendar

یکی از کامپوننت‌هایی که به تازگی به vuetify اضافه شده است، کامپوننت تقویم یا v-calendar است. از این کامپوننت برای نمایش تاریخ، روز، هفته، ماه و سال استفاده می‌شود. یک تقویم دارای یک نوع و یک مقدار است که تعیین می‌کند چه نوع تقویمی، در طول چه مدت زمانی نمایش داده شود.

حالت‌های مختلفی برای نمایش تقویم در صفحه وجود دارد که برای مثال می‌توان به موارد زیر اشاره کرد: 

events : به وسیله این گزینه میتوان برای هر روز یک رخداد خاص را مشخص نمود که به وسیله کلیک بر روی آن، اطلاعات آن رخداد نمایش داده شود.

weekly : می‌توان یک تقویم هفتگی را ایجاد نمود و رخدادهای هفتگی را برای آن تنظیم کرد.

نمونه ایجاد یک تقویم در پایین آمده است:

<div id="app">
  <v-app id="inspire">
    <v-layout wrap>
      <v-flex xs12>
        <v-sheet height="500">
          <v-calendar ref="calendar" v-model="start" :type="type" :end="end" color="primary">
          </v-calendar>
        </v-sheet>
      </v-flex>
  
      <v-flex sm4 xs12>
        <v-btn @click="$refs.calendar.prev()">
          <v-icon dark left>
            keyboard_arrow_left
          </v-icon>
          Prev
        </v-btn>
      </v-flex>
      <v-flex sm4 xs12>
        <v-select v-model="type" :items="typeOptions" label="Type">
        </v-select>
      </v-flex>
      <v-flex sm4 xs12>
        <v-btn @click="$refs.calendar.next()">
          Next
          <v-icon right dark>
            keyboard_arrow_right
          </v-icon>
        </v-btn>
      </v-flex>
    </v-layout>
  </v-app>
</div>
js قطعه کد
new Vue({
  el: '#app',
  data: () => ({
    type: 'month', //مشخص کننده نوع تقویم که در اینجا تقویم به صورت ماهانه است
    start: '2019-01-01',
    end: '2019-01-06',
    typeOptions: [
      { text: 'Day', value: 'day' },
      { text: '4 Day', value: '4day' },
      { text: 'Week', value: 'week' },
      { text: 'Month', value: 'month' },
      { text: 'Custom Daily', value: 'custom-daily' },
      { text: 'Custom Weekly', value: 'custom-weekly' }
    ]
  })
})





نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند

با سلام و تشکر

از  این DataPicker  استفاده کردم  ولی:

1- به چه صورت از یک دکمه برای نمایش تقویم و نمایش تاریخ انتخابی در TexBox استفاده کنیم

2-در حالت عادی دکمه‌های برگشت ماه و سال درست نمایش داده نمی‌شوند و در حالت Compatibility View دکمه‌ها درست دیده می‌شوند

مطالب
React 16x - قسمت 30 - React Hooks - بخش 1 - معرفی useState و useEffect
با استفاده از React Hooks که در نگارش 16.7.0 آن معرفی شدند، می‌توان در کامپوننت‌های تابعی «تا پیش از این» بدون حالت، به state و تمام قابلیت‌های دیگر React، دسترسی یافت. جهت یادآوری، در قسمت 8 این سری، کامپوننت‌های تابعی بدون حالت را معرفی کردیم. تا پیش از معرفی React Hooks، برای ردیابی تغییرات مقادیری خاص، می‌بایستی آن‌ها را در خاصیت state کامپوننت‌هایی که به صورت کلاس تعریف شده بودند، قرار می‌دادیم. بنابراین class components، تنها نوع کامپوننت‌هایی در React بودند که دسترسی به state را داشتند. شبیه به همین مورد، برای life-cycle hooks معرفی شده‌ی در قسمت 9 برقرار بود. برای مثال متد componentDidMount، تنها در class components، جهت دسترسی به یک API خارجی و انجام اعمال Ajax ای، قابل تعریف بود و کار کامپوننت‌های تابعی بدون حالت، بیشتر نمایش عناصر HTML و دریافت مقادیر خود از class components، توسط props بود. به این ترتیب امکان تجزیه‌ی یک UI پیچیده، به یک component tree با خوانایی بیشتری، میسر می‌شد. اما با معرفی React v16.7.0، از لحاظ فنی دیگر الزاما نیازی به class components وجود ندارد و می‌توان با استفاده از React Hooks، تمام قابلیت‌هایی را که پیشتر فقط توسط class components در اختیار داشتیم، اکنون با کامپوننت‌های تابعی نیز پیاده سازی کنیم.


برپایی پیش‌نیازها

در اینجا برای بررسی React Hooks، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> npm i -g create-react-app
> create-react-app sample-30
> cd sample-30
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

همچنین اگر به فایل package.json موجود در ریشه‌ی پروژه دقت کنیم، برای کار با React-hooks، نیاز است نگارش بسته‌های React و React-dom، حداقل مساوی 16.7 باشند که در زمان نگارش این مطلب، نگارش 16.12.0 آن به صورت خودکار نصب می‌شود. بنابراین بدون مشکلی می‌توانیم شروع به کار با React hooks کنیم.


معرفی useState Hook

در اینجا قصد داریم یک شمارشگر را به همراه یک دکمه، در صفحه نمایش دهیم؛ بطوریکه این شمارشگر، تعداد بار کلیک بر روی دکمه را ردیابی می‌کند. از پیش می‌دانیم که برای ردیابی مقدار تعداد بار کلیک شدن، باید متغیر آن‌را درون state یک class component قرار داد:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = { count: 0 };

  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <button onClick={this.incrementCount} className="btn btn-primary m-3">
        I was clicked {this.state.count} times!
      </button>
    );
  }
}

export default App;
در اینجا یک class component متداول را داریم که در آن دکمه‌ای تعریف شده‌است. سپس برای شمارش تعداد بار کلیک بر روی آن، خاصیت count را به شیء منتسب به state، با مقدار اولیه‌ی صفر، اضافه کرده‌ایم. اکنون هربار که بر روی آن کلیک می‌شود، رویدادگردان incrementCount فراخوانی شده و توسط متد this.setState، مقدار پیشین خاصیت count، بر اساس مقدار فعلی آن، یک واحد افزایش می‌یابد. نتیجه‌ی آن نیز در برچسب دکمه، نمایش داده می‌شود:


اکنون می‌خواهیم همین کامپوننت را توسط React hooks بازنویسی کنیم. در ابتدا، فایل app.js را به App‍Class.js، تغییر نام می‌دهیم تا نگارش قبلی class component را برای مقایسه، در اختیار داشته باشیم. در ادامه فایل جدید AppFunction.js را برای بازنویسی آن توسط یک کامپوننت تابعی، توسط میانبرهای imrc و سپس sfc در VSCode، ایجاد می‌کنیم. البته این تغییر نام فایل‌ها، نیاز به تغییر نام ماژول‌های import شده‌ی در فایل index.js را نیز به صورت زیر دارد:
//import App from "./App‍Class";
import App from "./AppFunction";

اولین سؤالی که اینجا مطرح می‌شود، این است: در این کامپوننت تابعی جدید، state را از کجا بدست بیاوریم؟
با React Hooks، بجای تعریف یک state به صورت خاصیت، آن‌را صرفا use می‌کنیم و این دقیقا نام اولین React Hooks ای است که بررسی می‌کنیم؛ یا همان useState. بنابراین ابتدا این شیء را import خواهیم کرد:
import React, { useState } from 'react';
useState یک تابع است و باید در ابتدای کامپوننت، مورد استفاده قرار گیرد. این متد برای شروع به کار، نیاز به یک state آغازین را دارد؛ دقیقا مانند همان‌کاری که در class component فوق انجام دادیم:
const App = () => {
    const [count, setCount] = useState(0);
در اینجا عدد صفر، همان مقدار آغازین متغیر count است (شبیه به کاری که در state = { count: 0 } انجام دادیم). سپس اولین خروجی متد useState که داخل یک آرایه مشخص شده‌است (JavaScript array destructuring ؛ با مزیت امکان انتخاب نام‌هایی دلخواه، بدون نیاز به تعریف alias، برخلاف حالت object destructuring)، همان متغیر count است که توسط state ردیابی خواهد شد. اینبار بجای متد this.setState قبلی که یک متد عمومی بود، متدی اختصاصی را صرفا جهت تغییر مقدار همین متغیر count، به نام setCount به عنوان دومین خروجی متد useState، تعریف می‌کنیم. در حقیقت تا اینجا امضای متد جنریک useState استفاده شده، به صورت زیر تغییر کرده‌است:
useState<number>(initialState: number | (() => number)): [number, React.Dispatch<React.SetStateAction<number>>]
متد useState، یک initialState را دریافت می‌کند و سپس یک عدد را (در اینجا چون مقدار آغازین، عددی است)، به همراه یک متد، جهت تغییر state آن، بازگشت می‌دهد:
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <button onClick={incrementCount} className="btn btn-primary m-3">
      I was clicked {count} times!
    </button>
  );
};

export default App;
- پس از تعریف useState، متد رویدادگردان onClick را همانند قبل تعریف می‌کنیم؛ با یک تغییر مهم: چون این متد داخل یک کامپوننت تابعی تعریف شده‌است، باید با const شروع شود و همانند یک متغیر که به آن متدی انتساب داده شده، معرفی گردد. پیشتر incrementCount تعریف شده‌ی داخل یک class component، یک خاصیت بود که متدی به آن انتساب داده شده بود.
- همچنین در اینجا (داخل این متد) دیگر خبری از this‌ها نیست؛ onClick، مستقیما به متغیر incrementCount اشاره می‌کند و {count} نیز مستقیما از خروجی useState، که به مقدار جاری count اشاره می‌کند، تامین می‌شود.
- اکنون با هربار کلیک بر روی این دکمه، متد منتسب به متغیر incrementCount فراخوانی شده و در داخل آن، همان متد setCount را جهت به روز رسانی state، فراخوانی می‌کنیم (بجای فراخوانی this.setState عمومی قبلی). در اینجا ابتدا مقدار جاری متغیر count در state، دریافت شده و سپس یک واحد به آن اضافه می‌شود. امضای متد جنریک setCount به صورت زیر است:
 const setCount: (value: React.SetStateAction<number>) => void


استفاده از مقدار قبلی state توسط useState

زمانیکه متد this.setState فراخوانی می‌شود، اینکار سبب در صف قرار گرفتن رندر مجدد کامپوننت جاری خواهد شد. همچنین اعمال این متد نیز ممکن است در صف قرار گیرد. یعنی اگر پس از فراخوانی this.setState، سعی در خواندن state به روز شده را داشته باشیم، ممکن است مقدار اشتباهی را دریافت کنیم:
  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };
برای نمونه در فراخوانی فوق، منظور از this.state.count، مقدار جاری یا همان مقدار قبلی count است که قرار است یک واحد به آن اضافه شود. اما چون متد this.setState کار به روز رسانی‌های state را نیز در صف قرار می‌دهد، دفعه‌ی بعدی که بر روی آن کلیک شد، الزامی ندارد که this.state.count، حتما در همان لحظه به روز رسانی شده باشد. برای رفع این مشکل می‌توان نوشت:
  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };
prevState در اینجا یعنی state قبلی و توسط متد setState، به صورت یک arrow function قابل دریافت است که در نهایت یک شیء را باز می‌گرداند. اکنون بر اساس state قبلی می‌توان به روز رسانی دقیقی را انجام داد.

این نکته در مورد کامپوننت‌های تابعی نیز وجود دارد:
  const incrementCount = () => {
    setCount(count + 1);
  };
در اینجا متغیر count، همانند خاصیت this.state.count کامپوننت‌های کلاسی عمل می‌کند. بنابراین الزامی ندارد که با هر بار فراخوانی setCount، مقدار جاری متغیر count، دقیقا به مقدار قبلی تنظیم شده‌ی آن اشاره کند. برای انجام مشابه اینکار با کامپوننت‌های تابعی که از useState استفاده می‌کنند، می‌توان به صورت زیر عمل کرد:
const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };
در اینجا نیز امکان دسترسی به مقدار قبلی count، توسط یک arrow function وجود دارد که برخلاف حالت prevState، فقط یک مقدار عددی مرتبط با count را باز می‌گرداند.


به روز رسانی بیش از یک خاصیت در state

فرض کنید قصد داریم به مثال جاری، یک مربع را در صفحه اضافه کنیم که با کلیک بر روی آن، رنگش تغییر می‌کند (خاموش و روشن می‌شود):


در حالت AppClass یا کامپوننت کلاسی، کدهای برنامه به صورت زیر تغییر می‌کنند:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = {
    count: 0,
    isOn: false
  };

  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };

  toggleLight = () => {
    this.setState(prevState => ({
      isOn: !prevState.isOn
    }));
  };

  render() {
    return (
      <>
        <h1>App Class</h1>
        <h2>Counter</h2>
        <button onClick={this.incrementCount} className="btn btn-primary m-3">
          I was clicked {this.state.count} times!
        </button>

        <h2>Toggle Light</h2>
        <div
          style={{
            height: "50px",
            width: "50px",
            cursor: "pointer"
          }}
          className={
            this.state.isOn ? "alert alert-info m-3" : "alert alert-warning m-3"
          }
          onClick={this.toggleLight}
        />
      </>
    );
  }
}

export default App;
توضیحات:
- در متد رندر، نیاز است تا تنها یک child، بازگشت داده شود. به همین جهت می‌توان از React.Fragment، برای محصور سازی المان‌های تعریف شده، استفاده کرد. البته در React 16.7.0، دیگر نیازی به ذکر صریح React.Fragment نبوده و فقط می‌توان نوشت </><> تا بیانگر یک فرگمنت باشد.
- سپس یک div تعریف شده که با استفاده از ویژگی style، یک سری شیوه‌نامه‌ی ابتدایی، مانند طول و عرض و نوع اشاره‌گر ماوس آن، تعیین شده‌اند.
- اکنون برای اینکه بتوان با کلیک بر روی این div، رنگ آن‌را تغییر داد، نیاز است بتوان توسط متغیری، مقدار خاموش و روشن بودن را ردیابی کرد. به همین جهت خاصیت جدید isOn را به state اضافه می‌کنیم.
- در آخر، رویداد onClick این div را به متد رویدادگران toggleLight متصل می‌کنیم تا توسط آن و فراخوانی this.setState، بتوان مقدار قبلی isOn را در state، دریافت و سپس آن‌را معکوس کرد و بجای مقدار جاری isOn در state درج کنیم. این فراخوانی، سبب رندر مجدد کامپوننت جاری شده و در نتیجه‌ی آن، مقدار className را بر اساس مقدار this.state.isOn، به صورت پویا تغییر می‌دهد.

برای مشاهده‌ی خروجی برنامه در این حالت، نیاز است به index.js مراجعه و تغییر زیر را اعمال کرد تا کامپوننت App، از ماژول App‍Class تامین شود:
import App from "./App‍Class";
//import App from "./AppFunction";

اکنون قصد داریم دقیقا معادل همین قطعه کد را در کامپوننت تابعی خود پیاده سازی کنیم. به همین جهت به فایل src\AppFunction.js بازگشته و تغییرات زیر را اعمال می‌کنیم:
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const [isOn, setIsOn] = useState(false);

  const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };

  const toggleLight = () => {
    setIsOn(prevIsOn => !prevIsOn);
  };

  return (
    <>
      <h1>App Function</h1>
      <h2>Counter</h2>
      <button onClick={incrementCount} className="btn btn-primary m-3">
        I was clicked {count} times!
      </button>

      <h2>Toggle Light</h2>
      <div
        style={{
          height: "50px",
          width: "50px",
          cursor: "pointer"
        }}
        className={isOn ? "alert alert-info m-3" : "alert alert-warning m-3"}
        onClick={toggleLight}
      />
    </>
  );
};

export default App;
توضیحات:
- اگر دقت کنید، کلیات این کامپوننت تابعی، با کامپوننت کلاسی، آنچنان متفاوت نیست. متد رندر آن دقیقا همان markup را بازگشت می‌دهد؛ با یک تفاوت مهم: در اینجا دیگر نیازی به ذکر thisها نیست، چون تمام ارجاعات، به متغیرهای داخل تابع App انجام شده‌است و نه به متدها و یا خاصیت‌های یک کلاس.
- مرحله‌ی بعد تغییر، جایگزینی this.state.isOn قبلی، با یک متغیر درون تابع App است. به همین جهت در اینجا یک useState دیگر را تعریف می‌کنیم. هر useState، تنها به قسمتی از state اشاره می‌کند و مانند خاصیت کلی state مربوط به یک کلاس نیست. همچنین در خاصیت state یک کلاس، مقدار آن همواره به یک شیء اشاره می‌کند؛ اما با useState چنین اجباری وجود ندارد و می‌تواند هر نوع مجاز جاوا اسکریپتی باشد. برای مثال در اینجا مقدار اولیه‌ی آن به false تنظیم شده‌است. پس از آن، خروجی این متد، قسمتی از state را که کنترل می‌کند، به نام متغیر isOn و تنظیم کننده‌ی آن‌را به نام متد setIsOn، معرفی خواهد کرد. متد useState، یک متد جنریک است. یعنی نوع خروجی‌های آن بر اساس نوع مقدار اولیه‌ای که به آن انتساب داده می‌شود، تعیین می‌شود. برای مثال اگر نوع مقدار اولیه‌ی آن، Boolean باشد، به صورت خودکار نوع متغیر و پارامتر متد خروجی از آن نیز Boolean خواهند بود.
- در آخر خاصیت toggleLight کلاس کامپوننت، تبدیل به یک متغیر یا ثابت، در تابع جاری App می‌شود و بجای this.setState کلی قبلی، از متد اختصاصی‌تر setIsOn، برای تغییر مقدار state متناظر، کمک گرفته خواهد شد. در اینجا با استفاده از prevIsOn، به مقدار دقیق پیشین متغیر isOn در state، دسترسی یافته و سپس آن‌را معکوس می‌کنیم.

برای مشاهده‌ی خروجی برنامه در این حالت، نیاز است به index.js مراجعه و تغییر زیر را اعمال کرد تا کامپوننت App، از ماژول App‍Function تامین شود:
// import App from "./App‍Class";
import App from "./AppFunction";


معرفی useEffect Hook

فرض کنید قصد داریم برچسب دکمه‌ی «I was clicked {this.state.count} times» را در المان Title صفحه، نمایش دهیم. برای انجام چنین کاری نیاز است با DOM API تعامل داشته باشیم؛ اما پیشتر یک چنین کاری را تنها با class components می‌شد انجام داد. برای رفع این محدودیت در کامپوننت‌های تابعی، hook جدیدی به نام useEffect، ارائه شده‌است که باید import شود:
import React, { useEffect, useState } from "react";
اکنون این سؤال مطرح می‌شود که در متد useEffect، واژه‌ی Effect به چه چیزی اشاره می‌کند؟
در اینجا effect به معنای side effect و یا اثرات جانبی است؛ مانند: تعامل با یک API خارجی، کار با API مرورگر و کلا هر جائیکه در برنامه با دنیای خارج تعاملی وجود دارد، به عنوان یک side effect شناخته می‌شود. بنابراین با استفاده متد useEffect، می‌توان در کامپوننت‌های تابعی نیز با دنیای خارج، تعامل برقرار کرد.
import React, { useEffect, useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You have clicked ${count} times`;
  });
متد useEffect، یک تابع را به عنوان ورودی دریافت کرده (effect function) و آن‌را پس از هر بار رندر کامپوننت جاری، اجرا می‌کند. برای مثال با تغییر state، کار رندر کامپوننت جاری، در صف قرار می‌گیرد و پس از اتمام رندر، تابع effect ذکر شده، اجرا می‌شود. برای مثال در اینجا پس از هر بار رندر کامپوننت،  document.title با عبارتی که به همراه تعداد بار کلیک کردن بر روی دکمه‌است، به روز رسانی می‌شود:


در اولین بار اجرای برنامه، عبارت «You have clicked 0 times»، در عنوان صفحه‌ی جاری، ظاهر می‌شود که از مقدار پیش‌فرض count، استفاده کرده‌است. اکنون اگر بر روی دکمه، کلیک کنیم، پس از تغییر برچسب دکمه (یا همان رندر کامپوننت، پس از تغییری در state)، آنگاه عنوان نمایش داده شده‌ی در مرورگر نیز تغییر می‌کند.

یک نکته: چون useEffect دارای همان scope متغیر count است، نیاز به API خاصی برای خواندن مقدار آن در اینجا نیست.

سؤال: برای پیاده سازی چنین قابلیتی در یک کامپوننت کلاسی چه باید کرد؟ در این مثال، در ابتدای کار باید مقدار پیش‌فرض موجود در state را در عنوان صفحه مشاهده کرد و پس از هربار به روز رسانی state نیز باید این عنوان، تغییر کند.
برای پیاده سازی معادل متد useEffect ای که در یک کامپوننت تابعی استفاده شد، در اینجا باید از دو life-cycle hook متفاوت، به نام‌های componentDidMount و componentDidUpdate، استفاده کنیم:
class App extends Component {

  componentDidMount() {
    document.title = `You have been clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You have been clicked ${this.state.count} times`;
  }
- برای به روز رسانی عنوان صفحه، در اولین بار نمایش آن، باید از متد componentDidMount استفاده کرد.
- همچنین چون می‌خواهیم به ازای هر تغییری در state نیز این عنوان تغییر کند (با هر بار کلیک بر روی دکمه)، باید از متد componentDidUpdate هم استفاده کنیم.


پاکسازی اثرات جانبی در useEffect Hook

فرض کنید قصد داریم موقعیت فعلی کرسر ماوس را در مرورگر نمایش دهیم. برای انجام اینکار در کامپوننت کلاسی، می‌توان از متد componentDidMount جهت دسترسی به DOM API و استفاده از متد addEventListener آن، برای گوش فرا دادن به حرکات کرسر ماوس، استفاده کرد:
class App extends Component {

  componentDidMount() {
    // ...
    window.addEventListener("mousemove", this.handleMouseMove);
  }
 دومین پارامتر این متد، یک callback function است که این حرکات را ردیابی می‌کند:
  handleMouseMove = event => {
    this.setState({
      x: event.pageX,
      y: event.pageY
    });
  };
در اینجا می‌توان با استفاده از شیء رخداد دریافتی، موقعیت x و y کرسر ماوس را در صفحه دریافت کرد و سپس با فراخوانی متد this.setState، این خواص را در state کامپوننت، اضافه و یا به روز رسانی نمود. بنابراین در این حالت الزامی به تعریف این خواص در شیء منتسب به state نیست؛ اما حداقل با تعریف آن‌ها می‌توان مقدار اولیه‌ای را برایشان درنظر گرفت:
class App extends Component {
  state = {
    //...
    x: 0,
    y: 0
  };
در آخر برای نمایش این اطلاعات موجود در state، می‌توان چنین المان‌هایی را به متد رندر کامپوننت، اضافه کرد:
<h2>Mouse Position</h2>
<p>X position: {this.state.x}</p>
<p>Y position: {this.state.y}</p>
بنابراین ردیابی تغییرات محل کرسر ماوس نیز یک side effect است که برای دسترسی به آن، نیاز است با window API کار کرد و این side effect‌ها دو نوع هستند:
- تعدادی از آن‌ها نیازی به پاکسازی و خارج شدن از حافظه را ندارند؛ مانند به روز رسانی عنوان صفحه در مرورگر. می‌توان یک چنین side effect هایی را اجرا و سپس آن‌ها را فراموش کرد.
- اما تعدادی از side effect‌ها را حتما باید پاکسازی کرد؛ مانند mousemove listener تعریف شده‌ی در مثال فوق. در اینجا زمانیکه کامپوننت جاری mount می‌شود، این listener را تعریف می‌کنیم؛ اما با Unmount شدن آن، باید این listener را حذف کرد تا برنامه دچار نشتی حافظه نشود (اگر اینکار انجام نشود، در این مثال مرورگر هنگ خواهد کرد!). روش انجام اینکار در متد componentWillUnmount، به صورت زیر است:
  componentWillUnmount() {
    window.removeEventListener("mousemove", this.handleMouseMove);
  }
سؤال: یک چنین قابلیتی را چگونه می‌توان در یک کامپوننت تابعی تعریف کرد؟
در این حالت نیز می‌توان از متد useEffect استفاده کرد. البته ابتدا باید state شیء ای را برای نگهداری اطلاعات به روز موقعیت مکانی کرسر ماوس، ایجاد کرد:
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
همانطور که عنوان شد، متد useState، برخلاف خاصیت کلی state یک کامپوننت کلاسی که فقط اشیاء را می‌پذیرد، هر نوع جاوا اسکریپتی را می‌تواند بپذیرد که در اینجا برای نمونه به یک شیء، تنظیم شده‌است.
سپس side effect خود را در قسمت effect function متد useEffect قرار می‌دهیم که آن نیز به متغیر handleMouseMove اشاره می‌کند:
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    // ...
    window.addEventListener("mousemove", handleMouseMove);
  });

  const handleMouseMove = event => {
    setMousePosition({
      x: event.pageX,
      y: event.pageY
    });
  };
به متغیر handleMouseMove، متدی انتساب داده شده‌است که با فراخوانی آن توسط mousemove listener، یک شیء evenet دریافت و سپس بر اساس خواص آن، خواص x و y شیء موجود در state، توسط متد setMousePosition، به روز رسانی می‌شود.
سپس برای نمایش x و y به روز رسانی شده‌ی در state، می‌توان از markup زیر در متد رندر استفاده کرد.
<h2>Mouse Position</h2>
{JSON.stringify(mousePosition, null, 2)}
<br />
اکنون سؤال اینجا است که معادل متد componentWillUnmount در اینجا چیست؟
اگر effect function تعریف شده، دارای یک خروجی (از نوع تابع) باشد، به این معنا است که این side effect، نیاز به پاکسازی دارد و این متد را در زمان Unmount آن فراخوانی می‌کند:
  useEffect(() => {
    // …
    // componentDidMount & componentDidUpdate
    window.addEventListener("mousemove", handleMouseMove);

    // componentWillUnmount
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  });
پیشتر آموختیم که effect function، تنها یکبار اجرا نمی‌شود؛ بلکه به ازای هر بار رندر، یکبار پس از آن اجرا می‌شود. یعنی خروجی آن نه فقط در زمان Unmount اجرا می‌شود، بلکه یکبار هم پیش از هر بار اجرای خود effect اجرا می‌گردد. به این ترتیب در اینجا فرصت پاکسازی اجرای قبلی را نیز خواهیم داد. بنابراین روش پاکسازی آن نسبت به متد componentWillUnmount کامپوننت‌های تابعی، پیشرفته‌تر است. یعنی توسط آن می‌توان یک side effect خاص را پیش و پس از هر بار رندر، در صورت نیاز فراخوانی کرد.

سؤال: اگر بخواهیم از اجرای یک side effect، به ازای هر بار رندر جلوگیری کنیم، چه باید کرد؟

برای اینکار می‌توان آرگومان دومی را به متد useEffect اضافه کرد که آرایه‌ای از مقادیر است. توسط اعضای آن می‌توان مقدار و یا مقادیری را مشخص کرد که side effect تعریف شده، به آن وابسته‌است. اکنون اگر این مقدار تغییر کند، آنگاه side effect متناظر با آن نیز اجرا می‌شود:
  useEffect(() => {
    document.title = `You have clicked ${count} times`;
    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  },[]);
در این مثال، چون پارامتر دوم را به صورت یک آرایه‌ی خالی مقدار دهی کرده‌ایم، effect function تعریف شده تنها در زمان mount و unmount اجرا می‌شود. البته اگر این تغییر را ذخیره کرده و برنامه را اجرا کنیم، تمام قسمت‌های تعریف شده‌ی آن به خوبی کار می‌کنند (با کلیک بر روی دکمه، برچسب آن به روز رسانی می‌شود و یا با حرکت کرسر ماوس در صفحه، موقعیت آن گزارش داده می‌شود)، منهای قسمت به روز رسانی عنوان صفحه با هر بار کلیک. علت اینجا است که با تغییر معرفی شده، دیگر حالت componentDidUpdate توسط متد useEffect پوشش داده نمی‌شود و به این ترتیب با هر بار به روز رسانی state و رندر کامپوننت، دیگر کار به اجرای مجدد side effect آن نمی‌رسد.
برای رفع این مشکل، باید به useEffect اعلام کنیم که side effect تعریف شده‌ی در آن، وابسته‌است به مقدار count و با تغییرات آن در state، نیاز است مجددا اجرا شود:
  useEffect(() => {
   // ...
  },[count]);
به همین جهت در آرایه‌ی تعریف شده، مقداری را که به آن وابستگی وجود دارد، مشخص می‌کنیم. با این تغییر اگر بر روی دکمه کلیک کنیم، چون اکنون useEffect می‌داند که باید تغییرات count را ردیابی کند، با اجرای مجدد effect function خود، عنوان صفحه نیز به روز رسانی خواهد شد.


کار با چندین listener مختلف در متد useEffect

سؤال: آیا تنظیم یک وابستگی خاص در متد useEffect، امکان تنظیم event listenerهای دیگر را غیرممکن می‌کند؟
برای پاسخ به این سؤال، چند event listener دیگر را ثبت می‌کنیم. برای مثال یکی دیگر از APIهای مرورگر، navigator نام دارد که توسط آن می‌توان وضعیت آنلاین و آفلاین بودن را به کمک خروجی خاصیت navigator.onLine آن، مشخص کرد. به کمک این API می‌خواهیم این وضعیت را نمایش دهیم. برای این منظور ابتدا state آن‌را در کامپوننت تابعی، ایجاد می‌کنیم:
const [status, setStatus] = useState(navigator.onLine);
مقدار اولیه‌ی این state را نیز توسط مقدار جاری خاصیت navigator.onLine، مشخص کرده‌ایم.
اکنون برای گوش فرا دادن به تغییرات این خاصیت (online و یا offline شدن کاربر)، نیاز است دو event listener را به کمک متد addEventListener ثبت کنیم و همچنین این متدها نیاز به پاکسازی هم دارند:
  useEffect(() => {
    // ...

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      // ...
  
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, [count]);
پس از ثبت event listener ها، اکنون می‌توان با هربار فراخوانی آن‌ها توسط مرورگر، وضعیت state آن‌را به true و یا false، تغییر داد:
  const handleOnline = () => {
    setStatus(true);
  };

  const handleOffline = () => {
    setStatus(false);
  };
و در آخر مقدار متغیر status را به نحو زیر در متد رندر، نمایش داد:
<h2>Network Status</h2>
<p>
  You are <strong>{status ? "online" : "offline"}</strong>
</p>

برای آزمایش حالت offline آن، فقط کافی است به ابزار توسعه دهندگان مرورگر مراجعه کرده و در برگه‌ی network آن، حالت online را offline کنید:


در این حالت هم نمایش وضعیت آنلاین بودن کاربر به درستی کار می‌کند و هم سایر قسمت‌هایی که تاکنون اضافه کرده‌ایم. به این معنا که هر چند توسط ذکر پارامتر [count]، وابستگی خاصی برای side effect ویژه‌ای، مشخص شده‌است، اما ما را از تنظیم event listener‌های دیگری در قسمت‌های mount و unmount محروم نمی‌کند.


پاکسازی اثرات جانبی در useEffect Hook، زمانیکه روشی برای آن وجود ندارد

در مثالی دیگر می‌خواهیم از API موقعیت جغرافیایی کاربر در مرورگر یا navigator.geolocation استفاده کنیم. توسط این API هم می‌توان طول و عرض جغرافیایی را به دست آورد و هم تغییرات آن‌را تحت نظر قرار داد. برای مثال با بررسی این تغییرات می‌توان سرعت را نیز به دست آورد.
در این حالت نیز ابتدا با تعریف state مختص به آن شروع می‌کنیم و اینبار به عنوان مثال، مقدار اولیه‌ی آن‌را خارج از تابع جاری تنظیم می‌کنیم (جهت نمایش یک گزینه‌ی مهیای دیگر):
const initialLocationState = {
  latitude: null,
  longitude: null,
  speed: null
};

const App = () => {
  // ...
  const [location, setLocation] = useState(initialLocationState);
سپس جهت کار با این API، مقدار اولیه‌ی موقعیت کاربر و همچنین ردیابی تغییرات آن‌را در متد useEffect، تنظیم می‌کنیم:
  useEffect(() => {
    // ...

    navigator.geolocation.getCurrentPosition(handleGeolocation);
    const watchId = navigator.geolocation.watchPosition(handleGeolocation);

    return () => {
      // ...
      navigator.geolocation.clearWatch(watchId);
    };
  }, [count]);
در اینجا متد getCurrentPosition، با متد addEventListener متفاوت است و تنها یک callback function را دریافت می‌کند که در آن می‌توان setLocation را جهت به روز رسانی موقعیت جاری جغرافیایی کاربر، فراخوانی کرد. مشکلی که با این متد وجود دارد، نداشتن معادلی شبیه به متد removeEventListener است. یعنی API آن روشی را برای unmount آن فراهم نکرده‌است. البته متد watchPosition که تغییرات موقعیت کاربر را ردیابی می‌کند، دارای API مخصوص پاکسازی آن نیز هست که در این کدها ذکر شده‌است.

یک روش برای حل این مشکل و غیرفعال کردن دستی listener متد getCurrentPosition پس از unmount، تعریف یک متغیر mounted پیش از متد useEffect است:
  let mounted = true;

  useEffect(() => {
   // ...

    return () => {
      // ...
      mounted = false;
    };
  }, [count]);
و آن‌را در زمان پاکسازی useEffect، با false مقدار دهی می‌کنیم. اکنون زمانیکه می‌خواهیم رویدادگردان این Listener را تعریف کنیم، تنها بر اساس مقدار متغیر mounted عمل خواهیم کرد که از نشتی حافظه‌ی آن جلوگیری می‌کند:
  const handleGeolocation = event => {
    if (mounted) {
      setLocation({
        latitude: event.coords.latitude,
        longitude: event.coords.longitude,
        speed: event.coords.speed
      });
    }
  };
در آخر هم برای نمایش آن در متد رندر به صورت زیر عمل می‌کنیم:
<h2>Geolocation</h2>
<p>Latitude is {location.latitude}</p>
<p>Longitude is {location.longitude}</p>
<p>Your speed is {location.speed ? location.speed : "0"}</p>

سؤال: در اینجا شیء location چندین بار تکرار شده‌است. آیا می‌توان مقادیر خواص آن‌را توسط Object Destructuring نیز به دست آورد؟
پاسخ: بله. در اینجا هم Object Destructuring بر روی location state، کار می‌کند:
const [{ latitude, longitude, speed }, setLocation] = useState(
    initialLocationState
  );
که پس از آن، دیگر نیازی به تکرار شیء location نخواهد بود:
<h2>Geolocation</h2>
<p>Latitude is {latitude}</p>
<p>Longitude is {longitude}</p>
<p>Your speed is {speed ? speed : "0"}</p>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-01.zip
مطالب
مدیریت AccessViolationException در برنامه‌های دات نت 4 به بعد
فرض کنید که از یک برنامه‌ی native ویندوز برای تهیه تصاویر سایت‌ها در یک برنامه‌ی وب استفاده می‌کنید و صبح که به سایت سر زده‌اید پیام در دسترس نبودن سایت قابل مشاهده است. مشکل از کجا است؟!

یک مثال ساده

using System;

namespace AccessViolationExceptionSample
{
    class Program
    {
        private static unsafe void AccessViolation()
        {
            byte b = *(byte*)(8762765876);
        }

        static void Main(string[] args)
        {
            try
            {
                AccessViolation();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.WriteLine("Press a key...");
            Console.ReadKey();
        }
    }
}
برنامه‌ی کنسول فوق را پس از فعال سازی Allow unsafe code در قسمت تنظیمات پروژه، کامپایل کرده و سپس آن‌را خارج از VS.NET اجرا کنید. احتمالا انتظار دارید که قسمت catch این کد حداقل اجرا شود و سپس سطر «کلیدی را فشار دهید» ظاهر گردد. اما ... خیر! کل پروسه کرش کرده و هیچ پیام خطایی را دریافت نخواهید کرد. اگر به لاگ‌های ویندوز مراجعه کنید پیام زیر قابل مشاهده است:
 System.AccessViolationException.  Attempted to read or write protected memory.
 This is often an indication that other memory is corrupt.
و این نوع مسایل هنگام کار با کتابخانه‌های C و ++C زیاد ممکن است رخ دهند. نمونه‌ی آن استفاده از WebControl دات نت است یا هر برنامه‌ی native دیگری. در این حالت اگر برنامه‌ی شما یک برنامه‌ی وب باشد، عملا سایت از کار افتاده‌است. به عبارتی پروسه‌ی ویندوزی آن کرش کرده و بلافاصله از طرف ویندوز خاتمه یافته است.


چرا قسمت catch اجرا نشد؟

از دات نت 4 به بعد، زمانیکه دسترسی غیرمجازی به حافظه صورت گیرد، برای مثال دسترسی به یک pointer آزاد شده، استثنای حاصل، توسط برنامه catch نشده و اجازه داده می‌شود تا برنامه کلا کرش کند. به این نوع استثناءها Corrupted State Exceptions یا CSE گفته می‌شود. اگر نیاز به مدیریت آن‌ها توسط برنامه باشد، باید به یکی از دو طریق زیر عمل کرد:
الف) از ویژگی HandleProcessCorruptedStateExceptions بر روی متد فراخوان کتابخانه‌ی native باید استفاده شود. برای مثال در کدهای فوق خواهیم داشت:
   [HandleProcessCorruptedStateExceptions]
  static void Main(string[] args)
  {
ب) و یا فایل کانفیگ برنامه را ویرایش کرده و چند سطر ذیل را به آن اضافه کنید:
 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
     <legacyCorruptedStateExceptionsPolicy enabled="true" />
  </runtime>
</configuration>
در این حالت مدیریت اینگونه خطاها در کل برنامه همانند برنامه‌های تا دات نت 3.5 خواهد شد.
مطالب
آموزش فایرباگ - #8 - Script Panel
تب Script در FireBug مخصوص دیباگ کردن کدهای JavaScript است. امکاناتی که در این قسمت گنجانده شده بسیار کاربردی بوده و همچنین در بیشتر قسمت‌ها با ابزارهای خطایابی دیگر مشابه است. برای مثال اگر با Visual Studio کار کرده باشید، با نحوه‌ی ایجاد Break Point ، قسمت‌های Watch,Stack و ... آشنا خواهید بود.

این پنل هم مشابه پنل‌های دیگر فایرباگ دارای یک بخش با عنوان Options Menu هست که با راست کلیک کردن بروی عناون یا کلیک بروی مثلث کنار عنوان پنل قابل دسترسی است. تنظیماتی که در اینجا قابل تعیین است عبارتند از:


  • Enabled/Disabled : برای فعال/غیرفعال کردن پنل است. فعال بودن این قسمت ممکن است موجب کاهش سرعت بارگزاری صفحات شود.
  • Track Throw/Catch : در صورت فعال بودن این گزینه، در صورت رویدادن خطا در بلاک try/catch ، دیباگر در آن نقطه از برنامه متوقف شده و کنترل برنامه به شما داده می‌شود. (البته بنده موفق بررسی کامل این قابلیت نشدم. ظاهرا ورژن‌های آخری باگ دارند. مثل غیرفعال شدن کامل همین پنل، "Debugger not activated"! اگر کسی موفقیتی در کار با این مورد داشت، سپاسگذار میشوم اطلاع بدهد.)
  • Show Break Notifications : در صورت فعال بودن این گزینه، هنگام توقف کدی در صفحه، اطلاعاتی در مورد علت توقف آن در بالای پنل ارائه خواهد شد.
توجه داشته باشید که ممکن است در ورژن‌های مختلف، تعداد این گزینه‌ها بیشتر یا کمتر باشد.


 Panel Toolbar

نواری است که ابزارهای پنل بروی آن قرار دارند و به ترتیب عبارنتد از:
  • Break On Next : این دکمه که مشابه آن در اکثر پنل‌ها وجود دارد، هنگام اجرای یک دستور JavaScript آن را متوقف کرده و شما می‌توانید به بررسی آن بپردازید.
  • Script Type Menu : با انتخاب یکی از چهار گزینه موجود می‌توانید نتیجه اسکریپت‌های اضافه شده به صفحه که در قسمت Script Location Menu نمایش داده شده اند را فیلتر کنید. (متاسفانه این گزینه هم مشابه گزینه Track Throw/Catch برای بنده، ورژن 1.11.4 نتیجه ای نداشته است.)
  • Script Location Menu : در این قسمت اسکریپت هایی که در صفحه وجود دارند نمایش داده می‌شوند. مشابه پنل HTML می‌توانید هنگام بازبودن این لیست، با تایپ کردن نتایج را فیلتر کنید.
    همچنین با راست کلیک کردن بروی این قسمت می‌توانید آیتم یا فایل انتخاب شده را در ادیتور مورد نظر باز کنید، در پنل DOM بررسی کنید، فایل را در یک تب جدید باز کنید، آدرس فایل را در حافظه موقت کپی کنید.
  • Execution Control Buttons : این دکمه‌ها زمانی که دیباگر به یک Break Point برسد یا به دلیل فعال بودن دکمه‌های Break On ... متوقف شود، فعال می‌شوند و با کمک آن‌ها می‌توانید عملیات خطایابی را ادامه دهید.
    در جدول زیر این دکمه‌ها و توضیحات تکمیلی هریک را مشاهده می‌کنید:

     نوعدکمه
    Shortcut
    توضیحات
    اجرای مجدد

    Shift + F18
    Stack فعلی را مجدد اجرا می‌کند.
    (Stack: لیستی از توابع به ترتیبی که با فراخوانی آنها تابع جاری فراخونی شده است. در مقاله بعدی توضیحات بیشتری ارائه خواهد شد.)
    ادامه

    F8
    اجرای برنامه را (تا Break Point بعدی) ادامه می‌دهد.
    وارد شدن

    F11
    در صورت رسیدن به یک تابع، با زدن این دکمه دیباگر وارد بنده‌ی آن تابع می‌شود.
    رد شدن

    F10
    اجرای برنامه را در سطح (Scope) فعلی ادامه می‌دهد.
    مشابه قبلی وارد تابع نمی‌شود.
    خارج شدن

    Shift + F11
    کنترل به تابعی که تابع جاری را فراخوانی کرده است بازمیگردد.
    روشی سودمند زمانی که هنگام خطایابی وارد کدهای jQuery یا مثل آن می‌شوید :)
بعد از Script Location Menu قسمتی وجود دارد که وضعیت فعلی Stack را نشان می‌دهد و با کلیک بروی هرکدام، به کد آن قسمت هدایت می‌شوید. ( البته اگر فایرباگ شما در این قسمت باگ نداشته باشد! )

Context Menu
تنها قابلیت جدیدی که در این منو وجود دارد، Run To This Line است. هنگامی که پنل در حالت دیباگ است، با راست کلیک کردن بروی خط مورد نظر و انتخاب گزینه‌ی Run to This Line می‌توانید خطوط میانی را رد کرده و به خط مورد نظر بروید. البته خطوطی که رد می‌شوند ، اجرا می‌شوند.
این کار معادل این است که درخط مورد نظر یک Break Point اضافه کنید و دکمه‌ی F8 را بزنید.



 Breakpoints
توسط Break Point‌ها می‌توانید خطوطی از برنامه را برای خطایابی مشخص کنید. زمانی که دیباگر به نقطه‌ی مورد نظر برسد متوقف شده و می‌توانید به بررسی برنامه بپردازید. می‌توانید با بردن موس بروی متغییرها مقدار آن هارا بررسی کنید یا با کمک قسمت‌های Watch و Stack در پنل جانبی اطلاعات بیشتری در مورد اجرای برنامه در نقطه‌ی جاری بدست آورید.(در مورد پنل‌های جانبی در قسمت بعدی توضیح خواهم داد.) همچنین با کمک دکمه هایی که در جدول فوق توضیح داده شده اند روند اجرای برنامه را خط به خط ادامه دهید.
برای ایجاد یک Break Point، بروی شماره‌ی خط مورد نظر کلیک کنید. نقطه ای قرمز رنگ نمایش داده خواهد شد. البته دقت کنید که همه‌ی خطوط برنامه اجرا نمی‌شوند و نمی‌توانید در آن خطوط از این امکان بهره ببرید. شماره‌ی خطوط فعال با رنگ سبز مشخص شده اند.

امکان مفید دیگری که همراه با این قابلیت ارائه شده است (که در محیط‌های دیگر هم وجود دارد)، عبارت شرطی است. به این صورت که ضمن قرار دادن Break Point در یک نقطه از برنامه، می‌توانید یک شرط هم برای توقف برنامه تعیین کنید.
فرض کنید یک حلقه دارید که 300 بار تکرار می‌شود و مثلا در اجرای 250ام آن مشکلی وجود دارد. در این حالت می‌توانید از این قابلیت استفاده کنید و شرط توقف را i == 250 قرار بدهید.
برای تعیین یک شرط برای یک Break Point، بروی خط مورد نظر راست کلیک کنید و شرط را در قسمت مشخص شده وارد کنید.


امکان مفید دیگری که وجود دارد، Break Point خودکار است. اگر از مقالات قبلی دکمه‌ی Break On All Errors در پنل Console و Break On Mutate در پنل HTML را بخاطر داشته باشید می‌دانید که در هر یک هنگام اجرای یک رخداد مورد نظر، دیباگر در خطی که موجب آن رخداد شده است متوقف شده و می‌توانید کنترل برنامه را بدست بگیرید. در این حالت نیازی به ایجاد Break Point نیست و FireBug بصورت خودکار این کار را انجام می‌دهد.


 Search
این بخش در همه پنل‌ها وجود دارد با این تفاوت که در پنل Script و CSS دو گزینه‌ی Multiple Files و Use Regular Expression وجود دارد که به ترتیب امکان جستجو در فایل‌های js و css اضافه شده به صفحه هستند. قابلیت دیگری هم که فقط در پنل Script وجود دارد، پرش به یک خط در برنامه است به این صورت که با وارد کردن # و یک عدد به عنوان شماره‌ی خط، همان خط نمایش داده می‌شود.


در قسمت بعدی پنل جانبی که شامل سه بخش Watch، Stack و Breakpoints است را بررسی خواهیم کرد.
مطالب
ساخت قالب‌های نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC
گروه بندی دکمه‌ها در Twitter bootstrap

    <div class="btn-group" data-toggle="buttons-radio">
        <button class="btn" type="button">بلی</button>
        <button class="btn" type="button">خیر</button>
    </div>
در این مثال دو دکمه را ملاحظه می‌کنید که در یک div با کلاس btn-group محصور شده‌اند. به این ترتیب این دو دکمه در کنار هم، همانند دکمه‌های یک toolbar قرار خواهند گرفت. همچنین در بوت‌استرپ امکان انتساب ویژگی data-toggle=buttons-radio نیز به این div وجود دارد. در این حالت، این دکمه‌ها همانند دکمه‌های رادیویی رفتار خواهند کرد:

در ادامه قصد داریم یک Editor template و یک Display template مخصوص را جهت تدارک یک چنین دکمه‌هایی، برای مدیریت خواص Boolean ایجاد کنیم. به عبارتی اگر مدل برنامه چنین تعاریفی را داشت:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Mvc4TwitterBootStrapTest.Models
{
    public class User
    {
        public int Id { set; get; }

        [DisplayName("نام")]
        [Required(ErrorMessage="لطفا نام را تکمیل کنید")]
        public string Name { set; get; }

        [DisplayName("نام خانوادگی")]
        [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")]
        public string LastName { set; get; }

        [DisplayName("فعال است؟")]
        [UIHint("BootstrapBoolean")]
        public bool? IsActive { set; get; }
    }
}
فیلد nullable bool آن به صورت خودکار به شکل زیر رندر شود:


تهیه قالب ادیتور Views\Shared\EditorTemplates\BootstrapBoolean.cshtml

@model bool?
@{  
    var yesIsSelected = Model.HasValue && Model.Value ? "active" : null;
    var noIsSelected = Model.HasValue && !Model.Value ? "active" : null;
    var isIndeterminate = !Model.HasValue ? "active" : null;
    var htmlField = ViewData.TemplateInfo.HtmlFieldPrefix;   
}
@Html.HiddenFor(model => model)
<div class="btn-group" data-toggle="buttons-radio">
    <button type="button" class="btn btn-info @yesIsSelected bool-@htmlField" onclick="$('#@htmlField').val(true);">
        بلی</button>
    <button type="button" class="btn btn-info @noIsSelected bool-@htmlField" onclick="$('#@htmlField').val(false);">
        خیر</button>
    @if (ViewData.ModelMetadata.IsNullableValueType)
    {
        <button type="button" class="btn btn-info @isIndeterminate bool-@htmlField" onclick="$('#@htmlField').val('');">
            نامشخص</button> 
    }
</div>
سورس کامل فایل BootstrapBoolean.cshtml را که در مسیر Views\Shared\EditorTemplates باید کپی شود، در اینجا ملاحظه می‌کنید.
نوع اطلاعاتی که این قالب ادیتور پردازش خواهد کرد از نوع nullable bool است. البته مشکلی هم با نوع‌های bool معمولی ندارد. در حالت nullable، دکمه سومی را به نام «نامشخص» به مجموعه دکمه‌های «بلی» و «خیر» اضافه می‌کند. گاهی از اوقات در فرم‌های دریافت اطلاعات نیاز است بررسی کنیم آیا واقعا کاربر اطلاعاتی را انتخاب کرده یا اینکه بدون توجه به فیلدها، بر روی دکمه ارسال کلیک کرده است. در یک چنین حالتی تعریف دکمه‌های سه وضعیتی Boolean می‌تواند مفهوم پیدا کند.
در مورد اصول تهیه این قالب در ابتدای مطلب، با کلاس‌های btn-group و ویژگی data-toggle آشنا شدید. دقیقا این سه دکمه نیز در اینجا به همین نحو تعریف شده‌اند.
در ابتدای نمایش یک View، خصوصا در حالت ویرایش اطلاعات، نیاز است اطلاعات موجود، به دکمه‌های تعریف شده اعمال شوند. در اینجا برای انتخاب یک دکمه، باید کلاس active به آن نسبت داده شود، که نحوه تدارک آن‌را در سه متغیر yesIsSelected، noIsSelected و isIndeterminate ابتدای تعاریف قالب مشاهده می‌کنید.
سپس یک فیلد مخفی به صفحه اضافه شده است. از این جهت که به کمک jQuery، در حین کلیک بر روی یکی از دکمه‌ها، مقدار آن‌را به این فیلد که نهایتا به سرور ارسال خواهد شد، اعمال خواهیم کرد.


تهیه قالب نمایشی Views\Shared\DisplayTemplates\BootstrapBoolean.cshtml

@model bool?
@if (Model.HasValue)
{
    if (Model.Value)
    {
    <span class="label label-success">بلی</span> 
    }
    else
    {
    <span class="label label-important">خیر</span> 
    }
}
else
{ 
    <span class="label label-inverse">نامشخص</span> 
}
در حالت صرفا نمایشی، فایل قالب BootstrapBoolean.cshtml قرار گرفته در مسیر Views\Shared\DisplayTemplates، یک چنین تعاریفی را خواهد داشت.

و نهایتا برای استفاده از آن تنها کافی است توسط ویژگی UIHint، نام این قالب، به خاصیت Boolean مدنظر اعمال شود:
[UIHint("BootstrapBoolean")]
public bool? IsActive { set; get; }

مطالب
React 16x - قسمت 4 - کامپوننت‌ها - بخش 1 - کار با عبارات JSX
برپایی پروژه‌ی ایجاد اولین کامپوننت React

در اینجا برای بررسی مقدماتی کامپوننت‌ها، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-04
> cd sample-04
> npm start
اکنون در ادامه اولین کاری را که انجام می‌دهیم، نصب توئیتر بوت استرپ 4 است تا بتوانیم توسط امکانات آن، ظاهر بهتری را برای برنامه‌ی تهیه شده تدارک ببینیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های `+ctrl را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
این دستور علاوه بر نصب بوت استرپ 4.3.1 (آخرین نگارش موجود در زمان نگارش این مطلب)، به دلیل ذکر سوئیچ save، مدخل آن‌را نیز به فایل package.json برنامه اضافه می‌کند.
پس از اجرای این دستور، ممکن است پیام‌های اخطاری مانند «requires a peer of jquery@1.9.1 - 3 but none is installed» را نیز مشاهده کنید که مهم نیستند. چون در اینجا صرفا از امکانات CSS ای بوت استرپ استفاده خواهیم کرد و کاری با jQuery نداریم. محل نصب آن نیز پوشه‌ی node_modules\bootstrap برنامه است.

سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.


ایجاد اولین کامپوننت React

در پوشه‌ی src برنامه، پوشه‌ی جدیدی را به نام components ایجاد می‌کنیم و تمام کامپوننت‌های خود را در آن قرار خواهیم داد. سپس داخل این پوشه، یک فایل جدید و خالی را به نام counter.jsx ایجاد می‌کنیم. پسوند این فایل jsx است و نام فایل‌های کامپوننت‌ها را نیز camel case وارد می‌کنیم؛ یعنی اولین حرف اولین واژه‌ی وارد شده، با حروف کوچک و تمام واژه‌های پس از آن با حروف بزرگ شروع خواهند شد مانند coolApp. مزیت استفاده‌ی از پسوند jsx نسبت به js، فراهم شدن امکانات مخصوص React در VSCode است.
در ابتدای فایل counter.jsx، نیاز است وابستگی‌های React را import کنیم. اگر از قسمت اول بخاطر داشته باشید، «simple react snippets» را نیز در VSCode نصب کردیم. به کمک آن می‌تواند این نوع importها را ساده‌تر وارد کرد. برای این منظور imrc را تایپ کرده و سپس دکمه‌ی tab را فشار دهید.  به این ترتیب یک سطر زیر به صورت خودکار تولید می‌شود:
import React, { Component } from 'react';
پس از این سطر، cc را تایپ کرده و سپس دکمه‌ی tab را فشار دهید تا ساختار کلاس یک کامپوننت React تولید شود. همان لحظه‌ای که این ساختار تولید می‌شود، اگر دقت کنید دو کرسر در صفحه ظاهر شده‌اند که با تایپ نام کامپوننت، نام کلاس و نام export آن‌را تکمیل می‌کنند. نام این کامپوننت را Counter که با حروف بزرگ شروع می‌شود، وارد می‌کنیم. اکنون کدهای آن را به نحو زیر ویرایش می‌کنیم:
import React, { Component } from "react";

class Counter extends Component {
  render() {
    return <h1>Hello world!</h1>;
  }
}

export default Counter;
مفهوم ساختار یک چنین کلاس و export ای را در قسمت قبل با معرفی کلاس‌ها، ارث بری، ماژول‌ها و همچنین exportهای آن‌ها بررسی کردیم. البته در قسمت قبل، export default class را مشاهده کردید و در اینجا بجای آن، سطر آخر این ماژول به export default ختم شده‌است که روش دیگری برای تعریف این export است.
خروجی متد render در اینجا، یک رشته‌ی معمولی نیست؛ بلکه یک عبارت jsx است که در قسمت اول معرفی شد. این عبارت در نهایت توسط کامپایلر Babel به معادل React.createElement ترجمه می‌شود. به همین جهت نیاز است تا import React را در ابتدای این ماژول درج کرد؛ هرچند به ظاهر به صورت مستقیم از آن استفاده نمی‌کنیم.

تا اینجا این کامپوننت در UI برنامه نمایش داده نمی‌شود. به همین جهت به فایل index.js مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
- ابتدا نیاز است تا شیء Counter را در اینجا import کنیم و چون خروجی پیش‌فرض است، نیازی به ذکر {} برای معرفی آن نیست:
import Counter from "./components/counter";
- سپس در سطر ReactDOM.render، بجای رندر کامپوننت App، کامپوننت Counter را ذکر می‌کنیم:
 ReactDOM.render(<Counter />, document.getElementById("root"));
اکنون برنامه هر زمانیکه به المان جدید Counter برسد، بجای آن به متد render کامپوننت متناظر مراجعه کرده و خروجی آن‌را رندر می‌کند. پس از این تغییر اگر به مرورگر مراجعه کنید، خروجی hello world را مشاهده خواهید کرد.


درج چند عنصر در عبارات JSX

می‌خواهیم در کامپوننت Counter، یک دکمه را نیز نمایش دهیم. برای انجام اینکار، به نحو زیر عمل می‌کنیم:
  render() {
    return <h1>Hello world!</h1><button>Increment</button>;
  }
در این حالت هم در VSCode و هم در کنسول توسعه دهندگان مرورگر، خطای «JSX expressions must have one parent element» ظاهر می‌شود.
عبارات JSX در نهایت باید تبدیل به متد React.createElement شوند. اولین پارامتر این متد، نوع المانی است که قرار است ایجاد شود که در اینجا h1 است. اما در اینجا دو المان را داریم. در این حالت Babel نمی‌داند که چگونه باید یک چنین عبارتی را به React.createElement ترجمه کند. یک راه حل این است که کل این عبارت را داخل یک div قرار داد:
  render() {
    return (
      <div>
        <h1>Hello world!</h1>
        <button>Increment</button>
      </div>
    );
در اینجا فرمت چند سطری return، توسط افزونه‌ی Prettier که در قسمت اول معرفی شد، پس از ذخیره‌ی فایل، به صورت خودکار اعمال شده‌است. همچنین اگر دقت کنید یک () جدید را نیز مشاهده می‌کنید. علت آن مقابله با automatic semicolon insertion است (درج ; خودکار). در جاوا اسکریپت اگر یک return را داشته باشید و پس از آن در همان سطر، چیزی درج نشده باشد، یک سمی‌کالن را به صورت خودکار درج/تفسیر می‌کند. به این ترتیب عبارت JSX چند سطری درج شده‌ی در سطرهای بعد از return، دیده نخواهد شد؛ یعنی چیزی شبیه به عبارات زیر تفسیر می‌شود:
return ;
  <div></div>
برای رفع این مشکل باید دقیقا جلوی واژه‌ی کلیدی return، یک پرانتز را باز کرد و آن‌را پس از خاتمه‌ی عبارت JSX، بست (که البته افزونه‌ی Prettier اینکار را به صورت خودکار برای شما انجام می‌دهد):
return (
  <div></div>
);
نکته 1: بدیهی است زمانیکه المان‌های درج شده را درون یک div محصور کردیم، به همین نحو نیز در DOM اصلی ظاهر خواهند شد. اگر علاقمند نیستید که این div در خروجی نهایی رندر شود، می‌توان بجای آن از React.Fragment استفاده کرد که هیچ نوع المان اضافه‌تری را در DOM بجای آن درج نمی‌کند:
    return (
      <React.Fragment>
        <h1>Hello world!</h1>
        <button>Increment</button>
      </React.Fragment>
    );

نکته 2: در VSCode برای ویرایش همزمان ابتدا و انتهای یک تگ (برای مثال ویرایش همزمان عبارت div در اینجا و تبدیل آن به React.Fragment در دو قسمت)، عبارت آن تگ را انتخاب کرده و سپس دکمه‌های ctrl+d را فشار دهید تا بتوانید همزمان هر دو عبارت انتخاب شده را با هم ویرایش کنید. به اینکار multi-cursor editing می‌گویند.


نمایش پویای اطلاعات در عبارات JSX

در ادامه بجای نمایش عبارت ثابت «Hello world»، می‌خواهیم آن‌را به صورت پویا تنظیم کنیم. برای این منظور یک خاصیت جدید را در کلاس جاری، به نام state تعریف کرده و آن‌را با یک شیء، مقدار دهی می‌کنیم. state، یک خاصیت ویژه در کامپوننت‌های React است و بیانگر داده‌هایی است که آن کامپوننت نیاز دارد. این داده‌ها می‌توانند یک key/value ساده باشند و یا حتی value تعریف شده نیز می‌تواند یک شیء پیچیده باشد.
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span>{this.state.count}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }
}

export default Counter;
در اینجا خاصیت state را با شیءای که حاوی key/value مساوی count با مقدار صفر است، مقدار دهی کرده‌ایم. سپس برای نمایش این اطلاعات در عبارت JSX، از یک {} استفاده می‌شود. داخل {}‌ها می‌توان هر نوع عبارت مجاز جاوا اسکریپتی را درج کرد. برای مثال با this شروع می‌کنیم که بیانگر اشاره‌گری به وهله‌ای از شیء جاری است. سپس می‌توان توسط آن به خاصیت state و سپس کلید count شیء منتسب به آن دسترسی یافت. به این ترتیب عدد صفر، در کنار دکمه‌ای با برچسب Increment، در مرورگر ظاهر خواهد شد.

همانطور که عنوان شد در بین {}‌ها می‌توان هر نوع عبارت مجاز جاوا اسکریپتی را ذکر کرد و عبارت چیزی است که مقداری را بازگشت می‌دهد. بنابراین عبارتی مانند {2+2} را نیز می‌توان در اینجا بکار برد و یا حتی در اینجا می‌توان متدی را فراخوانی کرد که مقداری را بازگشت می‌دهد:
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }

  formatCount() {
    const { count } = this.state; // Object Destructuring
    return count === 0 ? "Zero" : count;
  }
}

export default Counter;
در این مثال می‌خواهیم اگر مقدار count مساوی صفر بود، بجای عدد صفر، واژه‌ی Zero را نمایش دهد. به همین جهت این منطق را به یک متد مانند formatCount منتقل کرده و سپس آن‌را به صورت {()this.formatCount}، فراخوانی کرده و نمایش می‌دهیم.
در متد formatCount حتی می‌توان عبارات JSX را نیز بجای یک رشته‌ی ساده، بازگشت داد:
  formatCount() {
    const { count } = this.state; // Object Destructuring
    return count === 0 ? <h1>Zero</h1> : count;
  }


مقدار دهی ویژگی‌های عناصر در عبارات JSX

فرض کنید یک المان img را به عبارت JSX کلاس Counter اضافه کرده‌ایم. اکنون می‌خواهیم ویژگی src آن‌را مقدار دهی کنیم. در اینجا هر چیزی که بین "" قرار گیرد، به صورت یک رشته‌ی ثابت پردازش می‌شود. برای تنظیم آن به یک متغیر، ابتدا خاصیت state را به صورت زیر جهت درج imageUrl، ویرایش می‌کنیم:
  state = {
    count: 0,
    imageUrl: "/logo192.png"
  };
پس از آن عبارت مقدار خاصیت this.state.imageUrl را توسط یک {}، به ویژگی src تصویر نسبت می‌دهیم:
  render() {
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }

مقدار دهی class و style المان‌ها، نسبت به مقدار دهی attributes که مشاهده کردید، اندکی متفاوت است؛ از این جهت که در نهایت یک عبارت JSX توسط کامپایلر Babel به معادل جاوا اسکریپتی آن ترجمه می‌شود و اگر در اینجا به عنوان مثال از ویژگی class استفاده شود، چون نام class، یک نام و واژه‌ی کلیدی از پیش معین شده‌ی جاوا اسکریپتی است، امکان استفاده‌ی از آن در اینجا وجود ندارد. به همین جهت در React برای تنظیم ویژگی class عناصر، از className استفاده می‌شود:
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span className="badge badge-primary m-2">{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </React.Fragment>
    );
در اینجا اعمال یک سری از کلاس‌های بوت استرپ را که در ابتدای مطلب به پروژه اضافه کردیم، به ویژگی‌های className المان‌های span و button مشاهده می‌کنید.
تا اینجا اگر فایل کامپوننت Counter را ذخیره کنید، خروجی ذیل در مرورگر ظاهر خواهد شد:


روش مقدار دهی ویژگی style نیز متفاوت است. در اینجا React انتظار دارد تا شیءای را که به صورت زیر تشکیل می‌شود:
  styles = {
    fontSize: 50,
    fontWeight: "bold"
  };
به صورت {this.styles} به ویژگی style انتساب دهیم:
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span style={this.styles} className="badge badge-primary m-2">
          {this.formatCount()}
        </span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </React.Fragment>
    );
نحوه‌ی تشکیل خاصیت styles، بر اساس ذکر خواص CSS، به صورت خواصی camel-case است؛ مانند fontSize. در اینجا عدد 50 توسط react به صورت خودکار به 50px تبدیل می‌شود.
اعمال این styles نمونه، یک چنین خروجی را به همراه خواهد داشت:


مزیت تعریف شیء styles به صورت یک خاصیت در کلاس، امکان استفاده‌ی مجدد از آن در سایر المان‌ها است. اگر چنین چیزی مدنظر شما نیست، می‌توان این شیء را به صورت inline هم تعریف کرد:
<button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
در اینجا، ابتدا یک {} درج می‌شود تا بیانگر نمایش دریافت یک عبارت معتبر جاوا اسکریپتی باشد. سپس داخل آن یک {} دیگر نیز قرار گرفته‌است که بیانگر تعریف یک شیء جاوا اسکریپتی است و در این حالت باید با نحوه‌ی تشکیل عناصر شیء style مورد نظر React که به صورت caml-case هستند، تطابق داشته باشد.


مقدار دهی پویای ویژگی className عناصر در عبارات JSX

در ادامه می‌خواهیم اگر مقدار count مساوی صفر بود، span ای که هم اکنون با یک badge آبی (با کلاس badge-primary) نمایش داده می‌شود، زرد رنگ (با کلاس badge-warning) شود و در غیراینصورت آبی رنگ. بنابراین می‌خواهیم بر اساس مقدر count، مقدار کلاس‌های انتسابی به className را به صورت پویا تغییر دهیم و این الگویی است که در برنامه‌های واقعی بسیار استفاده می‌شود:
  render() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";

    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span style={this.styles} className={classes}>
          {this.formatCount()}
        </span>
        <button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
          Increment
        </button>
      </React.Fragment>
    );
برای این منظور متغیر classes را تعریف کرده‌ایم که در ابتدا با مقادیری که ثابت هستند، مقدار دهی شده‌اند. سپس بر اساس مقدار this.state.count، مقدار مشخص warning و یا primary به این رشته افزوده می‌شود. در آخر هم از این متغیر به صورت className={classes} استفاده شده‌است؛ با این خروجی:


البته باید دقت داشت که می‌توان منطق تشکیل متغیر classes را به یک متد، جهت خلوت سازی متد render نیز منتقل کرد. برای این کار، دو سطر مرتبط با متغیر classes را در VSCode انتخاب کنید. سپس یک آیکن لامپ مانند ظاهر می‌شود که با کلیک بر روی آن، منوی extract to method نیز قابل انتخاب است:
  render() {
    let classes = this.getBadgeClasses();
    // ...
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }
البته در اینجا می‌توان متغیر classes را نیز حذف کرد و مستقیما متد getBadgeClasses را مورد استفاده قرار داد:
<span style={this.styles} className={this.getBadgeClasses()}>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-04-part01.zip
اشتراک‌ها
کتابخانه FlipBook

Create Realistic 3D jQuery Flipbook & WordPress Flipbook Plugin like never before!!
As easy as simple HTML or WordPress Posts, yet powerful & amazing as a 3D Presentation.

کتابخانه FlipBook