نظرات مطالب
آشنایی با Fluent interfaces
Constructor کلاس Restrictions به صورت ( Public Restriction(MathMethods parent, int valنیز درست است ؟؟
مطالب
PowerShell 7.x - قسمت چهارم - نوشتن اولین اسکریپت
دستوراتی که درون کنسول مینویسیم، تک خطی یا one-linear هستند؛ هر چند میتوان با زدن کلیدهای Shift + Enter دستورات چندخطی هم نوشت یا حتی با گذاشتن semicolon بعد از هر دستور میتوانیم دریک خط چندین دستور را پشت‌سر هم بنویسیم. اما برای نوشتن دستورات طولانی‌تر بهتر است دستورات را درون فایل‌های جدایی قرار دهیم و از VSCode یا PowerShell ISE (فقط در ویندوز) نیز برای نوشتن اسکریپت‌ها استفاده کرد. اسکریپت‌های PowerShell با پسوند ps1 و psm1 (برای نوشتن ماژول) هستند؛ هر چند چندین پسوند دیگر نیز برای فایل‌های PowerShell وجود دارند که در اینجا میتوانید لیست آنها را مشاهده کنید. درون یک فایل ps1 امکان نوشتن و ترکیب دستورات مختلف را داریم. همچنین میتوانیم از امکانات زبان سی‌شارپ هم استفاده کنیم؛ زیرا PowerShell در واقع اپلیکیشنی است که توسط NET Core. و با زبان #C نوشته شده‌است. در نتیجه میتوانیم بگوئیم زبان اسکریپتی که در PowerShell استفاده میشود، یک DSL برای زبان #C است. در PowerShell همه چیز یک آبجکت محسوب میشود. برای تست این مورد میتوانید درون کنسول PowerShell دستور زیر را وارد کنید:
PS> "" | Get-Member
دستور فوق یک لیست از تمامی توابع و پراپرتی‌های نوع System.String را نمایش خواهد داد:
   TypeName: System.String

Name                 MemberType            Definition
----                 ----------            ----------
Clone                Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo            Method                int CompareTo(System.Object value), int CompareTo(strin…
Contains             Method                bool Contains(string value), bool Contains(string value…
CopyTo               Method                void CopyTo(int sourceIndex, char[] destination, int de…
EndsWith             Method                bool EndsWith(string value), bool EndsWith(string value…
EnumerateRunes       Method                System.Text.StringRuneEnumerator EnumerateRunes()
Equals               Method                bool Equals(System.Object obj), bool Equals(string valu…
GetEnumerator        Method                System.CharEnumerator GetEnumerator(), System.Collectio…
GetHashCode          Method                int GetHashCode(), int GetHashCode(System.StringCompari…
GetPinnableReference Method                System.Char&, System.Private.CoreLib, Version=6.0.0.0, …
GetType              Method                type GetType()
GetTypeCode          Method                System.TypeCode GetTypeCode(), System.TypeCode IConvert…
IndexOf              Method                int IndexOf(char value), int IndexOf(char value, int st…
IndexOfAny           Method                int IndexOfAny(char[] anyOf), int IndexOfAny(char[] any…
Insert               Method                string Insert(int startIndex, string value)
IsNormalized         Method                bool IsNormalized(), bool IsNormalized(System.Text.Norm…
LastIndexOf          Method                int LastIndexOf(string value, int startIndex), int Last…
LastIndexOfAny       Method                int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(ch…
Normalize            Method                string Normalize(), string Normalize(System.Text.Normal…
PadLeft              Method                string PadLeft(int totalWidth), string PadLeft(int tota…
PadRight             Method                string PadRight(int totalWidth), string PadRight(int to…
Remove               Method                string Remove(int startIndex, int count), string Remove…
Replace              Method                string Replace(string oldValue, string newValue, bool i…
ReplaceLineEndings   Method                string ReplaceLineEndings(), string ReplaceLineEndings(…
Split                Method                string[] Split(char separator, System.StringSplitOption…
StartsWith           Method                bool StartsWith(string value), bool StartsWith(string v…
Substring            Method                string Substring(int startIndex), string Substring(int …
ToBoolean            Method                bool IConvertible.ToBoolean(System.IFormatProvider prov…
ToByte               Method                byte IConvertible.ToByte(System.IFormatProvider provide…
ToChar               Method                char IConvertible.ToChar(System.IFormatProvider provide…
ToCharArray          Method                char[] ToCharArray(), char[] ToCharArray(int startIndex…
ToDateTime           Method                datetime IConvertible.ToDateTime(System.IFormatProvider…
ToDecimal            Method                decimal IConvertible.ToDecimal(System.IFormatProvider p…
ToDouble             Method                double IConvertible.ToDouble(System.IFormatProvider pro…
ToInt16              Method                short IConvertible.ToInt16(System.IFormatProvider provi…
ToInt32              Method                int IConvertible.ToInt32(System.IFormatProvider provide…
ToInt64              Method                long IConvertible.ToInt64(System.IFormatProvider provid…
ToLower              Method                string ToLower(), string ToLower(cultureinfo culture)
ToLowerInvariant     Method                string ToLowerInvariant()
ToSByte              Method                sbyte IConvertible.ToSByte(System.IFormatProvider provi…
ToSingle             Method                float IConvertible.ToSingle(System.IFormatProvider prov…
ToString             Method                string ToString(), string ToString(System.IFormatProvid…
ToType               Method                System.Object IConvertible.ToType(type conversionType, …
ToUInt16             Method                ushort IConvertible.ToUInt16(System.IFormatProvider pro…
ToUInt32             Method                uint IConvertible.ToUInt32(System.IFormatProvider provi…
ToUInt64             Method                ulong IConvertible.ToUInt64(System.IFormatProvider prov…
ToUpper              Method                string ToUpper(), string ToUpper(cultureinfo culture)
ToUpperInvariant     Method                string ToUpperInvariant()
Trim                 Method                string Trim(), string Trim(char trimChar), string Trim(…
TrimEnd              Method                string TrimEnd(), string TrimEnd(char trimChar), string…
TrimStart            Method                string TrimStart(), string TrimStart(char trimChar), st…
TryCopyTo            Method                bool TryCopyTo(System.Span[char] destination)
Chars                ParameterizedProperty char Chars(int index) {get;}
Length               Property              int Length {get;}
در واقع میتوانیم بگوئیم هرچیزی در PowerShell یک آبجکت NET. است. در ادامه لیستی را از قابلیت‌های PowerShell به عنوان یک زبان اسکریپتی، بررسی خواهیم کرد.

تعریف متغیر
برای تعریف یک متغیر از علامت $ قبل از نام متغیر استفاده میکنیم. نوع متغیر نیز براساس مقداری که به آن انتساب داده میشود، تعیین خواهد شد: 
$stringVariable = "Hello World"
$letter = 'A'
$isEnabled = $false
$age = 33
$height = 76
$doubleVar = 54321.21
$singleVar = 76549.11
$longVar = 2382.22
$dateVar = "July 24, 1986"
$arrayVar = "A", "B", "C"
$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
همچنین میتوانیم نوع متغیر را نیز به صورت صریح تعیین کنیم: 
[string]$stringVariable = "Hello World"
[char]$letter = 'A'
[bool]$isEnabled = $false
[int]$age = 33
[decimal]$height = 76
[double]$doubleVar = 54321.21
[single]$singleVar = 76549.11
[long]$longVar = 2382.22
[DateTime]$dateVar = "July 24, 1986"
[array]$arrayVar = "A", "B", "C"
[hashtable]$hashtableVar = @{ Name = "Sirwan"; Age = 33; }
لازم به ذکر است scope متغیرها در حالت پیش‌فرض به local تنظیم میشود. به این معنا که در جایی که تعریف میشوند، قابل دسترسی خواهند بود. قاعدتاً اگر متغیرها را در ابتدای اسکریپت تعریف کنید، میدان دید آن به صورت سراسری خواهد بود و در هرجایی از کد در دسترس خواهند بود. اما برای اینکه به صورت صریح یک متغیر را به صورت سراسری تعریف کنیم میتوانیم از کلمه‌کلیدی global بعد از تعیین نوع متغیر و قبل از علامت $ استفاده کنیم: 
$global:stringVariable = "Hello World"

// Or

[string]$global:stringVariable = "Hello World"

عبارات شرطی
همانند دیگر زبان‌های اسکریپتی، در PowerShell نیز قابلیت تعریف ساختارهای شرطی وجود دارد: 
$guess = 20
switch ($guess) {
    {$_ -eq 20} { Write-Host "You guessed right!" }
    {$_ -gt 20} { Write-Host "You guessed too high!" }
    {$_ -lt 20} { Write-Host "You guessed too low!" }
    default { Write-Host "You didn't guess a number!" }
}

$guess = 20
if ($guess -eq 20) { 
    Write-Host "You guessed right!" 
}
elseif ($guess -gt 20) { 
    Write-Host "You guessed too high!" 
}
elseif ($guess -lt 20) { 
    Write-Host "You guessed too low!" 
}
else { 
  Write-Host "You didn't guess a number!"
}
همانطور که مشاهده میکنید از یکسری اپراتور برای بررسی شرط‌ها استفاده شده است. در اینجا میتوانید لیست کامل آنها را مشاهده کنید. لازم به ذکر است که از PowerShell 7.0 به بعد نیز Ternary Operator اضافه شده است: 
$message = (Test-Path $path) ? "Path exists" : "Path not found"

حلقه‌ها
همچنین از حالت‌های مختلف loop نیز پشتیبانی میشود: 
[int]$num = 10
for ($i = 1; $i -le $num; $i++) {
    Write-Host "`n"

    for ($j = 1; $j -le $num; $j++) {
        Write-Host -NoNewline -ForegroundColor Green ($i * $j).ToString().PadLeft(6)
    }
}
Write-Host "`n`n"

[int]$counter = 1
while ($counter -le 10) {
    Write-Host "Hello World"
    $counter++
}

do {
    Write-Host "Hello World"
    $counter++
} while ($counter -le 10)

foreach ($i in 1..10) {
    Write-Host "Hello World"
}

$items = "Hello", "World"
$items | ForEach-Object {
    Write-Host $_
}

لازم به ذکر است برای ForEach-Object از % نیز میتوانید استفاده کنید. اما بطور کلی بهتر است تا حد امکان از aliaseها استفاده نکنید؛ زیرا خوانایی کد را مقداری سخت میکند. این مورد توسط خود VSCode هم هشدار داده میشود: 


آرایه‌ها
در PowerShell به صورت پیش‌فرض آرایه‌ها از نوع []System.Object در نظر گرفته میشوند. در اینجا نیز آرایه‌ها immutable هستند. چندین روش برای ایجاد آرایه وجود دارد. در ادامه یک آرایه خالی را تعریف کرده‌ایم: 
$array = @()
یک روش دیگر تعریف آرایه اینگونه است: 
$myArray = [Object[]]::new(10)
$byteArray = [Byte[]]::new(100)
$ipAddresses = [IPAddress[]]::new(5)

$mylist = [System.Collections.Generic.List[int]]::new()
از PowerShell 5.0 به بعد میتوانیم سینتکس namespaceها را با کمک using خلاصه‌تر بنویسیم:
using namespace System.Collections.Generic
$mylist = [List[int]]::new()
از range operator هم میتوانیم برای مقداردهی یک آرایه استفاده کرد: 
$numbers = 1..10
$alphabet = "a".."z"

foreach ($item in 1..10) {
    Write-Host "Hello World"
}
برای دسترسی به عناصر یک آرایه نیز میتوانیم از range operator استفاده کنیم: 
$numbers = 1..10

$numbers
Write-Output "".PadRight(10, "-")
$numbers[1..2 + 2..4]
همانطور که مشاهده میکنید از اپراتور + برای append کردن یک بازه از اعداد، به آرایه استفاده کرده‌ایم. دقت داشته باشید که با هر بار اضافه/حذف آیتم‌ها، یک آرایه جدید ساخته میشود. این مورد در آرایه‌هایی با سایز بزرگ میتواند مشکل کارآیی ایجاد کند. بنابراین اگر عملیات اضافه کردن و حذف کردن از یک آرایه را زیاد انجام میدهید، بهتر است از ArrayList استفاده کنید. تفاوت آن نیز این است که برخلاف آرایه‌های عادی، سایز ArrayList ثابت نیست. تعریف ArrayList نیز اینگونه است: 
$colours = [System.Collections.ArrayList]@("Red", "Green", "Blue")
$colours.Add("Yellow")
$colours.Remove("Red")
از دیگر کالکشن‌های NET. نیز میتوانید استفاده کنید؛ به عنوان مثال در مثال زیر از List برای اضافه کردن ده هزار آیتم استفاده کرده‌ایم:
using namespace System.Collections.Generic

$mylist = [List[int]]::new()
Measure-Command { 1..100000 | ForEach-Object { $mylist.Add($_) } }
Measure-Command { $mylist.Where({ $_ -gt 10000 }) }
Measure-Command { $mylist.Contains(10000) }
نکته: کد فوق در حالت استفاده از ArrayList مقداری کندتر است. دلیل آن نیز این است که ArrayList امکان اضافه کردن هر آبجکتی را به ما میدهد. در نتیجه موقع جستجو باید یکبار عملیات unboxing را برای تشخیص نوع درخواست شده انجام دهد.

Hashtable
ساختار دیگری که میتوانید استفاده کنید Hash Tableها هستند؛ از این ساختار برای ایجاد custom objects و همچنین پاس دادن پارامترها به یک command استفاده میشود:
$sensors = @{
    tempreture = "Temperature"
    humidity   = "Humidity"
    pressure   = "Pressure"
    light      = "Light"
    noise      = "Noise"
    co2        = "CO2"
    battery    = "Battery"
    min_temp   = "Min Temp"
    max_temp   = "Max Temp"
}
با دستور زیر میتوانید لیستی از Commandهایی که ورودی‌شان از نوع Hash table است را مشاهده کنید:
PS> Get-Command -ParameterType Hashtable


CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        TabExpansion2                                                 
Cmdlet          Add-Member                                         7.0.0.0    Microsoft.Powe…
Cmdlet          ConvertTo-Html                                     7.0.0.0    Microsoft.Powe…
Cmdlet          Get-Job                                            7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-Command                                     7.2.7.500  Microsoft.Powe…
Cmdlet          Invoke-RestMethod                                  7.0.0.0    Microsoft.Powe…
Cmdlet          Invoke-WebRequest                                  7.0.0.0    Microsoft.Powe…
Cmdlet          New-Object                                         7.0.0.0    Microsoft.Powe…
Cmdlet          New-PSRoleCapabilityFile                           7.2.7.500  Microsoft.Powe…
Cmdlet          New-PSSession                                      7.2.7.500  Microsoft.Powe…
Cmdlet          Remove-Job                                         7.2.7.500  Microsoft.Powe…
Cmdlet          Select-Xml                                         7.0.0.0    Microsoft.Powe…
Cmdlet          Set-PSReadLineOption                               2.1.0      PSReadLine
Cmdlet          Stop-Job                                           7.2.7.500  Microsoft.Powe…
Cmdlet          Wait-Job                                           7.2.7.500  Microsoft.Powe…

توابع
برای ایجاد توابع در PowerShell میتوانید از سینتکس زیر استفاده کنید:
function Write-HelloWorld {
    Write-Host "Hello World"
}
برای نامگذاری توابع بهتر است از قالب verb-noun استفاده کنید. برای قسمت verb نیز بهتر است از یکسری افعال تائیدشده (approved verbs) که مستندات مایکروسافت پیشنهاد میدهد استفاده کنید (+) در این زمینه VSCode در صورت انتخاب یک نام نامناسب به شما هشدار خواهد:

تفاوت بین Function و Cmdlet چیست؟
توابع و cmdletها عملاً از لحاظ کاربرد باهم تفاوتی ندارند. تنها تفاوت آنها در نحوه ساخت‌شان است. cmdletها با #C ساخته میشوند. به این معنا که یکسری DLL کامپایل شده هستند که در نهایت از آنها استفاده خواهیم کرد. اما توابع در PowerShell با کمک سینتکسی که اشاره شد ایجاد میشوند. توسط دستور زیر میتوانیم لیستی از توابعی را که درون سشن PowerShellمان بارگذاری شده‌اند، ببینیم: 
PS> Get-Command -CommandType Function

در PowerShell نیز توابع قابلیت دریافت ورودی را نیز دارند. در ساده‌ترین حالت میتوانیم از آرایه args$ استفاده کنیم؛ دقیقاً چیزی مشابه arguments در JavaScript است:
function Write-HelloWorld {
    Write-Host "First Argument: $($args[0])"
    Write-Host "Second Argument: $($args[1])"
    Write-Host "Third Argument: $($args[2])"
    Write-Host "Fourth Argument: $($args[3])"
}
به این حالت Positional Parameters گفته میشود. یک روش دیگر تعریف پارامتر استفاده از Named Parameters است:
function Write-HelloWorld(
    [string]$first,
    [string]$second,
    [string]$third,
    [string]$fourth
) {
    Write-Host "First Argument: $($first)"
    Write-Host "Second Argument: $($second)"
    Write-Host "Third Argument: $($third)"
    Write-Host "Fourth Argument: $($fourth)"
}

در قسمت بعد در مورد Advanced Functionها صحبت خواهیم کرد و اجزای دیگر توابع را بیشتر توضیح خواهیم داد.

یک نکته در مورد خروجی دستورات درون کنسول
در ویندوز میتوانیم خروجی کنسول را به Out-GridView پایپ کنیم که در واقع یک GUI برای نمایش دادن خروجی کنسول است. این کامند فقط در ویندوز قابل استفاده است:

یک نسخه cross-platform آن نیز که مناسب کنسول است تهیه شده که میتوانید از آن استفاده کنید: 
Get-Command -ParameterType Hashtable | Out-ConsoleGridview
با این خروجی:

البته به صورت پیش‌فرض نصب نیست و باید از طریق PowerShell Gallery آن را نصب کنید:

Install-Module -Name Microsoft.PowerShell.ConsoleGuiTools


مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دوازدهم - توزیع برنامه
یکی از مشکلاتی را که حین کار با AngularJS 2.0 به کرات شاهدش خواهید بود، کش شدن تک اسکریپت‌های ماژول‌های آن است. برای مثال فایل ts ایی را تغییر می‌دهید؛ به فایل js معادل آن کامپایل می‌شود. چون برنامه ماژولار است و این ماژول پیشتر توسط مرورگر بارگذاری شده‌است، بار دیگر نسبت به دریافت مجدد آن اقدام نمی‌کند. همچنین با ارائه‌ی نگارش RC، دیگر خبری از فایل‌های bundle این مجموعه نیست و اینبار اگر تبادلات شبکه‌ی بین سرور و برنامه را مرور کنید، به چند صد رفت و برگشت، برای دریافت فایل‌های JS کتابخانه‌های مرتبط خواهید رسید که اصلا بهینه نیست. در این قسمت قصد داریم، یک Gulp Task را ایجاد کنیم تا تمام اسکریپت‌های موجود را با هم یکی کرده و توزیع برنامه را ساده‌تر کند؛ به همراه بالا رفتن سرعت کار با این سیستم، بدون نیازی به توزیع تک تک فایل‌های js نهایی، که شاید صدها فایل باشند.


نصب پیشنیازهای کار با Gulp و TypeScript

فایل package.json در قسمت اول این سری معرفی شد. دراینجا قسمت devDependencies آن‌را به نحو ذیل تکمیل کنید:
"devDependencies": {
        "typescript": "^1.8.10",
        "gulp": "^3.9.1",
        "path": "^0.12.7",
        "gulp-clean": "^0.3.2",
        "fs": "^0.0.2",
        "gulp-concat": "^2.6.0",
        "gulp-typescript": "^2.13.1",
        "gulp-tsc": "^1.1.5",
        "del": "^2.2.0",
        "gulp-autoprefixer": "^3.1.0",
        "gulp-cssnano": "^2.0.0",
        "gulp-html-replace": "^1.5.4",
        "gulp-htmlmin": "^1.0.5",
        "gulp-uglify": "^1.5.3",
        "merge-stream": "^1.0.0",
        "systemjs-builder": "^0.15.16",
        "typings": "^0.8.1"
    },
به این ترتیب، پس از ذخیره‌ی فایل و یا کلیک راست بر روی نام فایل و انتخاب گزینه‌ی restore packages، وابستگی‌هایی مانند gulp، gulp-typescript و یک سری فشرده ساز CSS و HTML دریافت خواهند شد.
نکته‌ی مهم آن systemjs-builder است. این کتابخانه کار کامپایل systemjs.config.js را به یک تک اسکریپت انجام می‌دهد. به این ترتیب مشکل صدها بار رفت و برگشت به سرور، برای دریافت وابستگی‌های AngularJS 2.0، به طور کامل برطرف می‌شود.


افزودن فایل gulpfile.js به پروژه

یا یک فایل جدید جاوا اسکریپتی را به نام gulpfile.js به ریشه‌ی پروژه اضافه کنید و یا از منوی project -> add new item نیز می‌توانید گزینه‌ی gulp configuration file را در VS 2015 انتخاب نمائید. محتوای این فایل را به نحو ذیل تغییر دهید:
var gulp = require("gulp"),
    concat = require("gulp-concat"),
    tsc = require("gulp-typescript"),
    jsMinify = require("gulp-uglify"),
    cssPrefixer = require("gulp-autoprefixer"),
    cssMinify = require("gulp-cssnano"),
    del = require("del"),
    merge = require("merge-stream"),
    minifyHTML = require('gulp-htmlmin'),
    SystemBuilder = require("systemjs-builder");
 
var appFolder = "./app";
var outFolder = "wwwroot";
 
gulp.task("clean", () => {
    return del(outFolder);
});
 
gulp.task("shims", () => {
    return gulp.src([
            "node_modules/es6-shim/es6-shim.js",
            "node_modules/zone.js/dist/zone.js",
            "node_modules/reflect-metadata/Reflect.js"
    ])
    .pipe(concat("shims.js"))
    .pipe(jsMinify())
    .pipe(gulp.dest(outFolder + "/js/"));
});
 
gulp.task("tsc", () => {
    var tsProject = tsc.createProject("./tsconfig.json");
    var tsResult = gulp.src([
         appFolder + "/**/*.ts"
    ])
    .pipe(tsc(tsProject), undefined, tsc.reporter.fullReporter());
 
    return tsResult.js.pipe(gulp.dest("build/"));
});
 
gulp.task("system-build", ["tsc"], () => {
    var builder = new SystemBuilder();
 
    return builder.loadConfig("systemjs.config.js")
        .then(() => builder.buildStatic(appFolder, outFolder + "/js/bundle.js"))
        .then(() => del("build"));
});
 
 
gulp.task("buildAndMinify", ["system-build"], () => {
    var bundle = gulp.src(outFolder + "/js/bundle.js")
        .pipe(jsMinify())
        .pipe(gulp.dest(outFolder + "/js/"));
 
    var css = gulp.src(outFolder + "/css/styles.css")
        .pipe(cssMinify())
        .pipe(gulp.dest(outFolder + "/css/"));
 
    return merge(bundle, css);
}); 
 
gulp.task("favicon", function () {
    return gulp.src("./app/favicon.ico")
      .pipe(gulp.dest(outFolder));
});
 
gulp.task("css", function () {
    return gulp.src(appFolder + "/**/*.css")
      .pipe(cssPrefixer())
      .pipe(cssMinify())
      .pipe(gulp.dest(outFolder));
});
 
gulp.task("templates", function () {
    return gulp.src(appFolder + "/**/*.html")
        .pipe(minifyHTML())
        .pipe(gulp.dest(outFolder));
});
 
gulp.task("assets", ["templates", "css", "favicon"], function () {
    return gulp.src(appFolder + "/**/*.png")
      .pipe(gulp.dest(outFolder));
}); 
 
gulp.task("otherScriptsAndStyles", () => {
    gulp.src([
            "jquery/dist/jquery.*js",
            "bootstrap/dist/js/bootstrap*.js"
    ], {
        cwd: "node_modules/**"
    })
    .pipe(gulp.dest(outFolder + "/js/"));
 
    gulp.src([
        "node_modules/bootstrap/dist/css/bootstrap.css"
    ]).pipe(cssMinify()).pipe(gulp.dest(outFolder + "/css/"));
 
    gulp.src([
        "node_modules/bootstrap/fonts/*.*"
    ]).pipe(gulp.dest(outFolder + "/fonts/"));
}); 
 
//gulp.task("watch.tsc", ["tsc"], function () {
//    return gulp.watch(appFolder + "/**/*.ts", ["tsc"]);
//});
 
//gulp.task("watch", ["watch.tsc"]); 
 
gulp.task("default", [
    "shims",
    "buildAndMinify",
    "assets",
    "otherScriptsAndStyles"
    //,"watch"
]);
توضیحات

در این فایل فرض شده‌است که خروجی نهایی برنامه قرار است در پوشه‌ای به نام wwwroot کپی شود و پوشه‌ی اصلی برنامه، همان پوشه‌ای به نام app، در ریشه‌ی پروژه است.
 var appFolder = "./app";
var outFolder = "wwwroot";
سپس در اینجا یک سری task کامپایل و کپی کردن فایل‌ها تهیه شده‌اند:
 1) وظیفه‌ی clean، کار تمیز کردن پوشه‌ی نهایی خروجی برنامه را انجام می‌دهد (حذف تمام فایل‌های آن).
 2) وظیفه‌ی shims، کار بسته بندی، یکی کردن و فشرده کردن سه اسکریپت es6-shim.js، zone.js و Reflect.js را انجام می‌دهد. سپس تک فایل حاصل را به نام shims.js، در پوشه‌ی wwwroot/js کپی می‌کند.
 3) وظیفه‌ی tsc، یکبار دیگر کامپایلر TypeScript را اجرا می‌کند تا مطمئن شویم با آخرین نگارش فایل‌های js برنامه کار می‌کنیم.
 4) وظیفه‌ی system-build، کار پردازش خودکار مداخل فایل systemjs.config.js را انجام می‌دهد. در آخرین نگارش ارائه شده‌ی AngularJS 2.0، بجای ذکر مداخل مورد نیاز آن، این  تک فایل systemjs.config.js را به صفحه پیوست می‌کنیم تا اسکریپت‌های لازم را (چند صد عدد)، به صورت خودکار بارگذاری کند. برای یکی کردن این چند صد عدد اسکریپت، از کتابخانه‌ی SystemBuilder  آن کمک گرفته و کار کامپایل نهایی را انجام می‌دهیم. خروجی تمام این فایل‌ها، به همراه کلیه فایل‌های js حاصل از کامپایل فایل‌های TypeScript برنامه، در فایلی به نام bundle.js کپی شده‌ی در پوشه‌ی wwwroot/js نوشته می‌شود. بنابراین دیگر نیازی نیست تا فایل‌های js پوشه‌ی app و همچنین فایل‌های js وابستگی‌های AngularJS 2.0 را توزیع کنیم. تک فایل bundle.js، حاوی تمام این‌ها است.
 5) وظیفه‌ی buildAndMinify کار اجرای وظیفه‌ی system-bulder را به همراه فشرده سازی تک فایل bundle.js، به عهده دارد. به علاوه اگر در پوشه‌ی css آن نیز فایل styles.css موجود باشد، آن را فشرده می‌کند.
 6) در ادامه یک سری وظیفه‌ی کپی کردن منابع برنامه را مشاهده می‌کنید. مانند favicon که کار کپی کردن این آیکن را به پوشه‌ی wwwroot انجام می‌دهد. وظیفه‌ی css، فایل‌های css موجود در پوشه‌های برنامه را به wwwroot و زیر پوشه‌های آن کپی می‌کند. وظیفه‌ی templates، کار کپی کردن فایل‌های html قالب‌های کامپوننت‌ها را بر عهده دارد. وظیفه‌ی assets، کار کپی کردن فایل‌های png را انجام می‌دهد.
 7) وظیفه‌ی otherScriptsAndStyles یک سری css و js ثالث را به پوشه‌ی wwwroot کپی می‌کند؛ مانند فایل‌های بوت استرپ و جی‌کوئری.
 8) وظیفه‌ی default، کار اجرای تمام این وظایف را با هم به عهده دارد.

اکنون اگر بر روی gulpfile.js کلیک راست کنید، گزینه‌ی task runner explorer ظاهر خواهد شد. آن‌را انتخاب کنید:


بر روی وظیفه‌ی default کلیک راست کرده و آن‌را اجرا کنید. پس از مدتی پوشه‌ی جدید wwwroot ساخته شده و فایل‌های نهایی برنامه به آن کپی می‌شوند.
 

اصلاح فایل index.html و یا Views\Shared\_Layout.cshtml

اکنون که تمام فایل‌های مورد نیاز پروژه در پوشه‌ی wwwroot کپی شده‌اند، نیاز است فایل index.html را به نحو ذیل تغییر داد:
<!DOCTYPE html>
<html>
<head>
    <base href="/">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
 
    <link href="~/wwwroot/css/bootstrap.css" rel="stylesheet" />
    <link href="~/wwwroot/app.component.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
 
    <script src="~/wwwroot/js/shims.js"></script>
</head>
 
<body>
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
 
    <script src="~/wwwroot/js/jquery/dist/jquery.min.js"></script>
    <script src="~/wwwroot/js/bootstrap/dist/js/bootstrap.min.js"></script>
    <script src="~/wwwroot/js/bundle.js"></script>
 
    @RenderSection("Scripts", required: false)
</body>
</html>
همانطور که مشاهده می‌کنید، اینبار دیگر خبری از systemjs.config.js و وابستگی‌های آن نیست.
اسکریپت‌های shims که برای مرورگرهای قدیمی‌تر درنظر گرفته شده‌اند، به تک فایل wwwroot/js/shims.js منتقل شده‌اند.
تمام اسکریپت‌های AngularJS 2.0 و وابستگی‌های آن به همراه تمام اسکریپت‌های برنامه‌ی خودمان، به تک فایل wwwroot/js/bundle.js منتقل شده‌اند.

اکنون اگر برنامه را اجرا کنید، سرعت آن با قبل قابل مقایسه نیست! اینبار دیگر نه نیازی به بارگذاری تمام وابستگی‌های AngularJS 2.0 به صورت مجزا توسط systemjs.config.js وجود دارد و نه به ازای مشاهده‌ی هر صفحه‌ای، یکبار قرار است فایل js کامپوننت آن بارگذاری شود. تمام این‌ها داخل فایل wwwroot/js/bundle.js قرار گرفته‌اند و تنها یکبار بارگذاری می‌شوند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part12.zip


خلاصه‌ی بحث

با نوشتن یک Gulp Task جدید می‌توان بر اساس فایل systemjs.config.js، تمام اسکریپت‌های دخیل در اجرای برنامه را به صورت خودکار یافته و به صورت یک تک فایل نهایی، بسته بندی و توزیع کرد.
مطالب
بررسی شیوه‌نامه‌های المان‌های پر کاربرد بوت استرپ 4
در این قسمت، شیوه‌نامه‌هایی را بررسی می‌کنیم که به المان‌های پر کاربردی مانند دکمه‌ها، لیست‌ها و نشان‌ها اعمال می‌شوند.


شیوه‌نامه‌های کار با دکمه‌ها در بوت استرپ 4

کلاس پایه ایجاد دکمه‌های بوت استرپی، کلاس btn است. البته آن‌را می‌توان با کلاس‌های دیگری نیز ترکیب کرد:
- کلاس btn را می‌توان بر روی المان‌هایی مانند anchor، button و input نیز اعمال کرد:
<a class="btn btn-primary" href="#" role="button">Link</a>
<button class="btn btn-primary" type="submit">Button</button>
<input class="btn btn-primary" type="submit" value="Input">
در این حالت تمام این المان‌ها یکسان به نظر می‌رسند:


- برای تعیین رنگ اجباری دکمه‌های بوت استرپ، از فرمول زیر استفاده می‌شود؛ مانند btn-primary:

<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-warning">Warning</button>
<button class="btn btn-info">Info</button>
<button class="btn btn-light">Light</button>
<button class="btn btn-dark">Dark</button>
با این خروجی:


همانطور که ملاحظه می‌کنید، رنگ این دکمه‌ها نیز نسبت به نگارش سوم آن، به روز رسانی شده‌است.

همچنین نگارش outline آن‌ها نیز قابل تعریف است؛ مانند btn-outline-primary:

<button class="btn btn-outline-primary">Primary</button>
<button class="btn btn-outline-secondary">Secondary</button>
<button class="btn btn-outline-success">Success</button>
<button class="btn btn-outline-danger">Danger</button>
<button class="btn btn-outline-warning">Warning</button>
<button class="btn btn-outline-info">Info</button>
<button class="btn btn-outline-light">Light</button>
<button class="btn btn-outline-dark">Dark</button>
با این خروجی:


- کلاس btn-size که در اینجا size می‌تواند sm یا lg باشد و سبب ایجاد دکمه‌هایی کوچک یا بزرگ می‌شوند.
- کلاس btn-block سبب می‌شود تا دکمه‌ای کل عرض container را پر کند.
<button class="btn btn-primary">Default</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary btn-block">Block</button>
با این خروجی:


- امکان اعمال کلاس‌های active و disabled نیز در اینجا میسر است:
<h2>States</h2>
<h3>Active</h3>
<button class="btn btn-primary active">Active Button</button>

<h3>Disabled</h3>
<button class="btn btn-primary disabled">Disabled Button</button>
<a class="btn btn-primary disabled" href="#">Disabled Link Button</a>
با این خروجی:



تشکیل گروهی از دکمه‌ها در بوت استرپ 4

برای تبدیل تعدادی از دکمه‌ها به یک گروه، از کلاس btn-group استفاده می‌شود. همچنین امکان تشکیل گزینه‌ی عمودی آن، با کلاس btn-group-vertical نیز وجود دارد. در این  حالت دکمه‌ها بجای نمایش افقی، به صورت یک ستون نمایش داده می‌شوند. کلاس btn-toolbar نیز برای تشکیل نوار ابزاری از دکمه‌ها در نظر گرفته شده‌است. توسط کلاس‌های btn-group-sm و یا btn-group-lg می‌توان اندازه‌ی این گروه‌ها را تغییر داد.
<div class="btn-toolbar" aria-label="All pets">
    <div class="btn-group mb-2 mr-2" aria-label="Common pets">
        <button type="button" class="btn btn-primary active">Cat</button>
        <button type="button" class="btn btn-primary">Dog</button>
        <button type="button" class="btn btn-primary">Fish</button>
        <button type="button" class="btn btn-primary">Bird</button>
    </div>

    <div class="btn-group mb-2" aria-label="Exotic pets">
        <button type="button" class="btn btn-primary">Amphibian</button>
        <button type="button" class="btn btn-primary active">Reptile</button>
        <button type="button" class="btn btn-primary">Other</button>
    </div>
</div>
با این خروجی:


در اینجا دو گروه از دکمه‌ها تشکیل شده‌اند که این‌ها را داخل یک btn-toolbar قرار داده‌ایم. همچنین تعریف aria-labelها به screen readers و معلولین کمک می‌کند.
به علاوه با استفاده از کلاس‌های mb-2 و mr-2، سبب ایجاد margin بین این نوار ابزار با متن زیر آن و همچنین بین خود آن‌ها شده‌ایم.

و حالت عمودی آن:
<div class="btn-group-vertical mb-2" aria-label="Exotic pets">
    <button type="button" class="btn btn-primary">Amphibian</button>
    <button type="button" class="btn btn-primary active">Reptile</button>
    <button type="button" class="btn btn-primary">Other</button>
</div>
چنین شکلی را پیدا می‌کند:



کلاس‌های جدید تشکیل Badges در بوت استرپ 4

برای تشکیل نشان/آرم از کلاس badge استفاده می‌شود و برای تغییر شکل آن می‌توان از کلاس badge-pill کمک گرفت. برای تغییر رنگ آن نیز فرمول زیر وجود دارد:


یک مثال:
    <div class="container">
        <div class="row">
            <section class="col-12">
                <h1>Our Commitment <span class="badge badge-info">to you</span></h1>
                <h3>Grooming <span class="badge badge-danger badge-pill">new</span></h3>
            </section>
        </div><!-- row -->
    </div><!-- content container -->
با این خروجی:


همانطور که مشاهده می‌کنید، یک badge همواره به اندازه‌ی container آن در آمده و نمایش داده می‌شود.


ایجاد لیستی از آیتم‌ها در بوت استرپ 4

بوت استرپ، کلاس‌هایی را برای ایجاد لیست‌هایی با ظاهر لیست‌های اندرویدی به همراه دارد که در ادامه با مثال‌هایی آن‌ها را بررسی خواهیم کرد.
<ul class="list-group mb-3">
    <li class="list-group-item active">Grooming</li>
    <li class="list-group-item list-group-item-action">
        General Health
    </li>
    <li class="list-group-item list-group-item-action">Nutrition</li>
    <li class="list-group-item list-group-item-action">
        Pest Control
    </li>
    <li class="list-group-item list-group-item-action">Vaccinations</li>
</ul>
با این خروجی:

در اینجا برای تشکیل لیستی با ظاهری شکیل‌تر، می‌توان ابتدا کلاس list-group را به ul انتساب داد و سپس به هر کدام از liهای آن کلاس list-group-item انتساب داده می‌شود. با افزودن کلاس active به اولین مورد، ظاهر آن با رنگی متمایز نمایان می‌شود. همچنین اگر علاقمند بودیم تا هر کدام از آیتم‌ها با عبور ماوس بر روی آن‌ها، با رنگ ملایمی مشخص شوند، می‌توان از کلاس list-group-item-action استفاده کرد.

این list-group را بر روی لینک‌ها و دکمه‌ها نیز می‌توان همانند قبل اعمال کرد:
<div class="list-group mb-3">
    <a class="list-group-item active" href="#">Grooming</a>
    <a class="list-group-item list-group-item-action list-group-item-success"
        href="#">Nutrition</a>
    <a class="list-group-item list-group-item-action  list-group-item-info"
        href="#">
        Pest Control
    </a>
    <a class="list-group-item list-group-item-action  list-group-item-warning"
        href="#">Vaccinations</a>
</div>
با این خروجی:


در اینجا از کلاس‌هایی مانند list-group-item-warning برای تغییر رنگ پس زمینه‌ی هر آیتم می‌توان کمک گرفت.

برای تعریف badge برای هر آیتم، می‌توان به صورت زیر عمل کرد:
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
    Nutrition
    <span class="badge badge-info badge-pill">12</span>
</li>
با این خروجی:


با اعمال کلاس‌های badge، این نشان نمایش داده می‌شود؛ اما دقیقا در کنار متن Nutrition در اینجا. برای اینکه آن‌را به سمت دیگر این ردیف منتقل و همچنین تراز عمودی آن‌را نیز به میانه‌ی صفحه تنظیم کنیم، می‌توان از Flexbox کمک گرفت. با اعمال d-flex، این ردیف تبدیل به یک Flexbox container می‌شود و سپس می‌توان کلاس‌های مخصوص Flexbox مانند justify-content-between و align-items-center را به این ردیف اعمال کرد تا ترازهای عمودی و افقی آیتم‌های قرار گرفته‌ی درون آن تغییر کنند.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_08.zip
نظرات مطالب
OpenCVSharp #6
برای کار با وب کم و دوربین دیجیتال، این مقدار را باید محاسبه کرد (شمارش تعداد فریم دریافتی در طی حداقل 3 ثانیه):
private static double getFps(CvCapture capture)
{
    double counter = 0;
    double seconds = 0;
    var watch = Stopwatch.StartNew();
    while (capture.QueryFrame() != null)
    {
        counter++;
        seconds = watch.ElapsedMilliseconds / (double)1000;
        if (seconds >= 3)
        {
            watch.Stop();
            break;
        }
    }
    var fps = counter / seconds;
    return fps;
}
و بعد برای استفاده:
using (var capture = CvCapture.FromCamera(index: 0))
{
    var fps = getFps(capture);
    capture.SetCaptureProperty(CvConst.CV_CAP_PROP_FPS, fps);
    var interval = (int)(1000 / fps);
مطالب
شمسی سازی Date-Picker توکار Angular Material 6x
Angular Material به همراه یک کامپوننت Date-Picker بسیار شکیل و حرفه‌ای است اما ... از تقویم شمسی پشتیبانی نمی‌کند. در این مطلب می‌خواهیم با تدارک یک DateAdapter سفارشی، این مشکل را برطرف کنیم تا در نهایت به یک چنین Date-Picker شمسی برسیم:



تاریخچه‌ی تغییرات کامپوننت Date-Picker

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

DateAdapter شمسی تهیه شده از کتابخانه‌ی jalali-moment برای تبدیل تاریخ‌ها استفاده می‌کند. بنابراین ابتدا نیاز است این وابستگی را نصب کرد:
 npm install jalali-moment --save


افزودن DateAdapter شمسی به پروژه

برای افزودن DateAdapter شمسی تهیه شده، فایل جدید app\shared\material.persian-date.adapter.ts را به برنامه اضافه کرده و به صورت زیر تکمیل کنید:
import { DateAdapter } from "@angular/material";
import * as jalaliMoment from "jalali-moment";

export const PERSIAN_DATE_FORMATS = {
  parse: {
    dateInput: "jYYYY/jMM/jDD"
  },
  display: {
    dateInput: "jYYYY/jMM/jDD",
    monthYearLabel: "jYYYY jMMMM",
    dateA11yLabel: "jYYYY/jMM/jDD",
    monthYearA11yLabel: "jYYYY jMMMM"
  }
};

export class MaterialPersianDateAdapter extends DateAdapter<jalaliMoment.Moment> {

  constructor() {
    super();
    super.setLocale("fa");
  }

  getYear(date: jalaliMoment.Moment): number {
    return this.clone(date).jYear();
  }

  getMonth(date: jalaliMoment.Moment): number {
    return this.clone(date).jMonth();
  }

  getDate(date: jalaliMoment.Moment): number {
    return this.clone(date).jDate();
  }

  getDayOfWeek(date: jalaliMoment.Moment): number {
    return this.clone(date).day();
  }

  getMonthNames(style: "long" | "short" | "narrow"): string[] {
    switch (style) {
      case "long":
      case "short":
        return jalaliMoment.localeData("fa").jMonths().slice(0);
      case "narrow":
        return jalaliMoment.localeData("fa").jMonthsShort().slice(0);
    }
  }

  getDateNames(): string[] {
    const valuesArray = Array(31);
    for (let i = 0; i < 31; i++) {
      valuesArray[i] = String(i + 1);
    }
    return valuesArray;
  }

  getDayOfWeekNames(style: "long" | "short" | "narrow"): string[] {
    switch (style) {
      case "long":
        return jalaliMoment.localeData("fa").weekdays().slice(0);
      case "short":
        return jalaliMoment.localeData("fa").weekdaysShort().slice(0);
      case "narrow":
        return ["ی", "د", "س", "چ", "پ", "ج", "ش"];
    }
  }

  getYearName(date: jalaliMoment.Moment): string {
    return this.clone(date).jYear().toString();
  }

  getFirstDayOfWeek(): number {
    return jalaliMoment.localeData("fa").firstDayOfWeek();
  }

  getNumDaysInMonth(date: jalaliMoment.Moment): number {
    return this.clone(date).jDaysInMonth();
  }

  clone(date: jalaliMoment.Moment): jalaliMoment.Moment {
    return date.clone().locale("fa");
  }

  createDate(year: number, month: number, date: number): jalaliMoment.Moment {
    if (month < 0 || month > 11) {
      throw Error(
        `Invalid month index "${month}". Month index has to be between 0 and 11.`
      );
    }
    if (date < 1) {
      throw Error(`Invalid date "${date}". Date has to be greater than 0.`);
    }
    const result = jalaliMoment()
      .jYear(year).jMonth(month).jDate(date)
      .hours(0).minutes(0).seconds(0).milliseconds(0)
      .locale("fa");

    if (this.getMonth(result) !== month) {
      throw Error(`Invalid date ${date} for month with index ${month}.`);
    }
    if (!result.isValid()) {
      throw Error(`Invalid date "${date}" for month with index "${month}".`);
    }
    return result;
  }

  today(): jalaliMoment.Moment {
    return jalaliMoment().locale("fa");
  }

  parse(value: any, parseFormat: string | string[]): jalaliMoment.Moment | null {
    if (value && typeof value === "string") {
      return jalaliMoment(value, parseFormat, "fa");
    }
    return value ? jalaliMoment(value).locale("fa") : null;
  }

  format(date: jalaliMoment.Moment, displayFormat: string): string {
    date = this.clone(date);
    if (!this.isValid(date)) {
      throw Error("JalaliMomentDateAdapter: Cannot format invalid date.");
    }
    return date.format(displayFormat);
  }

  addCalendarYears(date: jalaliMoment.Moment, years: number): jalaliMoment.Moment {
    return this.clone(date).add(years, "jYear");
  }

  addCalendarMonths(date: jalaliMoment.Moment, months: number): jalaliMoment.Moment {
    return this.clone(date).add(months, "jmonth");
  }

  addCalendarDays(date: jalaliMoment.Moment, days: number): jalaliMoment.Moment {
    return this.clone(date).add(days, "jDay");
  }

  toIso8601(date: jalaliMoment.Moment): string {
    return this.clone(date).format();
  }

  isDateInstance(obj: any): boolean {
    return jalaliMoment.isMoment(obj);
  }

  isValid(date: jalaliMoment.Moment): boolean {
    return this.clone(date).isValid();
  }

  invalid(): jalaliMoment.Moment {
    return jalaliMoment.invalid();
  }

  deserialize(value: any): jalaliMoment.Moment | null {
    let date;
    if (value instanceof Date) {
      date = jalaliMoment(value);
    }
    if (typeof value === "string") {
      if (!value) {
        return null;
      }
      date = jalaliMoment(value).locale("fa");
    }
    if (date && this.isValid(date)) {
      return date;
    }
    return super.deserialize(value);
  }
}
کار این Adapter و یا «وفق دهنده» این است که مشخص می‌کند، هفته‌ی ایرانی از چه روزی شروع می‌شود. نام روزهای هفته‌ی ایرانی چیست؟ برچسب‌های نام ماه‌های ایرانی چگونه باید تامین شوند و در کل جهت وفق دادن تقویم میلادی اصلی با تقویم شمسی، چه اجزایی باید به سیستم معرفی شوند تا این تقویم توکار بدون مشکل مانند قبل کار کند.
 
معرفی وفق دهنده‌ی شمسی به پروژه

پس از تعریف MaterialPersianDateAdapter و همچنین PERSIAN_DATE_FORMATS، برای معرفی آن‌ها به برنامه، فایل app\shared\material.module.ts را گشوده و به صورت زیر تغییر دهید:
import { NgModule } from "@angular/core";
import {  DateAdapter,  MAT_DATE_FORMATS,  MAT_DATE_LOCALE } from "@angular/material";

import { MaterialPersianDateAdapter, PERSIAN_DATE_FORMATS } from "./material.persian-date.adapter";

@NgModule({
  providers: [
    { provide: DateAdapter, useClass: MaterialPersianDateAdapter, deps: [MAT_DATE_LOCALE] },
    { provide: MAT_DATE_FORMATS, useValue: PERSIAN_DATE_FORMATS }
  ]
})
export class MaterialModule {
}
کار این تعاریف، تعویض DateAdapter اصلی میلادی، با نمونه‌ی شمسی است. همچنین فرمت نمایشی برچسب‌ها را نیز جایگزین می‌کند.

پس از آن اگر mat-datepicker را به نحو متداولی به صفحه اضافه کنیم:
<mat-form-field>
    <input matInput [matDatepicker]="picker6" placeholder="json gregorian input" [(ngModel)]="dateControl">
    <mat-datepicker-toggle matSuffix [for]="picker6"></mat-datepicker-toggle>
    <mat-datepicker #picker6></mat-datepicker>
</mat-form-field>
یک چنین خروجی حاصل خواهد شد:




چند مثال تکمیلی از کاربردهای کامپوننت mat-datepicker

1) استفاده از تاریخ میلادی رسیده‌ی از سمت سرور و نمایش آن
<mat-form-field>
    <input matInput [matDatepicker]="picker6" placeholder="json gregorian input" [(ngModel)]="dateControl">
    <mat-datepicker-toggle matSuffix [for]="picker6"></mat-datepicker-toggle>
    <mat-datepicker #picker6></mat-datepicker>
</mat-form-field>
با این کدها:
@Component()
export class PersianDatepickerComponent {

  jsonDate = "2018-01-08T20:21:29.4674496";
  dateControl = this.jsonDate;
}
در اینجا jsonDate همان رشته‌ی تاریخی است که از سمت سرور دریافت شده و میلادی است. با انتساب آن به ngModel، به صورت خودکار شمسی نمایش داده خواهد شد:




2) تعیین تاریخ آغاز تقویم و نمایش آن در حالت انتخاب سال
<mat-form-field>
    <input matInput [matDatepicker]="picker2" placeholder="startAt 2017-01-01 and startView=year">
    <mat-datepicker-toggle matSuffix [for]="picker2"></mat-datepicker-toggle>
    <mat-datepicker #picker2 startView="year" [startAt]="startDate"></mat-datepicker>
</mat-form-field>
با این کدها:
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent  {

  startDate = moment("2017-01-01", "YYYY-MM-DD"); // = moment.from("2017-01-01", "en");
  
}
در این مثال خاصیت startAt را به یک تاریخ میلادی متصل کرده‌ایم و همچنین خاصیت startView به year تنظیم شده‌است که یک چنین خروجی را در بار اول نمایش تقویم ایجاد می‌کند:




3) تعیین باز‌ه‌ی تاریخی قابل انتخاب توسط کاربر
<mat-form-field>
    <input matInput [matDatepicker]="picker3" [min]="minDate" [max]="maxDate" placeholder="min: 2017-10-02 and max: 1396-07-29">
    <mat-datepicker-toggle matSuffix [for]="picker3"></mat-datepicker-toggle>
    <mat-datepicker #picker3></mat-datepicker>
</mat-form-field>
با این کدها:
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent  {

  minDate = moment.from("2017-10-02", "en"); // = moment('2017-10-02', 'YYYY-MM-DD');
  maxDate = moment.from("1396-07-29", "fa"); // = moment('1396-07-29', 'jYYYY-jMM-jDD');
}
همانطور که ملاحظه می‌کنید کتابخانه‌ی jalali-moment می‌تواند تاریخ شمسی و یا میلادی را توسط متد from آن دریافت کند و هر دو حالت در اینجا پس از انتساب به خواص min و max تقویم، به خوبی کار کرده و سبب محدود ساختن بازه‌ی قابل انتخاب توسط کاربر می‌شوند.



در این تصویر روزهای خاکستری، قابل انتخاب نیستند و غیرفعال شده‌اند (چون min به 10 مهر و max به 29 مهر تنظیم شده‌است).


4) غیرفعال کردن روزهای قابل انتخاب بر اساس یک منطق سفارشی
<mat-form-field>
    <input matInput [matDatepicker]="picker4" [matDatepickerFilter]="myFilter" placeholder="Date validation - Datepicker Filter">
    <mat-datepicker-toggle matSuffix [for]="picker4"></mat-datepicker-toggle>
    <mat-datepicker #picker4></mat-datepicker>
</mat-form-field>
با این کدها:
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent {

  myFilter = (d: moment.Moment): boolean => {
    const day: number = d.day();
    // Prevent Thursday and Friday from being selected.
    return day !== 5 && day !== 4;
  }
}
در اینجا روزهای پنج‌شنبه و جمعه در تقویم نمایش داده شده، بر اساس تعریف matDatepickerFilter سفارشی، دیگر قابل انتخاب نیستند:



5) کار با رخ‌دادهای تقویم
<mat-form-field>
    <input matInput [matDatepicker]="picker5" (dateInput)="onInput($event)" (dateChange)="onChange($event)"
        placeholder="dateInput and dateChange events">
    <mat-datepicker-toggle matSuffix [for]="picker5"></mat-datepicker-toggle>
    <mat-datepicker #picker5></mat-datepicker>
</mat-form-field>
با این کدها:
import { MatDatepickerInputEvent } from "@angular/material";
import * as moment from "jalali-moment";

@Component()
export class PersianDatepickerComponent {

  onInput(event: MatDatepickerInputEvent<moment.Moment>) {
    console.log("OnInput: ", event.value);
  }

  onChange(event: MatDatepickerInputEvent<moment.Moment>) {
    const x = moment(event.value).format("jYYYY/jMM/jDD");
    console.log("OnChange: ", x);
  }
}
در اینجا نحوه‌ی واکنش نشان دادن به رخ‌دادهای dateInput و dateChange کامپوننت mat-datepicker را ملاحظه می‌کنید:


در اینجا، onInput، با ورود دستی اطلاعات به textbox کامپوننت، فعال می‌شود و onChange، در صورت انتخاب یک تاریخ از تقویم.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
مسیریابی در AngularJs #بخش سوم
در بخش‌های پیشین ( بخش اول و بخش دوم) به خوبی با اصول و روش مسیریابی (Routing) در AngularJS آشناشدیم. در این بخش می‌خواهم به برخی جزئیات درباره مسیریابی بپردازم.
اولین موضوع، تغییراتی است که از نسخه 1.2 به بعد در روش استفاده از سرویس مسیریابی در AngularJS بوجود آمده است. از نسخه 1.2 سرویس مسیریابی از هسته اصلی AngularJS خارج شد و برای استفاده از امکانات این سرویس باید  فایل angular-route.js و یا angular-route.min.js را به صفحه خود بیفزاییم:  
<script src="~/Scripts/angular.min.js"></script>
<script src="~/Scripts/angular-route.min.js"></script>
سپس باید هنگام تعریف ماژول، ngRoute را به عنوان وابستگی تزریق کنیم: 
var app = angular.module("mainApp", ['ngRoute']);
روش Controller as در AngularJS که از نسخه 1.2 به بعد امکان استفاده از آن وجود دارد قبلا معرفی شده است. با پاس کردن خصوصیت controllerAs به متد when می‌توان از viewای استفاده کرد که در آن از این روش استفاده شده است.
.when('/controllerAS', {
    controller:      'testController',
    controllerAs:    'tCtrl',
    template:        '<div>{{tCtrl.Title}}</div>'
})
 باقی ماجرا مانند گذشته است. 
موضوع دیگری که پرداختن به آن می‌تواند مفید باشد، بررسی بیشتر متد when است. وقتی در متد config ماژول از routeProvider$ استفاده می‌کنیم، داریم سرویس route$ را تنظیم، مقداردهی اولیه، و نمونه گیری می‌کنیم. درواقع با استفاده از متدهای when و otherwise داریم سرویس route$ را مقداردهی اولیه می‌کنیم (برای آشنایی با تقاوت factory، service و provider کلیک کنید ). خوب! جریان این مقادیری که به عنوان پارامتر به این متدها پاس می‌کنیم چیست؟
متد when به این صورت تعریف شده است:
when(string path, object route)

 پارامتر path در بخش‌های قبل به اندازه کافی معرفی شده است. پارامتر route یک شی است شامل اطلاعاتی که با تطبیق آدرس صفحه با پارامتر path، به  route.current$  مقداردهی می‌شود (حالا باید متوجه شده باشید که روال افزودن داده‌های سفارشی به سیستم مسیریابی و دسترسی به آن‌ها که در بخش دوم مطرح شد به چه شکل کار می‌کند). این شی می‌تواند خصوصیات از قبل تعریف شده‌ای داشده باشد که در ادامه آن‌ها را مرور می‌کنیم:
controller: می‌تواند یک رشته شامل نام کنترلر از قبل تعریف شده، یا یک تابع به عنوان تابع کنترلر باشد.
controllerAs: رشته‌ای شامل نام مستعار کنترلر.
template: رشته‌ای شامل قالب html، و یا تابعی که قالب html را بازمی‌گرداند. این خصوصیت بر templateUrl اولویت دارد. اگر مقدار این خصوصیت یک تابع باشد، routeParams$ به عنوان پارامتر ورودی به آن پاس می‌شود.
templateUrl: رشته‌ای شامل مسیر فایل قالب html، و یا تابعی که این رشته را بازمی‌گرداند. اگر مقدار این خصوصیت یک تابع باشد، routeParams$ به عنوان پارامتر ورودی به آن پاس می‌شود.
redirectTo: مقداری برای به روز رسانی  location$، و فراخوانی روال مسیر یابی. این مقدار می‌تواند یک رشته، و یا تابعی که یک رشته را بازمی‌گرداند باشد. اگر مقدار این خصوصیت یک تابع باشد، این پارامترها به آن پاس می‌شود:
  •  routeParams$ برای دسترسی به پارامترهای آمده در آدرس صفحه جاری.
  • ()location.path$ جاری به صورت یک رشته.
  • ()location.search$ جاری به صورت یک شی.
caseInsensitiveMatch: یک مقدار منطقی است که مشخص می‌کند بزرگ و کوچک بودن حروف در تطبیق آدرس صفحه با پارامتر route در نظر گرفته بشود یا نه. مقدار پیشفرض این خصوصیت false است. یعنی در همه مثالهایی که تا کنون زده شده، اگر بزرگ و کوچه بودن حروف آدرس صفحه با مقدار مشخص شده برای پارامتر route متفاوت باشد، روال مسیریابی انجام نخواهد شد. برای رفع این مشکل کافی است مقدار این خصوصیت را true قرار دهیم. برای مثال، مسیر 'controllerAS/' که بالاتر تعریف کرده‌ایم را درنظر بگیرید. اگر www.mySite.com/#/ControllerAS را وارد کنیم، هیچ اتفاقی نخواهد افتاد و در واقع این آدرس با route مشخص شده تطبیق پیدا نمی‌کند. اگر بخواهیم کوچک و بزرگ بودن حروف در نظر گرفته نشود، کافیست به این ترتیب عمل کنیم:
.when('/controllerAS', {
    controller:      'testController',
    controllerAs:    'tCtrl',
    template:        '<div>{{tCtrl.Title}}</div>',
    caseInsensitiveMatch: true
})
resolve: نگاشتی از وابستگی‌هایی که می‌خواهیم به کنترلر تزریق شود. قبلا مفهوم promise توضیح داده شده است . اگر هر یک از این وابستگی‌ها یک promise باشد، مسیریاب تا resolve شدن همه آن‌ها یا reject شدن یکی از آن‌ها منتظر می‌ماند. در صورتی که همه promiseها resolve شوند، رخداد routeChangeSuccess$، و در صورتی که یکی از آن‌ها reject شود رخداد routeChangeError$ اجرا می‌شود. یکی از کاربردهای resolve زمانیست که بخواهید جلوی تغییر محتویات صفحه، پیش از بارگذاری داده‌ای که از سمت سرور درخواست کرده‌اید را بگیرید. 
$routeProvider
    .when('/resolveTest',
        {
            resolve: {
                // این وابستگی بلافاصله بازمی‌گردد
                person: function () {
                    return {
                        name: "Hamid Saberi",
                        email: "Hamid.Saberi@Gmail.com"
                    }
                },
                // بازمی‌گرداند promise این وابستگی یک
                // شدن آن به تاخیر می‌افتد resolve پس تغییر مسیر تا 
                currentDetails: function ($http) {
                    return $http({
                        method: 'Get',
                        url: '/current_details'
                    });
                },
                //می‌توانیم از یک وابستگی در وابستگی دیگر استفاده کنیم
                facebookId: function ($http, currentDetails) {
                    $http({
                        method: 'GET',
                        url: 'http://facebook.com/api/current_user',
                        params: {
                            email: currentDetails.data.emails[0]
                        }
                    })
                },
                // بارگذاری فایل‌های اسکریپت مورد نیاز
                fileDeps:function($q, $rootScope){
                    var deferred = $q.defer();
                    var dependencies =
                    [
                        'controllers/AboutViewController.js',
                        'directives/some-directive.js'
                    ];
 
                    //$Script.js بارگذاری وابستگی‌ها با استفاده از 
                    $script(dependencies, function(){
                        // همه وابستگی‌ها بارگذاری شده اند
                        $rootScope.$apply(function(){
                            deferred.resolve();
                        });
                    });
 
                    return deferred.promise;
                }
            },
            controller: function ($scope, person, currentDetails, facebookId) {
                this.Person = person;
            },
            controllerAs: 'rtCtrl',
            template: '<div>{{rtCtrl.Person.name}}</div>',
            caseInsensitiveMatch: true
        })

نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
با فرض اینکه layout برنامه تعاریف لازم را دارد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/PersianDatePicker.css" rel="stylesheet" />
</head>
<body>
    @RenderBody()
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/PersianDatePicker.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
فایلی را به نام PersianDatePicker.cshtml در مسیر Views\Shared\EditorTemplates ایجاد کنید؛ با این محتوا:
@using System.Globalization
@model DateTime?
@{
    Func<DateTime, string> toPersianDate = date =>
    {
        var dateTime = new DateTime(date.Year, date.Month, date.Day, new GregorianCalendar());
        var persianCalendar = new PersianCalendar();
        return persianCalendar.GetYear(dateTime) + "/" + 
               persianCalendar.GetMonth(dateTime).ToString("00") + "/" + 
               persianCalendar.GetDayOfMonth(dateTime).ToString("00");
    };
    var today = toPersianDate(DateTime.Now);
    var name = this.ViewContext.ViewData.ModelMetadata.PropertyName;
    var value = Model.HasValue ? toPersianDate(Model.Value) : string.Empty;
}
<input type="text" dir="ltr" 
       name="@name" id="@name" 
       value="@value" 
       onclick="PersianDatePicker.Show(this,'@today');" />
بعد از این تنها کاری که باید انجام شود، استفاده از ویژگی UIHint است:
// Location: Views\Shared\EditorTemplates\PersianDatePicker.cshtml
[UIHint("PersianDatePicker")]
public DateTime AddDate { set; get; }
به صورت خودکار به متد EditorFor اعمال می‌شود:
<div>
    <label>تاریخ:</label>
    @Html.EditorFor(model => model.AddDate)
</div>

همچنین اگر نیاز است مقدار رشته تاریخ شمسی آن به یک DateTime میلادی انتساب داده شود (در حین ارسال اطلاعات به سرور)، باید از یک model binder سفارشی برای اینکار در ASP.NET MVC استفاده کنید. یک نمونه پیاده سازی شده آن‌را در بحث PersianDateModelBinder می‌توانید مشاهده و استفاده کنید. 
مطالب
تعیین اعتبار یک checkBoxList با کمک jQuery

checkBoxList جزو کنترل‌هایی در ASP.Net است که نمی‌توان RequiredFieldValidator استاندارد را بر آن اعمال کرد. به عبارتی اگر نیاز بود حداقل یک آیتم چک باکس لیست حتما توسط کاربر انتخاب شود، راه حل آماده‌ای برای آن وجود ندارد. پیاده سازی این‌کار با استفاده از jQuery به سادگی میسر است که در ادامه آن‌را مرور خواهیم کرد.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CheckBoxListValidator._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
//<![CDATA[
function CheckItems(sender, args) {
//Get the total nuumber of selected CheckBoxes
var num = jQuery("table#<%=CheckBoxList1.ClientID%> input:checked").length;
args.IsValid = num > 0;
}
//]]>
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:CheckBoxList ID="CheckBoxList1" runat="server">
<asp:ListItem>item1</asp:ListItem>
<asp:ListItem>item2</asp:ListItem>
</asp:CheckBoxList>
<asp:CustomValidator ClientValidationFunction="CheckItems" ID="ValidateIt"
runat="server" ErrorMessage="(*)"> </asp:CustomValidator>
<asp:Button ID="Button1" runat="server" />
</div>
</form>
</body>
</html>

توضیحات:
یک CustomValidator استاندارد را به فرم اضافه کرده‌ایم تا توسط تابعی که به ClientValidationFunction آن معرفی می‌شود، کار اعتبار سنجی سمت کاربر را انجام دهد. این تابع یا همان CheckItems مثال فوق، امضای استاندارد و آشنایی دارد. اگر تعیین اعتبار صورت گرفته باشد، باید args.IsValid در آن به true تنظیم شود یا بر عکس.
اصل قضیه هم، همین یک سطر کد زیر است:

var num = jQuery("table#<%=CheckBoxList1.ClientID%> input:checked").length;
کار این سطر که از جی‌کوئری استفاده می‌کند، پیدا کردن جدولی است که ID آن مساوی آی دی سمت کلاینت چک باکس لیست ما است (ASP.Net یک چک باکس لیست را به صورت یک جدول حاوی چک باکس‌ها رندر می‌کند). سپس در همان ناحیه مشغول به جستجوی چک باکس‌هایی می‌شود که تیک خورده‌اند. نهایتا تعداد آن‌ها را بر می‌گرداند.

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

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

کتابخانه jcrop کتابخانه‌ای است که این امکانات را برای شما فراهم می‌کند. این کتابخانه بدین صورت است که در حین برش به شما 4 عدد x1,x2,y1,y2 را داده و شما با ارسال آن به سمت سرور میتوانید بر اساس این اعداد، عکس اصلی را برش بزنید. بدین صورت شما هم عکس اصلی را دارید و هم مختصات برش را دارید و اگر دوست دارید در جاهای مختلف از عکس اصلی برش داشته باشید، بسیار مفید خواهد بود.

مرحله اول:
ابتدا فایل jcrop را دانلود نمایید.

مرحله دوم:
 کد Html زیر را به صفحه اضافه کنید:
<div>
    <!-- upload form -->
    <!-- hidden crop params -->
    <input type="hidden" id="x1" name="x1" />
    <input type="hidden" id="y1" name="y1" />
    <input type="hidden" id="x2" name="x2" />
    <input type="hidden" id="y2" name="y2" />
    <h2>ابتدا تصویر خود را انتخاب کنید</h2>
    <div><input type="file" name="postedFileBase" data-buttonText="انتخاب تصویر" id="image_file" onchange="fileSelectHandler()" /></div>
    <div></div>
    <div>
        <h2>قسمتی از تصویر را انتخاب نمایید</h2>
        <img id="preview" />
        <div>
            <label>حجم فایل </label> <input type="text" id="filesize" name="filesize" />
            <label>نوع فایل</label> <input type="text" id="filetype" name="filetype" />
            <label>ابعاد فایل</label> <input style="direction: ltr;" type="text" id="filedim" name="filedim" />

        </div>
    </div>

</div>
در اینجا 4 عدد input به صورت مخفی قرار گرفته‌اند که مختصات برش به ترتیب در آن‌ها ذخیره می‌شوند و هنگام post یا قرار دادن در formData جهت ارسال ایجکسی نیز می‌توانند مورد استفاده قرار بگیرند. جهت input file مورد نظر برای زیبایی و پشتیبانی از عبارت فارسی دلخواه به جای «Browse» از کتابخانه  Bootstrap FileStyle استفاده شده است و رویداد Onchange آن نیز به یک تابع جاوا اسکریپتی بایند شده تا بعد از تایید تصویر، باقی عملیات برش تصویر انجام شوند.

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

مرحله سوم:
سپس برای استایل دهی کدهای بالا از کد Css زیر استفاده میکنیم:
.bheader {
background-color: #DDDDDD;
border-radius: 10px 10px 0 0;
padding: 10px 0;
text-align: center;
}
.bbody {
color: #000;
overflow: hidden;
padding-bottom: 20px;
text-align: center;
background: -moz-linear-gradient(#ffffff, #f2f2f2);
background: -ms-linear-gradient(#ffffff, #f2f2f2);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
background: -webkit-linear-gradient(#ffffff, #f2f2f2);
background: -o-linear-gradient(#ffffff, #f2f2f2);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
background: linear-gradient(#ffffff, #f2f2f2);
}
.bbody h2, .info, .error {
margin: 10px 0;
}
.step2, .error {
display: none;
}
.error {
color: red;
}
.info {
}
label {
margin: 0 5px;
}
.roundinput {
border: 1px solid #CCCCCC;
border-radius: 10px;
padding: 4px 8px;
text-align: center;
width: 150px;
}
.jcrop-holder {
display: inline-block;
}
input[type=submit] {
background: #e3e3e3;
border: 1px solid #bbb;
border-radius: 3px;
-webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
box-shadow: inset 0 0 1px 1px #f6f6f6;
color: #333;
padding: 8px 0 9px;
text-align: center;
text-shadow: 0 1px 0 #fff;
width: 150px;
}
input[type=submit]:hover {
background: #d9d9d9;
-webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
box-shadow: inset 0 0 1px 1px #eaeaea;
color: #222;
cursor: pointer;
}
input[type=submit]:active {
background: #d0d0d0;
-webkit-box-shadow: inset 0 0 1px 1px #e3e3e3;
box-shadow: inset 0 0 1px 1px #e3e3e3;
color: #000;
}

مرحله چهارم:
افزودن کد جاوااسکریپتی زیر برای کار کردن با کتابخانه Jcrop می‌باشد:
function bytesToSize(bytes) {
    var sizes = ['بایت', 'کیلو بایت', 'مگابایت'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};

function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
};

var jcrop_api, boundx, boundy;
function fileSelectHandler() {

    var oFile = $('#image_file')[0].files[0];

    $('.error').hide();
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (!rFilter.test(oFile.type)) {
        $('.error').html('فقط تصویر معتبر انتخاب نمایید').show();
        return;
    }

   
    var oImage = document.getElementById('preview');

    var oReader = new FileReader();
    oReader.onload = function (e) {

        oImage.src = e.target.result;
        

        oImage.onload = function () { 
          

            $('.step2').fadeIn(500);

            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
     
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }
  
                $('#preview').Jcrop({

                    aspectRatio: 2,
                    bgFade: true,
                    bgOpacity: .3, 
                    onChange: updateInfo,
                    onSelect: updateInfo
                }, function () {
 
                    //var bounds = this.getBounds();
                    //var boundx = bounds[0];
                    //var boundy = bounds[1];

                    // Store the Jcrop API in the jcrop_api variable
                    jcrop_api = this;
                });
        };
    };
    oReader.readAsDataURL(oFile);
}

تابع fileSelectHandler

function fileSelectHandler() {
    var oFile = $('#image_file')[0].files[0];
    $('.error').hide();
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (!rFilter.test(oFile.type)) {
        $('.error').html('فقط تصویر معتبر انتخاب نمایید').show();
        return;
    }
این تابع که مستقیما به input file رویداد Onchange متصل است. فایل انتخابی آن را خوانده و نوع تصویر را بررسی میکند. اگر تصویر از نوع Jpeg یا Png نباشد، یک خطا را به کاربر نشان میدهد و با return شدن از ادامه کد جلوگیری میکنیم.
در ادامه همین تابع بالا، کدهای زیر را اضافه می‌کنیم:
   var oImage = document.getElementById('preview');
    var oReader = new FileReader();
    oReader.onload = function (e) {

        oImage.src = e.target.result;
        oImage.onload = function () {
       
            $('.step2').fadeIn(500);

            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
   
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }
            $('#preview').Jcrop({

                aspectRatio: 2,
                bgFade: true, 
                bgOpacity: .3, 
                onChange: updateInfo,
                onSelect: updateInfo,
                onRelease: clearInfo
            }, function () {

                //var bounds = this.getBounds();
                //var boundx = bounds[0];
                //var boundy = bounds[1];

                jcrop_api = this;
            });
        };
    };
    oReader.readAsDataURL(oFile);

FileReader یکی از توابع موجود در HTML است که مستندات آن در سایت موزیلا موجود است و قابلیت خواندن غیرهمزمان فایلها و اشیا Blob را دارد. در خط آخر به عنوان پارامتر ما فایلی را که در آپلودر خوانده ایم و در مرحله قبل نوع فایل آن را بررسی کردیم، پاس میکنیم و باعث می‌شود که رویداد Load شیء FileReader صدا زده شود.
در این رویداد ابتدا اطلاعات این فایل را از قبیل سایز و ابعاد و نوع فایل، خوانده و در همان تگ Div که با کلاس info تعیین شده بود، نمایش می‌دهیم. سپس متغیر jcrop_api  را که به صورت global در بالای تابع صدا زدیم، بررسی میکنم که آیا از قبل پر شده‌است یا خیر؟ اگر از قبل پرشده‌است باید شیء Jcrop را که به آن اعمال شده است، نابود و آن را نال کنیم تا برای تصویر جدید آماده شود. این کد زمانی کاربرد دارد که کاربر از تصویر قبلی انصراف داده‌است و تصویر جدیدی را انتخاب نموده است یا اینکه عملیات دارد به صورت ایجکسی پیاده می‌شود. اگر عملیات نابودی روی این پلاگین صورت نگیرد، برای مرتبه دوم کار نخواهد کرد.


سپس پلاگین جی‌کوئری Jcrop را بر روی آن اعمال می‌کنیم. در پرامتر اول یک سری تنظیمات اولیه را انجام می‌دهیم که در ادامه با آن آشنا می‌شویم و در پارامتر دوم یک callback را به آن پاس میکنیم تا بعد از آماده شدن پلاگین اجرا شود که در آن شیء جدید ایجاد شده یعنی this را در متغیری به اسم jcrop_api دخیره میکنیم تا در بررسی‌های آتی که در بند بالا توضیح داده شد، در دسترس داشته باشیم. همچنین در این تابع شما می‌توانید اندازه تصویر انتخابی را نیز داشته باشید.

این پلاگین شامل option‌های متفاوتی در پارامتر اول است که آن‌ها را بررسی می‌کنیم:
MinSize : شما میتوانید حداقل پهنا و ارتفاعی را برای برش زدن تصویر در نظر بگیرید.
minSize:[40,20]
پارامتر دوم aspectRatio جهت نگهداری تناسب بین پهنا و ارتفاع می‌باشد. تنظیم زیر برای تناسب 3 به دو می‌باشد.
aspectRatio:1.5
bgFade اگر با true مقدار دهی شود عملیات برش در حین تاریک شدن صفحه (محلی که جزء برش نیست) و یا بالعکس آن با یک Fade صورت خواهد گرفت.
bgOpacity از 0 تا یک مقدار میگیرد و میزان opacity محل‌های تاریک را تعیین می‌کند. همچنین شامل سه رویداد onSelect,onChange,onrelease هم می‌باشد که به ترتیب در موارد زیر رخ میدهند:
ناحیه مورد نظر انتخاب شد.
ناحیه مورد نظر در حالت انتخاب است و ماوس در حال درگ شدن است و با هر حرکتی ماوس اجرا می‌گردد.
ناحیه انتخابی از حالت انتخاب خارج شد.


دو رویداد اول یعنی onchange و onSelect را برای به روزسانی فیلدهای مخفی و مختصات استفاده میکنیم:

function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
};

این مختصات از طریق یک پارامتر به آن‌ها پاس می‌شود. به غیر از این چهار عدد مختصات می‌توانید با استفاده از متغیرهای w و h هم اندازه پهنا و ارتفاع محل برش خورده را نیز به دست آورید. هر چند که این اعداد، از تفریق خود مختصات هم به دست می‌آیند.
یک تابع جزئی دیگر هم در این فایل وجود دارد که حین نمایش اندازه تصویر، واحد نمایش مناسب آن را برای ما انتخاب میکند:
function bytesToSize(bytes) {
    var sizes = ['بایت', 'کیلو بایت', 'مگابایت'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};

بعد از اینکه کدهای سمت کلاینت را تمام کردیم لازم است با نحوه برش تصویر در سمت سرور هم آشنا شویم:
public static byte[] Resize(this byte[] byteImageIn, int x1,int y1,int x2,int y2)
        {

            ImageConverter ic = new ImageConverter();
            Image src = (Image)(ic.ConvertFrom(byteImageIn)); 

            Bitmap target = new Bitmap(x2 - x1, y2 - y1);
            using (Graphics graphics = Graphics.FromImage(target))
                graphics.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height),
                    new Rectangle(x1,y1,x2-x1,y2-y1), 
                    GraphicsUnit.Pixel);
            src = target;
            using (var ms = new MemoryStream())
            {
                src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                return ms.ToArray();
            }

 }

از آنجا که ما تصاویر را در دیتابیس به صورت آرایه‌ای از بایت‌ها ذخیره میکنیم، extension method ذکر شده در بالا تصویر را در حالت آرایه‌ای از بایت‌ها برش می‌دهد. بدیهی که بسته به نیاز شما کد بالا دست خوش تغییراتی خواهد شد. ابتدا تصویر باینری را به شی Image تبدیل میکنیم و یک شیء Bitmap جدید را به عنوان بوم خالی و به اندازه کادر برش ایجاد می‌کنیم تا تصویر برش خورده در آن قرار بگیرد و سپس توسط متد DrawImage میخواهیم که تصویر مبدا را با مختصات شیء Rectangle از نقطه 0 و 0 بوم آغاز کرده و تا انتهای آن شروع به ترسیم کند. سپس آن را ذخیره و مجددا در قالب همان آرایه‌ای از بایت‌ها بر می‌گردانیم.

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