پاسخ به پرسش‌ها
پابلیش پروژه DNTIdentity و بهینه سازی فایل جاوااسکریپت

با توجه به آیکون و آواتار پوشه فایل ها ارسال شده اند ولی فایل های css , js فشرده نشده نمایش داده می شود، البته در حالت دیباگ مشکلی وجود ندارد ولی بعد از ریلیز و پاپلیش دچار مشکل می شه؟

مطالب
بررسی روش تعریف انقیاد دو طرفه‌ی سفارشی در کامپوننت‌های Angular
برخلاف AngularJS، در برنامه‌های Angular امکانات two way data binding به صورت پیش‌فرض ارائه نمی‌شوند تا از تمام مشکلات آن مانند digest cycle ،watchers و غیره خبری نباشد. اما گاهی از اوقات نیاز است انقیاد دو طرفه‌ی سفارشی را بین دو کامپوننت ایجاد کنیم. در این مطلب روش ایجاد یک چنین انقیادهایی را بررسی خواهیم کرد و در اینجا در ابتدا نیاز است دو پیشنیاز Property Binding و Event Binding را بررسی کنیم که از جمع آن‌ها two way data binding حاصل می‌شود:


البته Angular به همراه دایرکتیو ویژه‌ای به نام ngModel است که two-way data binding را با import ماژول ویژه‌ی فرم‌ها میسر می‌کند:


که آن نیز در اصل از جمع Property Binding و Event Binding تشکیل شده‌است:
<input [ngModel]="username" (ngModelChange)="username = $event">
و یا به صورت خلاصه:
<input [(ngModel)]='username' />
در اینجا می‌خواهیم یک چنین امکانی را بدون استفاده از ngModel و ماژول فرم‌ها پیاده سازی کنیم.


انقیاد به خواص یا Property binding

فرض کنید دو کامپوننت والد و فرزند را ایجاد کرده‌ایم:


در کامپوننت والد، مقداری را توسط متد deposit هربار 100 آیتم افزایش می‌دهیم:
import { Component, OnInit } from "@angular/core";

@Component({
  selector: "app-parent",
  templateUrl: "./parent.component.html",
  styleUrls: ["./parent.component.css"]
})
export class ParentComponent implements OnInit {

  amount = 500;

  constructor() { }

  ngOnInit() {
  }

  deposit() {
    this.amount += 100;
  }
}
با این قالب:
<h2>Custom two way data binding</h2>

<div class="panel panel-primary">
  <div class="panel-heading">
    <h2 class="panel-title">Parnet Component</h2>
  </div>
  <div class="panel-body">
    <label>Available amount:</label> {{amount}}
    <button (click)="deposit()" class="btn btn-success">Deposit 100</button>
    <div>
      <app-child [amount]="amount"> </app-child>
    </div>
  </div>
</div>
که در آن مقدار amount کامپوننت والد نمایش داده شده‌است و همچنین این مقدار به خاصیت ورودی کامپوننتی به نام app-child نیز نسبت داده شده‌است.

کامپوننت فرزند به صورت ذیل تعریف می‌شود:
import { Component, OnInit, Input } from "@angular/core";

@Component({
  selector: "app-child",
  templateUrl: "./child.component.html",
  styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {

  @Input() amount: number;

  constructor() { }

  ngOnInit() {
  }

  withdraw() {
    this.amount -= 100;
  }
}
که در آن خاصیت amount، از والد آن، توسط ویژگی Input دریافت می‌شود. سپس در متد withdraw هربار می‌توان 100 آیتم را از آن کسر کرد.
با این قالب:
<div class="panel panel-default">
  <div class="panel-heading">
    <h2 class="panel-title">Child Component</h2>
  </div>
  <div class="panel-body">
    <label>Amount available: </label> {{amount}}

    <button (click)="withdraw()" class="btn btn-danger">Withdraw 100</button>
  </div>
</div>
که در آن مقدار amount فرزند نمایش داده شده‌است و همچنین امکان فراخوانی متد withdraw وجود دارد.

در اینجا زمانیکه data binding را به صورت ذیل تعریف می‌کنیم:
<app-child [amount]="amount"> </app-child>
روش مقدار دهی خاصیت amount داخل [] ، انقیاد به خواص نامیده می‌شود و سمت راست آن نیز یک خاصیت درنظر گرفته می‌شود. یعنی مقدار خاصیت amount والد (درون "") به مقدار خاصیت amount فرزند (درون []) نسبت داده خواهد شد.
این ارتباط نیز یک طرفه‌است. برای مثال اگر بر روی دکمه‌ی Deposit والد کلیک کنیم:


مقدار افزایش یافته‌ی در والد، به فرزند نیز منتقل می‌شود و نمایش داده خواهد شد. اما اگر بر روی دکمه‌ی withdraw فرزند کلیک کنیم:


تغییر صورت گرفته، به والد انعکاس پیدا نمی‌کند. برای اطلاع رسانی به والد، به انقیاد به رخ‌دادها نیاز داریم.


انقیاد به رخ‌دادها یا Event binding

یک کامپوننت می‌تواند به رخ‌دادهای صادر شده‌ی توسط کامپوننتی دیگر گوش فرا دهد:
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";

@Component({
  selector: "app-child",
  templateUrl: "./child.component.html",
  styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {

  @Input() amount: number;
  @Output() amountChange = new EventEmitter();

  constructor() { }

  ngOnInit() {
  }

  withdraw() {
    this.amount -= 100;
    this.amountChange.emit(this.amount);
  }
}
برای این منظور در کامپوننت فرزند، یک خاصیت Output را به نام amountChange از نوع EventEmitter تعریف می‌کنیم. سپس جایی که قرار است کار کاهش amount صورت گیرد، با صدور رخ‌دادی (this.amountChange.emit)، این مقدار را به والد اعلام می‌کنیم.
اکنون در قالب کامپوننت والد، این رخ‌داد را درون یک () معرفی خواهیم کرد:
<app-child [amount]="amount" (amountChange)="this.amount= $event"> </app-child>
به این ترتیب زمانیکه کامپوننت فرزند، مقدار amount را تغییر می‌دهد، این مقدار توسط this.amountChange.emit به والد منتشر خواهد شد و می‌توان در سمت والد توسط event$ به آن دسترسی یافته و آن‌را به خاصیت this.amount کامپوننت والد نسبت دهیم.
اکنون اگر برنامه را آزمایش کنیم، با کلیک بر روی دکمه‌ی withdraw فرزند، مقدار کاهش یافته به والد نیز منعکس می‌شود:



پیاده سازی syntax ویژه‌ی Banana in a box

تا اینجا پیاده سازی two way data-binding سفارشی به پایان می‌رسد. اما تعریف طولانی:
<app-child [amount]="amount" (amountChange)="this.amount= $event"> </app-child>
به صورت ذیل هم قابل نوشتن و ساده سازی است:
<app-child [(amount)]="amount"> </app-child>
که به آن syntax ویژه Banana in a box نیز گفته می‌شود.
نکته‌ی ویژه‌ی آن، وجود پسوند Change در نام رخ‌داد تعریف شده‌است:
  @Input() amount: number;
  @Output() amountChange = new EventEmitter();
 اگر نام خاصیت Input مساوی x باشد، باید جهت فعالسازی syntax ویژه Banana in a box، نام رخ‌داد متناظر با آن دقیقا مساوی xChange انتخاب شود. مانند amount ورودی در اینجا و amountChange خروجی تعریف شده.

بنابراین به صورت خلاصه جهت تعریف یک انقیاد دو طرفه سفارشی:
- ابتدا باید انقیاد به یک خاصیت ورودی x را تعریف کرد.
- سپس نیاز است انقیاد به یک رخ‌داد خروجی هم‌نام، که نام آن، پسوند Change را اضافه‌تر دارد، یعنی xChange را تعریف کرد.
- اکنون می‌توان two-way data binding syntax ویژه‌ای را به نام banana in a box بر روی این‌دو تعریف کرد[(x)].


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
مطالب
اضافه کردن پیوست به فایل‌های PDF با استفاده از iTextSharp

فایل PDF موجود عجیب و غریبی است. می‌شود به آن فایل پیوست اضافه کرد. مثلا اگر یک راهنمای آموزشی را با فرمت PDF تهیه می‌کنید، لازم نیست تا فایل‌های مرتبط با آن‌را جداگانه ارائه دهید. می‌شود تمام این‌ها را داخل همان فایل PDF مدفون کرد. روش انجام اینکار به کمک iTextSharp ساده است اما چند نکته را نیز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

pdfDoc.Add(new Phrase("Test"));

var fs = PdfFileSpecification.FileEmbedded(pdfWriter, @"C:\path\logo.png", "logo.png", null);
pdfWriter.AddFileAttachment("توضیحات",fs);
}

Process.Start("Test.pdf");
}
}
}

در ساده‌ترین حالت ممکن، با استفاده از متد AddFileAttachmen شیء PdfWriter می‌توان پیوستی را به یک فایل PDF در حال تولید اضافه کرد. اگر به فایل نهایی مراجعه کنیم و همچنین قسمت attachments را هم دستی در Adobe reader انتخاب نمائیم، شکل زیر حاصل خواهد شد:


روش متداول بکارگرفته شده دو مشکل را به همراه دارد:
  • قسمت modified مقدار دهی نشده است.
  • پنل مربوط به پیوست‌ها باید دستی باز شود.

نحوه مقدار دهی ستون modified پس از تعریف یک PdfDictionary و قرار دادن PdfName.MODDATE در آن، به صورت زیر است:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

pdfDoc.Add(new Phrase("Test"));

var filePath = @"C:\path\logo.png";
var fileInfo = new FileInfo(filePath);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
pdfWriter.AddFileAttachment("توضیحات", fs);
}

Process.Start("Test.pdf");
}
}
}

که اینبار خروجی زیر را به همراه دارد:


و برای نمایش خودکار پنل پیوست‌ها در Adobe reader به طوری که کاربر نهایی متوجه وجود این فایل‌های پیوست شده گردد، می‌توان ViewerPreferences شیء pdfWriter را مقدار دهی نمود:

pdfWriter.ViewerPreferences = PdfWriter.PageModeUseAttachments;

در مورد فایل‌های موجود چطور؟ آیا می‌توان به یک فایل PDF از پیش تهیه شده هم فایل پیوست کرد؟
پاسخ: بله. باید از امکانات شیء PdfReader استفاده کرد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
var reader = new PdfReader("Test.pdf");
using (var stamper = new PdfStamper(reader, new FileStream("newTest.pdf", FileMode.Create)))
{
var filePath = @"C:\path\logo.png";
addAttachment(stamper, filePath, "توضیحات");
stamper.Close();
}

Process.Start("newTest.pdf");
}

private static void addAttachment(PdfStamper stamper, string filePath, string description)
{
var fileInfo = new FileInfo(filePath);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
var pdfWriter = stamper.Writer;
var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
stamper.AddFileAttachment(description, fs);
}
}
}

در اینجا به کمک کلاس PdfReader، یک فایل موجود خوانده شده و سپس با استفاده از امکانات کلاس PdfStamper که خاصیت Writer آن همان pdfWriter است می‌توان فایل مورد نظر را به فایل موجود افزود.


مطالب
نحوه پیکربندی سرور شیرپوینت 2013 برای نصب app از Office Store
از ویژگی‌های جدید و البته جالب شیرپوینت 2013 امکان استفاده از App‌ها می‌باشد. برای شناخت بیشتر app‌ها پیشنهاد می‌کنم به MSDN  مراجعه کنید. در این پست قصد دارم مراحل استفاده از SharePoint Marketplace مایکروسافت را برای دریافت و نصب app در سرور شیرپوینت و طریقه پیکر بندی سروربیان کنم.
اگر برای بار اول بخواهید یک app را روی سرور شیرپوینت نصب کنید ممکن است این پیغام به شما نمایش داده شود :
Sorry, apps are turned off. If you know who tuns the server, tell them to enable apps.


دقت کنید که کم رنگ بودن آیکون App به معنی عدم پشتیبانی در سرور شیرپوینت شما است و در صورت تلاش برای نصب آن این پیغام را خواهید دید :

دلیل این پیغام ( apps are turned off) تنظیم نبودن سرور شیرپوینت (Front-End) برای پشتیبانی و میزبانی از App‌ها می‌باشد . برای استفاده از app‌ها در شیرپوینت نیازمند یک sub-domain و دیگر تنظیمات هستید تا بتوانید از app‌ها استفاده کنید . برای این منظور مراحل زیر را پی بگیرید :
وارد سایت Office Store مایکروسافت شده و app مورد نظر خود را بیابید . در اینجا من از app‌های رایگان1 مورد را انتخاب می‌کنم و با آن شروع می‌کنم : نمایش وضعیت آب و هوا در شیرپوینت .

روی Add کلیک کنید تا جزییات app و شناسه آن نمایش داده شود . سپس آن شناسه را کپی کنید : ( شناسه app مذکور WA103062091 است )

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

در پنجره باز شده شناسه app را paste کنید و جستجو را آغاز کنید :
  

باید در نتیجه جستجو نمایش داده شود که app در SharePoint Store یافت شد
  

  روی لینک کلیک کنید تا نتیجه جستجو در Store نمایش داده شود :
توجه داشته باشید که در صفحه باز شده حتما یک واحد پولی و یک زبان را انتخاب نمایید .

ودر این مرحله خطای مذکور که گفته شد نمایش داده می‌شود :  

حال به بیان راه حل می‌پردازیم :
برای استفاده از App‌ها در شیرپوینت باید سرویس‌های مرتبط و زیر دامنه (CNAME) سرور مرتبط برای آن تنظیم شده باشد .
برای این منظور ابتدا تنظیمات DNS را انجام می‌دهیم :

 
برای دامنه جاری یک CNAME تعریف کنید : 

Alias Name پنجره فوق به این معنا است که تمام app‌ها در مسیری با فرمت زیر مدیریت می‌شوند :
AppID.app.vm-seifollahi.iri
اگر به جای *.app فقط * قرار دهید ، هر شناسه app به عنوان زیر دامنه آدرس دهی می‌شود که در کل تفاوتی ندارد و برای مشخص شدن بهتر این کار را انجام دادم .
برای چک کردن صحت تنظیمات خود روی مسیری مانند Apps-12345678ABCDEF.app.vm-seifollahi.iri دستور ping را اجرا نمایید.
  
پس از تایید این تنظیمات باید وارد CA شوید و سرویس‌ها را تنظیم کنید : باید دو سرویس App Management Service و Subscription Setting Service در وضعیت Started باشند .

  پس از چک کردن سرویس‌ها باید تنظیمات مربوط به App Pool‌های IIS و دیتابیس برای App Managemetn Service و Subscription Service تنظیم شود . برای این منظور از Power Shell کمک می‌گیریم و دستورات زیر را در آن اجرا می‌کنیم (توضیحات در کامنت‌ها وجود دارند ) :


$account = Get-SPManagedAccount "vmseifollahi\administrator
# Gets the name of the managed account and sets it to the variable $account for later use.

$appPoolSubSvc = New-SPServiceApplicationPool -Name SettingsServiceAppPool -Account $account
# Creates an application pool for the Subscription Settings service application. 
# Uses a managed account as the security account for the application pool.
# Stores the application pool as a variable for later use.

 
$appPoolAppSvc = New-SPServiceApplicationPool -Name AppServiceAppPool -Account $account
# Creates an application pool for the Application Management service application. 
# Uses a managed account as the security account for the application pool.
# Stores the application pool as a variable for later use.

 
$appSubSvc = New-SPSubscriptionSettingsServiceApplication –ApplicationPool $appPoolSubSvc –Name SettingsServiceApp –DatabaseName MBS_SettingsServiceDB
# Creates the Subscription Settings service application, using the variable to associate it with the application pool that was created earlier.
# Stores the new service application as a variable for later use.
$proxySubSvc = New-SPSubscriptionSettingsServiceApplicationProxy –ServiceApplication $appSubSvc
# Creates a proxy for the Subscription Settings service application.

 
$appAppSvc = New-SPAppManagementServiceApplication -ApplicationPool $appPoolAppSvc -Name AppServiceApp -DatabaseName MBS_AppServiceDB
# Creates the Application Management service application, using the variable to associate it with the application pool that was created earlier.
# Stores the new service application as a variable for later use.

 
$proxyAppSvc = New-SPAppManagementServiceApplicationProxy -ServiceApplication $appAppSvc
# Creates a proxy for the Application Management service application.
 

پس از نصب مشاهده میکنید که دیتابیس‌ها با موفقیت نصب شدند :
  

حال به CA رفته ( DOMAIN/_admin/ServiceApplications.aspx ) و از Start بودن سرویس‌های تنظیم شده اطمینان پیدا کنید : (از همین صفحه نیز می‌توانید تنظیماتی که قبلا در power shell انجام شد را انجام دهید)
 

حال در CA به صفحه Apps می‌رویم :

و روی Configure App URL کلیک کنید :

در صورتی که پیغام زیر را مشاهده کردید ، IIS را باز کنید :
 

در قسمت Application Pools به دنبال SharePoint Web Service Root بگردید و آن را Start نمایید :

حال صفحه تنظیمات باز می‌شود . مقادیر domain و prefix را تنظیم کنید :

سپس روی OK کلیک کنید در این مرحله تنظیمات سرور شیرپوینت تمام شد و باید به ترتیب زیر آنها را restart کنید :

ابتدا SharePoint Timer service را Stop کنید.
سپس سرویس IIS را Restart کنید
حال SharePoint Timer service را Start کنید .

اکنون مراحل را مجدد از سر بگیرید یعنی روی منوی تنظیمات سایت و روی add App کلیک کنید و app را جستجو کنید و مراحل نصب را اجرا کنید تا به مرحله Add کردن app برسید . حال مشاهده می‌کنید که دکمه فعال بوده و می‌توانید آن را نصب کنید :
  
 

پس از کلیک روی add به store preview منتقل خواهید شد : (این تصویر مربوط به محصولی دیگر است)

ممکن است پس از زدن دکمه continue خطایی مانند تصویر زیر را مشاهده کنید :

در این صورت احتمالا با کاربر System Account وارد سیستم شده اید که باید از آن خارح شده و با نام کاربری دیکری که دسترسی لازم را دارد وارد شوید .

با کلیک روی continue به marketplace مایکروسافت منتقل خواهید شد که نیازمند یک حساب کاربری در مایکروسافت می‌باشد :

حال پنجره زیر نمایش داده می‌شود و به شما اجازه‌ی دانلود app داده می‌شود :

 
روی return to site کلیک کنید تا پنجره بعدی برای گرفتن اعتماد شما برای نصب نمایش داده شود :



روی trust it کلیک کنید تا به صفحه site Content منتقل شوید :


همانطور که مشاهده می‌کنید app در حال دانلود شدن است :

 
حال در سمت چپ سایت روی نام App کلیک کنید (ترجیحا از مرور گر IE و ورژن 9 یا 10 استفاده کنید )

حال وارد تنظمیات app می‌شوید (در صورت درخواست نام کاربری و کلمه عبور آن را وارد کنید)

و نتیجه این هفت خوان رستم :

 
مطالب
آموزش TypeScript #1
با گسترش روز افزون زبان برنامه نویسی Javascript و استفاده هر چه بیشتر آن در تولید برنامه‌های تحت وب این زبان به یکی از قدرت‌های بزرگ در تولید برنامه‌های مبتنی بر وب تبدیل شده است. ترکیب این زبان با Css و Html5 تقریبا هر گونه نیاز برای تهیه و توسعه برنامه‌های وب را حل کرده است. جاوا اسکریپت در ابتدا برای اسکریپت نویسی سمت کلاینت برای صفحات وب ایجاد شد و برای سال‌ها به‌عنوان ابزاری برای مدیریت کردن رویدادهای صفحات وب محدود شده بود  و در نتیجه بسیاری از امکانات لازم برای برنامه‌نویسی برنامه‌های مقیاس بزرگ را به‌همراه نداشت. امروزه به قدری Javascript  توسعه داده شده است که حتی در تولید برنامه‌های Native مانند Windows Store  و برنامه‌های تحت Cloud نیز استفاده می‌شود. پیشرفت‌های صورت گرفته و اشاره شده در این حوزه موجب شد تا شاهد پیداش برنامه‌های مبتنی بر جاوا اسکریپت با سایزهای بی سابقه‌ای باشیم و این بیانگر این بود که تولید برنامه‌های مبتنی بر جاوا اسکریپت در مقیاس‌های بزرگ امر دشواری است و اینک TypeScript توسط غول نرم افزاری جهان پا به عرصه گذاشته که این فرآیند را آسان‌تر نماید. به کمک TypeScript می‌توان برنامه تحت JavaScript در مقیاس بزرگ تولید کرد به طوری با هر مرورگر و سیستم عاملی سازگار باشد. TypeScript از شی گرایی نیز پشتیبانی می‌کند و خروجی آن در نهایت به JavaScript کامپایل می‌شود. خیلی‌ها عقیده دارند که هدف اصلی مایکروسافت از تولید و توسعه این زبان رقابت با CoffeeScript است.  CoffeeScript یک زبان متن باز است که در سال 2009 توسط Jeremy Ashkenas ایجاد شده است و سورس آن در GitHub موجود می‌باشد. در آینده، بیشتر به مباحث مربوط به CoffeeScript و آموزش آن خواهم پرداخت.

در تصویر ذیل یک مقایسه کوتاه بین CoffeeScript و TypeScript را مشاهده می‌کنید.

با TypeScript چه چیزهایی به دست خواهیم آورد؟

یک نکته مهم این است که این زبان به خوبی در Visual Studio پشتیبانی می‌شود و قابلیت Intellisense نوشتن برنامه به این زبان را دلپذیرتر خواهد کرد و از طرفی دیگر به نظر من یکی از مهم‌ترین مزیت هایی که TypeScript در اختیار ما قرار می‌دهد این است که می‌توانیم به صورت Syntax آشنای شی گرایی کد نویسی کنیم و خیلی راحت‌تر کد‌های خود را سازمان دهی کرده و از نوشتن کد‌های تکراری اجتناب کنیم.

یکی دیگر از مزیت‌های مهم این زبان این است که این زبان از Static Typing به خوبی پشتیبانی می‌کند. این بدین معنی است که شما ابتدا باید متغیر‌ها را تعریف کرده و نوع آن‌ها را مشخص نمایید و هم چنین در هنگام پاس دادن مقادیر به پارامتر‌های توابع باید حتما به نوع داده ای آن‌ها دقت داشته باشید چون کامپایلر بین انواع داده ای در TypeScript تمایز قایل است و در صورت رعایت نکردن این مورد شما با خطا مواجه خواهید شد. این تمایز قایل شدن باعث می‌شود که برنامه هایی خواناتر داشته باشیم از طرفی باعث می‌شود که خطا یابی و نوشتن تست برای برنامه راحت‌تر و تمیزتر باشد. بر خلاف JavaScript، در TypeScript(به دلیل پشتیبانی از شی گرایی) می‌توانیم علاوه بر داشتن کلاس، اینترفیس نیز داشته باشیم و در حال حاضر مزایای استفاده از اینترفیس بر کسی پوشیده نیست.

به دلیل اینکه کد‌های TypeScript ابتدا کامپایل شده و بعد تبدیل به کد‌های JavaScript می‌شوند در نتیجه قبل از رسیدن به مرحله اجرای پروژه، ما از خطاهای موجود در کد خود مطلع خواهیم شد.

البته این نکته را نیز فراموش نخواهیم کرد که این زبان تازه متولد شده است(سال 2012 توسط Anders Hejlsberg) و همچنان در حال توسعه است و این در حال حاضر مهم‌ترین عیب این زبان می‌تواند باشد چون هنوز به پختگی سایر زبان‌های اسکریپتی در نیامده است.

در ذیل یک مثال کوچک به زبان TypeScript و JavaScript را برای مقایسه در خوانایی و راحتی کد نویسی قرار دادم:

TypeScript:

class Greeter {
    greeting: string;

    constructor (message: string) {
        this.greeting = message;
    }

    greet() {
        return "Hello, " + this.greeting;
    }
}
بعد از کامپایل کد بالا به کدی معادل زیر در JavaScript تبدیل خواهد شد:
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();
توضیح چند واژه در TypeScript

Program : یک برنامه TypeScript مجموعه ای از یک یا چند Source File است. این Source File‌ها شامل کد‌های پیاده سازی برنامه هستند ولی در خیلی موارد برای خوانایی بیشتر برنامه می‌توان فقط تعاریف را در این فایل‌های سورس قرار داد.
Module: ماژول در TypeScript شبیه به مفاهیم فضای نام یا namespace در دات نت است و می‌تواند شامل چندین کلاس یا اینترفیس باشد.
Class : مشابه به مفاهیم کلاس در دات نت است و دقیقا همان مفهوم را دارد. یک کلاس می‌تواند شامل چندین تابع و متغیر با سطوح دسترسی متفاوت باشد. در TypeScript مجاز به استفاده از کلمات کلیدی public و private نیز می‌باشید. یک کلاس در Typescript می‌تواند یک کلاس دیگر را توسعه دهد(ارث بری در دات نت) و چندین اینترفیس را پیاده سازی نماید.
Interface: یک اینترفیس فقط شامل تعاریف است و پیاده سازی در آن انجام نخواهد گرفت. یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد.
Function: معادل متد در دات نت است. می‌تواند پارامتر ورودی داشته باشد و در صورت نیاز یک مقدار را برگشت دهد.
Scope: دقیقا تمام مفاهیم مربوط به محدوده فضای نام و کلاس و متد در دات نت در این جا نیز صادق است. 


آماده سازی Visual Studio برای شروع به کار
در ابتدا باید Template مربوطه به TypeScript را نصب کنید تا از طریف VS.Net بتوانیم به راحتی به این زبان کد نویسی کنیم. می‌توانید فایل نصب را از اینجا دانلود کنید. بعد از نصب از قسمت Template‌های موجود گزینه Html Application With TypeScript را انتخاب کنید

یا از قسمت Add  در پروژه‌های وب خود نظیر MVC گزینه TypeScript File را انتخاب نمایید.


در پست بعدی کد نویسی با این زبان را آغاز خواهیم کرد. 

اشتراک‌ها
کتابخانه Motion Slider
Motion Slider is a responsive jQuery slider plugin for mobile and desktop that enables multi-transition and browser prefix free CSS animation to your HTML DOM elements
کتابخانه Motion Slider
مطالب
بومی سازی تاریخ و اعداد در جاوا اسکریپت در سال 2020
احتمالا تا به امروز در برنامه‌های جاوا اسکریپتی خود از کتابخانه‌های ثالث و یا توابع ویژه‌ای برای نمایش شمسی تاریخ، نمایش فارسی اعداد، افزودن جدا کننده‌ی سه رقمی اعداد (جداکننده‌ی هزارگان)، نمایش تاریخ نسبی مانند 1 روز قبل و ... استفاده کرده‌اید. خبر خوب اینکه موتور جاوا اسکریپتی تمام مرورگرهای جدید (از فایرفاکس 29 و کروم 24 به بعد) به صورت توکار یک چنین تبدیل‌هایی را پشتیبانی می‌کنند و برای مثال برای تبدیل تاریخ میلادی به شمسی و نمایش آن، در بسیاری از موارد نیازی به کتابخانه‌ی حجیم moment.js (و یا سایر روش‌های مرسوم خانگی) نیست.


معرفی API استاندارد بومی سازی JavaScript

Internationalization یا به صورت خلاصه i18n (یعنی یک i به همراه 18 حرف و یک n)، پروسه‌ای که در آن برنامه به نحوی طراحی می‌شود تا خروجی آن قابلیت استفاده‌ی برای انواع و اقسام فرهنگ‌ها را داشته باشد. برای مثال دو متد زیر را در نظر بگیرید:
function formatDate(d)
{
  var month = d.getMonth() + 1;
  var date = d.getDate();
  var year = d.getFullYear();
  return month + "/" + date + "/" + year;
}

function formatMoney(amount)
{
  return "$" + amount.toFixed(2);
}
آیا در همه جای دنیا، تاریخ به صورت ماه، روز و سال نمایش داده می‌شود؛ آن هم به صورت میلادی؟ و یا آیا خروجی فرمت شده‌ی یک مقدار پولی، همیشه با دلار شروع می‌شود و نمایش آن نیز با اعداد انگلیسی است؟
پیشتر جاوا اسکریپت برای مدیریت یک چنین مواردی (i18n-aware formatting) از متد toLocaleString استفاده می‌کرد (و هنوز هم برای پشتیبانی از برنامه‌های قدیمی، از API عمومی آن حذف نشده‌است) و خروجی آن از هر مرورگر و پیاده سازی خاصی، به مرورگر دیگری می‌تواند متفاوت باشد؛ حتی اگر جزئیات دقیقی هم درخواست شود. برای رفع این مشکل، استاندارد ECMAScript Internationalization API ارائه شد تا قابلیت‌های توکار i18n جاوا اسکریپت را بهبود بخشیده و همچنین یک‌دست کند. توسط آن امکان انتخاب یک یا چند منطقه‌ی خاص و سپس فرمت کردن تاریخ، اعداد و یا حتی مرتب سازی واژه‌ها و عبارات با معرفی collations، میسر می‌باشد. در اینجا حتی امکان سفارشی سازی این فرمت‌ها نیز پیش‌بینی شده‌است.


معرفی اینترفیس Intl

i18n API در یک شیء سراسری به نام Intl قابل دسترسی است و تعدادی از سازنده‌های آن Intl.Collator ،Intl.DateTimeFormat و Intl.NumberFormat نام دارند؛ مانند:
const result =  new Intl.NumberFormat("fa").format(123456)
برای کار با این شیء، نیازی به import هیچ ماژول و یا کتابخانه‌ای نیست و جزئی از جاوا اسکریپت استاندارد می‌باشد. به همین جهت کار با آن حجمی را به برنامه‌ی شما اضافه نخواهد کرد.

تمام این سازنده‌ها می‌توانند با یک فرهنگ و یا آرایه‌ا‌‌ی از فرهنگ‌های مدنظر شروع شوند:
const portugueseTime =  new Intl.DateTimeFormat(["pt-BR", "pt-PT"], options);
در مثال اول فرهنگ فارسی و در مثال دوم فرهنگ پرتغالی که در برزیل و پرتغال مورد استفاده‌است، ذکر شده‌اند.
پارامتر اختیاری دوم آن‌ها نیز تنظیماتی است که جهت سفارشی سازی این بومی سازی می‌توان تعریف کرد.


نمایش شمسی تاریخ میلادی توسط i18n API

پس از معرفی i18n API، اکنون می‌خواهیم در طی مثال‌هایی، تمام کتابخانه‌های ثالث تبدیل تاریخ میلادی به شمسی را کنار گذاشته و با استفاده از جاوا اسکریپت استاندارد، این تبدیل را انجام دهیم. پارامتر دوم سازنده‌ی new Intl.DateTimeFormat که تنظیمات آن‌را مشخص می‌کند، می‌تواند به همراه ترکیبی از موارد زیر باشد که مقادیر مجاز برای آن‌ها را نیز مشاهده می‌کنید:
{
  weekday: 'narrow' | 'short' | 'long',
  era: 'narrow' | 'short' | 'long',
  year: 'numeric' | '2-digit',
  month: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long',
  day: 'numeric' | '2-digit',
  hour: 'numeric' | '2-digit',
  minute: 'numeric' | '2-digit',
  second: 'numeric' | '2-digit',
  timeZoneName: 'short' | 'long',
// Time zone to express it in
  timeZone: 'Asia/Shanghai',
  // Force 12-hour or 24-hour
  hour12: true | false,
// Rarely-used options
  hourCycle: 'h11' | 'h12' | 'h23' | 'h24',
  formatMatcher: 'basic' | 'best fit'
}
برای نمونه، ذکر Intl.DateTimeFormat بدون هیچ تنظیمی و فقط با تعیین فرهنگ فارسی:
var dateFormat = new Intl.DateTimeFormat("fa");
console.log(dateFormat.format(Date.now()));
خروجی «۱۳۹۸/۱۲/۱» را نمایش می‌دهد.


نمایش تاریخ شمسی با فرمت «۱۳۹۸ اسفند ۱, پنجشنبه»

برای تبدیل تاریخ میلادی به شمسی می‌توان از سازنده‌ی new Intl.DateTimeFormat با فرهنگ fa استفاده کرد. در اینجا ذکر مقدار long برای نام روز هفته، سبب درج نام روز می‌شود. نمایش سال به صورت عددی تنظیم شده‌است، ماه را به صورت بلند و نام کامل نمایش می‌دهد و مقدار روز را به صورت عددی درج می‌کند. این اعداد نیز فارسی هستند:
const date = new Date(Date.UTC(2020, 1, 20, 3, 0, 0, 200));
const faDate = new Intl.DateTimeFormat("fa", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
}).format(date);
console.log(faDate);
که برای نمونه سبب درج خروجی «۱۳۹۸ اسفند ۱, پنجشنبه» در کنسول توسعه دهندگان مرورگر خواهد شد.

اگر فقط نیاز به نمایش «۱ اسفند ۱۳۹۸» بود، می‌توان از تنظیمات زیر که در آن ماه، روز و سال ذکر شده‌اند و در آن، ماه به صورت کامل و بلند نمایش داده می‌شود، استفاده کرد:
const isoString = new Date().toISOString();
const date = new Date(isoString);
console.log(
  new Intl.DateTimeFormat("fa", {
    month: "long",
    day: "numeric",
    year: "numeric"
  }).format(date)
);

یک نکته: همین خروجی را با متد قدیمی toLocaleDateString نیز می‌توان به دست آورد؛ اما روش توصیه شده برای برنامه‌های جدید، همان استفاده از new Intl است.
console.log(
  new Date().toLocaleDateString("fa", {
    month: "long",
    day: "numeric",
    year: "numeric"
  })
);

نمایش تاریخ شمسی با فرمت «۹۸/۱۲/۱،‏ ۶:۳۰»

برای اینکار پس از ذکر فرهنگ fa، تمام اجزای تاریخ را به صورت عددی مشخص می‌کنیم و سال را نیز دو رقمی نمایش خواهیم داد:
const date = new Date(Date.UTC(2020, 1, 20, 3, 0, 0, 200));
const fmt = new Intl.DateTimeFormat("fa", {
  year: "2-digit",
  month: "numeric",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});
console.log(fmt.format(date));
در این حالت اگر نیاز بود حتما اعداد ماه و روز، دو رقمی باشند، می‌توان تنظیم 2-digit را صریحا ذکر کرد:
const faDateTime = new Intl.DateTimeFormat("fa", {
  year: "2-digit",
  month: "2-digit",
  day: "2-digit",
  hour: "2-digit",
  minute: "2-digit",
  timeZoneName: "short"
}).format;
const now = Date.now();
console.log(faDateTime(now));
با خروجی «۹۸/۱۲/۰۱،‏ ۱۲:۵۹ (‎+۳:۳۰ گرینویچ)»

و یا اگر «۱ اسفند ۱۳۹۸،‏ ۰۹:۲۹ (UTC)» مدنظر بود، می‌توان ماه را به long تنظیم کرد و مقدار timeZone را صریحا ذکر نمود (که البته ذکر تنظیمات timeZone اختیاری است):
const faTime = new Intl.DateTimeFormat("fa", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "2-digit",
  minute: "2-digit",
  timeZoneName: "short",
  timeZone: "UTC"
});
console.log(faTime.format(now));

نمایش تاریخ‌های نسبی مانند «1 روز بعد»
برای نمایش تاریخ‌های نسبی، می‌توان از شیء new Intl.RelativeTimeFormat استفاده کرد:
const rtf = new Intl.RelativeTimeFormat("en", {
  localeMatcher: "best fit", // other values: "lookup"
  numeric: "always", // other values: "auto"
  style: "long", // other values: "short" or "narrow"
});
console.log(rtf.format(-1, "day"));
console.log(rtf.format(1, "day"));
با خروجی‌های «۱ روز پیش» و «۱ روز بعد»


نمایش اعداد فارسی توسط i18n API

احتمالا برای تبدیل اعداد انگلیسی به فارسی و نمایش آن‌ها، متدهایی را برای replace حروف و اعداد طراحی کرده‌اید. به کمک شیء استاندارد Intl.NumberFormat دیگر نیازی به آن‌ها نخواهید داشت!
خروجی شیء Intl.NumberFormat به همراه ذکر فرهنگ فارسی و هیچ تنظیم اضافه‌تری
console.log(new Intl.NumberFormat("fa").format(123456));
به صورت «۱۲۳٬۴۵۶» است که هم اعداد آن فارسی شده‌اند و هم به همراه جداکننده‌ی هزارگان خودکار است.

اگر می‌خواهید این جداکننده‌ی هزارگان نمایش داده نشود، نیاز است تنظیمات آن‌را به همراه useGrouping: false، به صورت زیر ذکر کرد:
console.log(
   new Intl.NumberFormat("fa", { useGrouping: false }).format(123456)
);

این شیء یک مقدار غیرعددی را
console.log(new Intl.NumberFormat("fa").format("تست"));
به صورت «ناعدد» نمایش می‌دهد.

و یا برای نمایش واحد پولی، می‌توان حالت نمایش را به currency و نوع currency را به IRR که ریال است، تنظیم کرد:
const gasPrice = new Intl.NumberFormat("fa", {
  style: "currency",
  currency: "IRR",
  minimumFractionDigits: 3
});
console.log(gasPrice.format(5.2597));
با این خروجی: «‎ریال ۵٫۲۶۰» که در اینجا امکان تنظیم نمایش تعداد اعشار آن نیز میسر است.

برای نمایش درصد پس از اعداد می‌توان از تنظیم زیر استفاده کرد:
const faPercent = new Intl.NumberFormat("fa", {
  style: "percent",
  minimumFractionDigits: 2
}).format;
console.log(faPercent(0.438));
که خروجی «۴۳٫۸۰٪» را نمایش می‌دهد.

و یا برای نمایش ممیز به همراه تنظیم دقت آن داریم:
const persianDecimal = new Intl.NumberFormat("fa", {
  minimumIntegerDigits: 2,
  maximumFractionDigits: 2
});
console.log(persianDecimal.format(3.1416));
با این خروجی: «۰۳٫۱۴»
مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت ششم - رخدادهای مرورگرها
واکنش نشان دادن به تغییرات صفحات وب، قسمت مهم و عمده‌ای از کار توسعه‌ی برنامه‌های وب را تشکیل می‌دهد. مفاهیم مرتبط با DOM events از زمان IE 4.0 و Netscape Navigator version 2 به مرورگرها اضافه شدند و به مرور تکامل یافتند. پیش از ظهور مرورگرهای مدرن (به IE 9.0 و مرورگرهای پیش از آن، مرورگرهای «باستانی» گفته می‌شود) به علت عدم هماهنگی آن‌ها با استانداردهای وب و تفاوت روش‌های رخدادگردانی، jQuery نقش مهمی را در زمینه‌ی یکدست سازی کدهای مدیریت رخدادها در بین مرورگرهای مختلف ارائه کرد. اما با پیش‌رفت‌های صورت گرفته و هماهنگی بیشتر مرورگرها با استانداردهای وب، دیگر نیازی به jQuery برای ارائه‌ی کدهای یکدست رخدادگردانی نیست و کار مستقیم با API وب مرورگرها برای این منظور کافی است.


انواع رخدادها: بومی و سفارشی

دو رده بندی عمومی رخدادها در مرورگرها وجود دارند: بومی و سفارشی.
بومی‌ها همان‌هایی هستند که در مستندات رسمی استانداردهای وب ذکر شده‌اند؛ مانند click که توسط ماوس و یا صفحه کلید فعال می‌شود و یا load که در زمان بارگذاری کامل صفحه، تصاویر و یا یک iframe رخ می‌دهد.
رخدادهای سفارشی مواردی هستند که توسط یک کتابخانه‌ی خاص و یا جهت یک برنامه‌ی خاص تهیه شده‌اند. مانند یک رخداد سفارشی که زمان شروع آپلود یک فایل را اعلام می‌کند.
رخدادهای سفارشی که بدون jQuery ایجاد و رخ‌می‌دهند، توسط jQuery نیز قابل بررسی و مدیریت هستند و نه برعکس. به عبارتی رخدادهای سفارشی ایجاد شده‌ی توسط jQuery غیراستاندارد بوده و صرفا مختص به API آن هستند.
در این بین، شیء استاندارد Event کار اتصال رخدادهای سفارشی و استاندارد را انجام می‌دهد. هر نوع رخداد DOM (سفارشی و یا بومی)، توسط یک شیء Event بیان می‌شود که آن نیز به همراه تعدادی خاصیت و متد، جهت مدیریت این رخداد است. برای مثال رخداد click دارای خاصیت type ایی به نام click است که در شیء Event متناظر با آن تعریف شده‌است.


انتشار رخدادها در صفحه

در روزهای آغازین وب، Netscape روش event capturing را برای انتشار رخدادها در صفحه ارائه داد و در مقابل آن IE روش event bubbling را معرفی کرد که متضاد یکدیگر بودند. در سال 2000 با ارائه استاندارد DOM Level 2 Events Specification، این وضعیت تغییر کرد و شامل هر دو مورد event capturing و event bubbling است و در حال حاضر تمام مرورگرهای مدرن این استاندارد را پیاده سازی کرده‌اند. بر اساس این استاندارد، زمانیکه رویدادی خلق می‌شود، فاز capturing آغاز می‌گردد که از شیء window شروع، سپس به شیء document منتشر می‌شود و این روند تا رسیدن به المانی که سبب بروز رخداد شده‌است ادامه پیدا می‌کند. پس از پایان فاز capturing، فاز جدید bubbling شروع می‌شود. در این فاز، رخداد از تمام والدین شیء هدف عبور می‌کند تا به شیء window برسد.
برای مثال اگر سند HTML ما چنین تعریفی را داشته باشد و بر روی المان «child of child of one» کلیک شده باشد:
   <!DOCTYPE html> 
   <html>
   <head>
     <title>event propagation demo</title>
   </head>
   <body>
     <section>
       <h1>nested divs</h1>
       <div>one
        <div>child of one
          <div>child of child of one</div>
        </div>
      </div>
    </section>
  </body>
  </html>
این رخداد در فاز capturing از این المان‌ها عبور می‌کند:
1.window
2.document
3.<html>
4.<body>
5.<section>
6.<div>one
7.<div>child of one
8.<div>child of child of one
و در فاز Bubbling از این المان‌ها:
9.<div>child of child of one
10.<div>child of one
11.<div>one
12.<section>
13.<body>
14.<html>
15.document
16.window
هرچند به دلایل تاریخی و همچنین عدم پشتیبانی jQuery از فاز capturing، بیشتر از فاز Bubbling به صورت پیش‌فرض در رخدادگردانی استفاده می‌شود. همچنین صدور رخداد از المانی که آن‌را ایجاد کرده‌است، بیشتر منطقی به نظر می‌رسد تا عکس آن.
البته باید درنظر داشت که jQuery از روش ارائه شده‌ی توسط مرورگر برای فاز Bubbling استفاده نمی‌کند و این مسیر را خودش مجددا محاسبه و رخدادگردان‌های این مسیر را به صورت دستی اجرا می‌کند. به همین جهت کارآیی آن نسبت به روش توکار و بومی مرورگرها کمتر است.


ایجاد رخدادهای DOM و صدور آن‌ها در jQuery

برای نمایش ایجاد و صدور رخدادهای DOM با و بدون jQuery، از قطعه کد HTML زیر استفاده می‌کنیم:
   <div>
     <button type="button">do something</button>
   </div>
 
  <form method="POST" action="/user">
     <label>Enter user name:
       <input name="user">
     </label>
     <button type="submit">submit</button>
  </form>
jQuery به همراه دو متد trigger و triggerHandler برای ایجاد و انتشار رخدادها در طول DOM است. متد trigger سبب ایجاد، صدور و انتشار یک رخداد به تمام والدهای شیء صادر کننده‌ی رخداد می‌شود. نوع این انتشار نیز bubbling است. متد triggerHandler، فقط بر روی المانی که فراخوانی می‌شود، عمل کرده و سبب انتشار این رخداد از طریق bubbling نمی‌شود:
   // submits the form 
   $('FORM').trigger('submit');
 
   // submits the form by clicking the button 
   $('BUTTON[type="submit"]').trigger('click');
 
   // focuses the text input 
   $('INPUT').trigger('focus');
 
  // removes focus from the text input 
  $('INPUT').trigger('blur');
در این مثال‌ها توسط متد trigger، به دو روش سبب submit یک فرم و همچنین در ابتدا سبب focus یک تکست باکس و سپس رفع آن شده‌ایم.
هرچند روش دومی نیز در jQuery API برای انجام همینکارها نیز پیش بینی شده‌است:
   // submits the form 
   $('FORM').submit();
 
   // submits the form by clicking the button 
   $('BUTTON[type="submit"]').click();
 
   // focuses the text input 
   $('INPUT').focus();
 
  // removes focus from the text input 
  $('INPUT').blur();
در اینجا به ازای هر رخداد، یک نام مستعار در jQuery API تدارک دیده شده‌است.

در ادامه فرض کنید یک دکمه داخل یک div قرار گرفته‌است و آن div نیز به همراه یک مدیریت کننده‌ی رخداد کلیک است. در این حالت اگر بخواهیم با کلیک بر روی دکمه سبب اجرای رویدادگردان div والد نشویم، می‌توان از متد triggerHandler استفاده کرد:
  // clicks the first button - the click event does not bubble 
  $('BUTTON[type="button"]').triggerHandler('click');


ایجاد رخدادهای DOM و صدور آن‌ها در جاوا اسکریپت (بدون استفاده از jQuery)

در web API مرورگرها، برای انجام بروز رخدادهای معادل مثالی که با jQuery مطرح شد، می‌توان متدهای بومی متناظر با این رخدادها را بر روی المان‌ها فراخوانی کرد:
   // submits the form 
   document.querySelector('FORM').submit();
 
   // submits the form by clicking the button 
   document.querySelector('BUTTON[type="submit"]').click();
 
   // focuses the text input 
   document.querySelector('INPUT').focus();
 
  // removes focus from the text input 
  document.querySelector('INPUT').blur();
قطعه کد فوق به علت استفاده‌ی از querySelector، با IE 8.0 و تمام مرورگرهای پس از آن سازگار است.
متدهای توکار و بومی click ،focus و blur بر روی تمام عناصر DOM که از اینترفیس HTMLElement مشتق شده باشند، وجود دارند. متد submit فقط بر روی المان‌هایی از نوع <form> وجود دارد و قابل فراخوانی است.
باید دقت داشت که فراخوانی متدهای click و submit از نوع bubbling است؛ اما متدهای focus و blur خیر. از این جهت که این دو رخداد فاز capturing را سبب می‌شوند.

متدهای یاد شده را توسط سازنده‌ی شیء Event و یا متد createEvent شیء document نیز می‌توان ایجاد کرد. یکی از کاربردهای آن، ارائه‌ی رفتاری سفارشی مانند triggerHandler جی‌کوئری است:
   var clickEvent;

   if (typeof Event === 'function') {
     clickEvent = new Event('click', {bubbles: false});
   }
   else {
       clickEvent = document.createEvent('Event');
      clickEvent.initEvent('click', false, true);
  }

  document.querySelector('BUTTON[type="button"]').dispatchEvent(clickEvent);
کار با سازنده‌ی شیء Event در تمام مرورگرهای جدید، منهای IE (تمام نگارش‌های آن) پشتیبانی می‌شود. در اینجا اگر این پشتیبانی وجود داشت، از خاصیت bubbles: false شیء Event استفاده می‌شود و اگر مرورگری قدیمی بود، از متد document.createEvent برای این منظور کمک گرفته می‌شود. در این حالت دومین پارامتر متد initEvent، همان bubbles است.


ایجاد و صدور رخدادهای سفارشی

فرض کنید در حال تهیه‌ی کتابخانه‌ای هستیم که افزودن و حذف آیتم‌ها را به یک گالری عکس ارائه می‌دهد. می‌خواهیم روشی را در اختیار مصرف کننده قرار دهیم تا بتواند به این رخدادهای سفارشی (غیر استانداردی که جزو W3C نیستند) گوش فرا دهد.
در جی‌کوئری برای ایجاد رخدادهای سفارشی به صورت زیر عمل می‌شود:
  // Triggers a custom "image-removed" element, 
  // which bubbles up to ancestor elements.
$libraryElement.trigger('image-removed', {id: 1});
در اینجا نیز صدور رخدادها همانند قبل و توسط همان متد trigger است. اما مشکلی که با آن وجود دارد این است که گوش فرا دهنده‌ی به این رخداد نیز باید توسط جی‌کوئری ارائه شود و خارج از این کتابخانه قابل دریافت و پیگیری نیست.
در خارج از جی‌کوئری و توسط web API استاندارد مرورگرها ایجاد و صدور رخدادهای سفارشی به همراه bubbling آن به صورت زیر است:
  var event = new CustomEvent('image-removed', {
    bubbles: true,
    detail: {id: 1}
  });
  libraryElement.dispatchEvent(event);
البته باید به‌خاطر داشته باشید این روش صرفا با مرورگرهای جدید (منهای تمام نگارش‌های IE) کار می‌کند. در اینجا اگر نیاز به ارائه‌ی راه حلی سازگار با IE نیز وجود داشت می‌توان از document.createEvent استفاده کرد:
  var event = document.createEvent('CustomEvent');
  event.initCustomEvent('image-removed', false, true, {id: 1});
  libraryElement.dispatchEvent(event);
و اگر بخواهیم بررسی وجود IE و یا پشتیبانی از CustomEvent را نیز قید کنیم، به قطعه کد زیر خواهیم رسید که با تمام مرورگرهای موجود کار می‌کند:
  var event;
 
   // If the `CustomEvent` constructor function is not supported, 
   // fall back to `createEvent` method. 
   if (typeof CustomEvent === 'function') {
     event = new CustomEvent('image-removed', {
       bubbles: true,
       detail: {id: 1}
     });
  }
  else {
      event = document.createEvent('CustomEvent');
      event.initCustomEvent('image-removed', false, true, {
        id: 1
      });
  }

  libraryElement.dispatchEvent(event);


گوش فرادادن به رخدادهای صادر شده، توسط jQuery

در جی‌کوئری با استفاده از متد on آن می‌توان به تمام رخدادهای استاندارد و همچنین سفارشی گوش فرا داد:
  $(window).on('resize', function() {
     // react to new window size 
  });
در ادامه برای حذف تمام گوش فرا دهنده‌های به رخداد resize می‌توان از متد off آن استفاده کرد:
  // remove all resize listeners - usually a bad idea 
  $(window).off('resize');
اما مشکلی را که این روش به همراه دارد، از کار انداختن تمام قسمت‌های دیگری است که هم اکنون به صدور این رخداد گوش فرا می‌دهند.
روش بهتر انجام اینکار، ذخیره‌ی ارجاعی به متدی است که قرار است این رویداد گردانی را انجام دهد:
  var resizeHandler = function() {
      // react to new window size 
  };

  $(window).on('resize', resizeHandler);

  // ...later 
  // remove only our resize handler 
  $(window).off('resize', resizeHandler);
و در آخر تنها این گوش فرا دهنده‌ی خاص را در صورت نیاز غیرفعال می‌کنیم و نه تمام گوش فرادهنده‌های سراسر برنامه را.

همچنین اگر یک گوش فراهنده‌ی به رخدادی تنها قرار است یکبار در طول عمر برنامه اجرا شود، می‌توان از متد one استفاده کرد:
$(someElement).one('click', function() {
    // handle click event 
});
پس از یکبار اجرای رخدادگردان کلیک در اینجا، از کلیک‌های بعدی صرفنظر خواهد شد.


گوش فرادادن به رخدادهای صادر شده، توسط جاوا اسکریپت خالص (یا همان web API مرورگرها)

ابتدایی‌ترین روش گوش فرادادن به رخدادها که از زمان آغاز معرفی آن‌ها در دسترس بوده‌است، روش تعریف inline آن‌ها است:
  <button onclick="handleButtonClick()">click me</button>
در اینجا متد رویدادگردان به یکی از ویژگی المان انتساب داده می‌شود. مشکل این روش، نیاز به سراسری تعریف کردن متد handleButtonClick است و دیگر نمی‌توان آن‌را در فضای نامی خاص و یا سفارشی قرار داد.
روش دیگر ثبت رویدادگردان click، انتساب متد آن به خاصیت رخداد متناظری در آن المان ویژه است:
  buttonEl.onclick = function() {
    // handle button click 
  };
مزیت این روش، عدم نیاز به استفاده‌ی از متدهای سراسری است.
البته باید دقت داشت که یکی از دو روش یاد شده را می‌توانید استفاده کنید. در اینجا آخرین رویدادگردان متصل شده‌ی به المان، همواره تمام نمونه‌های موجود دیگر را بازنویسی می‌کند.
اگر نیاز به معرفی رویدادگردان‌های متعددی برای یک المان در ماژول‌های مختلف برنامه وجود داشت، از زمان IE 9.0 به بعد، متد addEventListener برای این منظور تدارک دیده شده‌است و syntax آن بسیار شبیه به متد on جی‌کوئری است:
  buttonEl.addEventListener('click', function() {
    // handle button click 
  });
در این حالت دیگر مشکل نیاز به متدهای سراسری و یا عدم امکان تعریف بیش از یک رویدادگردان خاص برای المانی مشخص، دیگر وجود ندارد.
برای نمونه معادل قطعه کد جی‌کوئری که پیشتر با متد on نوشتیم، با جاوا اسکریپت خالص به صورت زیر است:
  window.addEventListener('resize', function() {
    // react to new window size 
  });
در اینجا برای حذف یک رویدادگردان می‌توان از متد removeEventListener استفاده کرد. تفاوت مهم آن با متد off جی‌کوئری این است که در اینجا حتما باید مشخص باشد کدام رویدادگردان را می‌خواهید حذف کنید:
  var resizeHandler = function() {
      // react to new window size 
  };

  window.addEventListener('resize', resizeHandler);

  // ...later 
  // remove only our resize handler 
  window.removeEventListener('resize', resizeHandler);
یعنی روش حذف رویدادگردان‌ها در اینجا شبیه به مثال دومی است که برای متد off جی‌کوئری ارائه کردیم. ابتدا باید ارجاعی را به متد رویدادگردان تهیه کنیم و سپس بر اساس این ارجاع، امکان حذف آن وجود خواهد داشت.
در اینجا حتی امکان تعریف متد one جی‌کوئری نیز پیش بینی شده‌است (البته جزو استانداردهای جدید وب از سال 2016 است):
  someElement.addEventListener('click', function(event) {
     // handle click event 
  }, { once: true });
اگر بخواهیم متد one سازگار با مرورگرهای قدیمی‌تر را نیز ارائه کنیم، چنین شکلی را پیدا می‌کند:
  var clickHandler = function() {
    // handle click event 
    // ...then unregister handler 
    someElement.removeEventListener('click', clickHandler);
  };
  someElement.addEventListener('click', clickHandler);
در اینجا پس از بروز رخداد، کار removeEventListener آن به صورت خودکار صورت می‌گیرد.


کنترل انتشار رخدادها

فرض کنید می‌خواهیم جلوی انتخاب المان‌های صفحه مانند تصاویر و متن را توسط ماوس بگیریم. روش انجام اینکار با jQuery به صورت زیر است:
$(window).on('mousedown', function(event) {
    event.preventDefault();
});
و یا توسط web API مرورگرها به این صورت:
  window.addEventListener('mousedown', function(event) {
    event.preventDefault();
  });
مطابق «W3C DOM Level 3 Events specification» عملکرد پیش‌فرض رخداد mousedown با انتخاب متون و یا کشیدن و رها کردن المان‌ها آغاز می‌شود. متد preventDefault یکی از متدهای شیء event است که به رویدادگردان‌های تعریف شده ارسال می‌شود و توسط آن عملکرد پیش‌فرض آن رخداد لغو می‌شود.

برای جلوگیری کردن از انتشار رخدادی مانند click جهت رسیدن به سایر رویدادگردان‌های ثبت شده‌ی در بین راه فاز bubbling، می‌توان از متد stopPropagation استفاده کرد. روش انجام اینکار در جی‌کوئری:
  $someElement.on('click', function(event) {
      event.stopPropagation();
  });
البته jQuery صرفا فاز انتشار از نوع bubbling را پشتیبانی می‌کند.
و با web Api جهت جلوگیری از انتشار رخدادها در فاز capturing (این تنها راه مدیریت فاز capturing است):
  // stop propagation during capturing phase 
  someElement.addEventListener('click', function(event) {
      event.stopPropagation();
  }, true);
و یا استفاده از web API برای جلوگیری از انتشار رخدادها در فاز bubbling:
  // stop propagation during bubbling phase 
  someElement.addEventListener('click', function(event) {
      event.stopPropagation();
  });
البته باید درنظر داشت که متد stopPropagation از انتشار رخدادها به سایر گوش فرا دهنده‌های همان المان صادر کننده‌ی رخداد جلوگیری نمی‌کند. برای این منظور باید از متد stopImmediatePropagation استفاده کرد؛ در جی‌کوئری:
  $someElement.on('click', function(event) {
      event.stopImmediatePropagation();
  });
و توسط web API مرورگرها:
  someElement.addEventListener('click', function(event) {
      event.stopImmediatePropagation();
  });

یک نکته: در این حالت اگر متد رویدادگردانی مقدار false را برگرداند، به معنای فراخوانی هر دوی متد preventDefault و stopPropagation است.


ارسال اطلاعات به رویدادگردان‌ها

روش ارسال اطلاعات اضافی به رویداد گردان‌ها در جی‌کوئری به صورت زیر است:
  $uploaderElement.trigger('uploadError', {
    filename: 'picture.jpeg'
  });
و رویدادگردان گوش فرا دهنده‌ی به آن، به این نحو می‌تواند به filename دسترسی پیدا کند:
  $uploaderParent.on('uploadError', function(event, data) {
     showAlert('Failed to upload ' + data.filename);
  });
در اینجا دومین پارامتر تعریف شده، امکان دسترسی به تمام خواص سفارشی ارسالی را میسر می‌کند.

روش انجام اینکار با web API مرورگرها به صورت زیر است:
   // send the failed filename w/ an error event
  var event = new CustomEvent('uploadError', {
     bubbles: true,
     detail: {filename: 'picture.jpeg'}
   });
   uploaderElement.dispatchEvent(event);
 
   // ...and this is a listener for the event 
   uploaderParent.addEventListener('uploadError', function(event) {
      showAlert('Failed to upload ' + event.detail.filename);
  });
این روش با تمام مرورگرهای مدرن (منهای تمام نگارش‌های IE) کار می‌کند و روش دسترسی به خاصیت detail سفارشی تعریف و ارسال شده، از طریق همان خاصیت event ارسالی به رویدادگردان است.
و اگر می‌خواهید از IE هم پشتیبانی کنید، روش جایگزین کردن شیء CustomEvent با createEvent به صورت زیر است:
  // send the failed filename w/ an error event 
   var event = document.createEvent('CustomEvent');
   event.initCustomEvent('uploadError', true, true, {
     filename: 'picture.jpeg'
   });
   uploaderElement.dispatchEvent(event);
 
   // ...and this is a listener for the event 
   uploaderParent.addEventListener('uploadError', function(event) {
    showAlert('Failed to upload ' + event.detail.filename);
  });


متوجه شدن زمان بارگذاری یک شیء در صفحه

در حین توسعه‌ی برنامه‌های وب، با این نوع سؤالات زیاد مواجه خواهید شد: چه زمانی تمام و یا بعضی از المان‌های صفحه کاملا بارگذاری و رندر شده‌اند؟
پاسخ به این نوع سؤالات در W3C UI Events specification توسط رویداد استاندارد load داده شده‌است.

- چه زمانی تمام المان‌های موجود در صفحه کاملا بارگذاری و رندر شده و همچنین شیوه‌نامه‌های تعریف شده نیز به آن‌ها اعمال گردیده‌اند؟
روش انجام اینکار با jQuery:
  $(window).on('load', function() {
    // page is fully rendered 
  });
و توسط web API بومی مرورگرها که بسیار مشابه نمونه‌ی jQuery است:
  window.addEventListener('load', function() {
    // page is fully rendered 
  });

- چه زمانی markup استاتیک صفحه‌ی جاری در جای خود قرار گرفته‌اند؟
اهمیت این موضوع، به دسترسی به زمان مناسب و امن ایجاد تغییرات در DOM بر می‌گردد. برای این منظور رویداد استاندارد DOMContentLoaded پیش‌بینی شده‌است که زودتر از رویداد load، در دسترس برنامه نویس قرار می‌گیرد. در جی‌کوئری توسط یکی از دو روش معروف زیر به رویداد یاد شده دسترسی خواهید داشت:
  $(document).ready(function() {
    // markup is on the page 
  });

  //or
  $(function() {
    // markup is on the page 
  });
و معادل web API آن در تمام مرورگرهای جدید، همان تعریف رویدادگردانی برای DOMContentLoaded استاندارد است:
  document.addEventListener('DOMContentLoaded', function() {
    // markup is on the page 
  });

یک نکته: بهتر است این تعریف web API را پیش از تگ‌های <link> قرار دهید. زیرا بارگذاری آن‌ها، اجرای هر نوع اسکریپتی را تا زمان پایان عملیات، سد می‌کند.

- چه زمانی المانی خاص در صفحه بارگذاری شده‌است و چه زمانی بارگذاری یک المان با شکست مواجه شده‌است؟
در جی‌کوئری توسط بررسی رویدادهای load و error می‌توان به وضعیت نهایی بارگذاری المان‌هایی خاص دسترسی یافت:
   $('IMG').on('load', function() {
     // image has successfully loaded 
   });
 
   $('IMG').on('error', function() {
     // image has failed to load 
   });
روش انجام اینکار با web API مرورگرها نیز یکی است:
  document.querySelector('IMG').addEventListener('load', function() {
    // image has successfully loaded 
  });

  document.querySelector('IMG').addEventListener('error', function() {
    // image has failed to load 
  });

- جلوگیری از ترک اتفاقی صفحه‌ی جاری
گاهی از اوقات نیاز است برای از جلوگیری از تخریب صفحه‌ی جاری و از دست رفتن اطلاعات ذخیره نشده‌ی کاربر، اگر بر روی دکمه‌ی close بالای صفحه کلیک کرد و یا کاربر به اشتباه به صفحه‌ی دیگری هدایت شد، جلوی اینکار را بگیریم. برای این منظور رخداد استاندارد beforeunload درنظر گرفته شده‌است. روش استفاده‌ی از این رویداد در جی‌کوئری:
  $(window).on('beforeunload', function() {
    return 'Are you sure you want to unload the page?';
  });
و در web API مرورگرها:
  window.addEventListener('beforeunload', function(event) {
    var message = 'Are you sure you want to unload the page?';
    event.returnValue = message;
    return message;
  });
در حالت web API بعضی از مرورگرها از نتیجه‌ی متد استفاده می‌کنند و بعضی دیگر از مقدار خاصیت event.returnValue. به همین جهت هر دو مورد در اینجا مقدار دهی شده‌اند.
مطالب
MVC Scaffolding #1
پیشنیازها
کل سری ASP.NET MVC
به همراه کل سری EF Code First


MVC Scaffolding چیست؟

MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامه‌های ASP.NET MVC مبتنی بر EF Code First.


بررسی مقدماتی MVC Scaffolding

امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژه‌های ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندی‌ها و قابلیت‌های سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدل‌های برنامه اضافه کنید:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [DisplayName("Is Complete")]
        public bool IsComplete { set; get; }

        [StringLength(450)]
        public string Description { set; get; }
    }
}
سپس بر روی پوشه Controllers کلیک راست کرده و گزینه Add controller را انتخاب کنید. تنظیمات صفحه ظاهر شده را مطابق شکل زیر تغییر دهید:


همانطور که ملاحظه می‌کنید در قسمت قالب‌ها، تولید کنترلرهایی با اکشن متد‌های ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آن‌را پذیرفته‌ایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:


الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدل‌های برنامه ایجاد می‌گردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه می‌کند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلی‌های دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)

معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شده‌ای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی می‌تواند شامل فیلدهای بیشتری باشد و نباید آن‌را مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).

مزیت‌ها:
قسمت عمده‌ای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شده‌اند. کارهای اولیه‌ای که با هر روش و الگوی شناخته شده‌ای قصد پیاده سازی آن‌ها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمده‌ای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرم‌های آن‌را بخواهید با jQuery Ajax پیاده سازی کنید یا کنترل‌های پیش فرض را با افزونه‌های jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.


نصب بسته اصلی MVC Scaffolding توسط NuGet

بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console می‌توان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
اگر به مراحل نصب آن دقت کنید یک سری وابستگی را نیز به صورت خودکار دریافت کرده و نصب می‌کند:
Attempting to resolve dependency 'T4Scaffolding'.
Attempting to resolve dependency 'T4Scaffolding.Core'.
Attempting to resolve dependency 'EntityFramework'.
Successfully installed 'T4Scaffolding.Core 1.0.0'.
Successfully installed 'T4Scaffolding 1.0.8'.
Successfully installed 'MvcScaffolding 1.0.9'.
Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1.
Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1.
Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
از مواردی که با T4 آغاز شده‌اند در قسمت‌های بعدی برای سفارشی سازی کدهای تولیدی استفاده خواهیم کرد.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز می‌توان پیاده سازی کرد:
Scaffold Controller Task
نوشتن این دستور نیز ساده است. حروف sca را تایپ کرده و دکمه tab را فشار دهید. منویی ظاهر خواهد شد که امکان انتخاب دستور Scaffold را می‌دهد. یا برای نوشتن Controller نیز به همین نحو می‌توان عمل کرد.
نکته و مزیت مهم دیگری که در اینجا در دسترس می‌باشد، سوئیچ‌های خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچ‌های قابل استفاده است.


برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
اگر این دستور را اجرا کنیم به همان نتیجه حاصل از مراحل توضیح داده شده قبل خواهیم رسید؛ البته یا یک تفاوت: یک Partial View اضافه‌تر نیز به نام CreateOrEdit در پوشه Views\Tasks ایجاد شده است. این Partial View بر اساس بازخورد برنامه نویس‌ها مبنی بر اینکه Viewهای Edit و Create بسیار شبیه به هم هستند، ایجاد شده است.


بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding

در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
البته اگر دستور فوق را به همین نحو اجرا کنید با یک سری خطای Skipping مواجه خواهید شد مبنی بر اینکه فایل‌های قبلی موجود هستند و این دستور قصد بازنویسی آن‌ها را ندارد. برای اجبار به تولید مجدد کدهای موجود می‌توان از سوئیچ Force استفاده کرد:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
اتفاقی که در اینجا رخ خواهد داد، بازنویسی کد بی‌کیفت ابتدایی همراه با وهله سازی مستقیم DbContext در کنترلر، به نمونه بهتری که از الگوی مخزن استفاده می‌کند می‌باشد:
    public class TasksController : Controller
    {
        private readonly ITaskRepository taskRepository;

        // If you are using Dependency Injection, you can delete the following constructor
        public TasksController()
            : this(new TaskRepository())
        {
        }

        public TasksController(ITaskRepository taskRepository)
        {
            this.taskRepository = taskRepository;
        }
کیفیت کد تولیدی جدید مبتنی بر الگوی مخزن بد نیست؛ دقیقا همانی است که در هزاران سایت اینترنتی تبلیغ می‌شود؛ اما ... آنچنان مناسب هم نیست و اشکالات زیر را به همراه دارد:
public interface ITaskRepository : IDisposable
{
  IQueryable<Task> All { get; }
  IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties);
  Task Find(int id);
  void InsertOrUpdate(Task task);
  void Delete(int id);
  void Save();
}
اگر به ITaskRepository تولیدی دقت کنیم دارای خروجی IQueryable است؛ به این حالت leaky abstraction گفته می‌شود. زیرا امکان تغییر کلی یک خروجی IQueryable در لایه‌های دیگر برنامه وجود دارد و حد و مرز سیستم توسط آن مشخص نخواهد شد. بهتر است خروجی‌های لایه سرویس یا لایه مخزن در اینجا از نوع‌های IList یا IEnumerable باشند که درون آن‌ها از IQueryable‌ها برای پیاده سازی منطق مورد نظر کمک گرفته شده است.
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزن‌های تعریف شده را تک موجودیتی می‌کند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیت‌ها به کلاس‌های مخزن واگذار شده‌اند و کنترلرها به این نحو خلوت‌تر گردیده‌اند، یک مرحله پیشرفت محسوب می‌شود.
نظرات مطالب
EF Code First #11
سلام. با توجه به مواردی که تا این شماره در مورد EF Code First فرمودید، بنده یک معماری مناسب برنامه را با استفاده از EF  اینگونه درک کردم:

1- Domain Layer یا Model Layer: با استفاده از Code First فقط Entityها تعریف می شود (بدون Mapping و Data Annotation)

2- DAL: با استفاده از DbContext و Fluent API موارد مرتبط با Mapping و Data Annotation و ساخت DbSet ها تعریف می شود.

3- Facade DAL: با استفاده از WCF یا هر تکنیک سرویس گرایی دیگر یک لایه سطح بالا بر روی DAL کشیده می شود تا در BLL تنها از آن استفاده شود و عملا لایه های BLL و UI از EF هیچ اطلاعی نخواهند داشت.

4- BLL: کلاس های مربوط به برنامه، Rule ها و Infrastructureها
5- UI: شامل پروژه ASP.NET MVC، WPF و یا برنامه های موبایلی

آیا این معماری درک درستی از مطالب ارائه شده توسط شما است؟
و سوال دوم اینکه در مورد برنامه هایی با منطق MVC و یا MVVM لایه مدل تقریبا با این معماری چیزی نخواهد بود و تنها ویو مدل ها به چشم خواهند خورد. آیا این مورد هم درست است؟