مطالب
آموزش TypeScript #3
در این پست به تشریح انواع داده در زبان TypeScript و ذکر مثال در این زمینه می‌پردازیم.
 
تعریف متغیر‌ها و انواع داده
در TypeScript هنگام تعریف متغیر‌ها باید نوع داده ای آن‌ها را مشخص کنیم. در TypeScript پنج نوع داده ای وجود دارد که در زیر با ذکر مثال تعریف شده اند. مفاهیم ماژول، کلاس و تابع در پست بعدی به تشریح توضیح داده خواهند شد.

 number : معادل نوع داده ای number در JavaScript است. برای ذخیره سازی اعداد صحیح و اعشاری استفاده می‌شود.
یک مثال:
class NumberTypeOfTypeScript { 
    MyFunction()
    {
        var p: number; 
        p = 1;
        var q = 2;
        var r = 3.33;
        alert("Value of P=" + p + "  Value of q=" + q + " Value of r=" + r);           
    }
}

window.onload = () =>{ 
    var value = new NumberTypeOfTypeScript();
    value.MyFunction();
}
حال باید یک فایل Html برای استفاده از این کلاس داشته باشیم. به صورت زیر:
<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="app.js"></script>
</head>
<body>
    <h1>Number Type in TypeScript</h1> 
    <div id="content"/>
</body>
</html>
بعد از اجرای پروزه خروجی به صورت زیر خواهد بود:

string : معادل نوع داده ای رشته ای است و برای ذخیره سازی مجموعه ای از کاراکتر‌ها از نوع UTF-16 استفاده می‌شود.

یک مثال:
class StringTypeOfTypeScript { 
    Myfunction() { 
      var  s: string;
      s="TypeScript"
      var empty = "";
      var abc = "abc";
      alert("Value of s="+ s+" Empty string="+ empty+" Value of abc ="+abc) ;     
    }
}
window.onload = () =>{ 
    var value = new StringTypeOfTypeScript();
    value.Myfunction();
}
کد کامپایل شده و تبدیل آن به JavaScript: 
var StringTypeOfTypeScript = (function () {
    function StringTypeOfTypeScript() { }
    StringTypeOfTypeScript.prototype.Myfunction = function () {
        var s;
        s = "TypeScript";
        var empty = "";
        var abc = "abc";
        alert("Value of s=" + s + " Empty string=" + empty + " Value of abc =" + abc);
    };
    return StringTypeOfTypeScript;
})();
window.onload = function () {
    var value = new StringTypeOfTypeScript();
    value.Myfunction();
};
خروجی به صورت زیر است:


boolean: برای ذخیره سازی مقادیر true یا false می‌باشد.
مثال:
class booleanTypeofTypeScript { 
    MyFunction() {
        var lie: bool;        
        lie = false;
        var a = 12;
        if (typeof (lie) == "boolean" && typeof (a) == "boolean") {
            alert("Both is boolean type");
        }

        if (typeof (lie) == "boolean" && typeof (a) != "boolean") {
            alert("lie is boolean type and a is not!") 
        }
        else { 
            alert("a is boolean type and lie is not!");
        }
    }     
}

window.onload =()=> { 
    var access = new booleanTypeofTypeScript();
    access.MyFunction();
 }
کد کامپایل شده و تبدیل آن به JavaScript:
var booleanTypeofTypeScript = (function () {
    function booleanTypeofTypeScript() { }
    booleanTypeofTypeScript.prototype.MyFunction = function () {
        var lie;
        lie = false;
        var a = 12;
        if(typeof (lie) == "boolean" && typeof (a) == "boolean") {
            alert("Both is boolean type");
        }
        if(typeof (lie) == "boolean" && typeof (a) != "boolean") {
            alert("lie is boolean type and a is not!");
        } else {
            alert("a is boolean type and lie is not!");
        }
    };
    return booleanTypeofTypeScript;
})();
window.onload = function () {
    var access = new booleanTypeofTypeScript();
    access.MyFunction();
};
null: همانند دات نت هنگامی که قصد داشته باشیم مقدار یک متغیر را null اختصاص دهیم از این کلمه کلیدی استفاده می‌کنیم.
مثال:
class NullTypeinTypeScript { 
    MyFunction() { 
        var p: number = null;
        var x = null;
        if (p== null) {
            alert("p has null value!");
        }
        else { alert("p has a value"); }
    }
}
window.onload = () =>{ 
    var value = new NullTypeinTypeScript();
    value.MyFunction();
}
کد کامپایل شده و تبدیل آن به JavaScript:
var NullTypeinTypeScript = (function () {
    function NullTypeinTypeScript() { }
    NullTypeinTypeScript.prototype.MyFunction = function () {
        var p = null;
        var x = null;
        if(p == null) {
            alert("p has null value!");
        } else {
            alert("p has a value");
        }
    };
    return NullTypeinTypeScript;
})();
window.onload = function () {
    var value = new NullTypeinTypeScript();
    value.MyFunction();
};

undefined:معادل نوع undefined در Javascript است. اگر به یک متغیر مقدار اختصاص ندهید مقدار آن undefined خواهد بود.
مثال:
class UndefinedTypeOfTypeScript { 
    Myfunction() { 
        var p: number;
        var x = undefined;
        if (p == undefined && x == undefined) {
            alert("p and x is undefined");
        }
        else { alert("p and c cannot undefined"); }
    }
}
window.onload = () =>{ 
    var value = new UndefinedTypeOfTypeScript();
    value.Myfunction();
}
کد کامپایل شده و تبدیل آن به JavaScript: 
var UndefinedTypeOfTypeScript = (function () {
    function UndefinedTypeOfTypeScript() { }
    UndefinedTypeOfTypeScript.prototype.Myfunction = function () {
        var p;
        var x = undefined;
        if(p == undefined && x == undefined) {
            alert("p and x is undefined");
        } else {
            alert("p and c cannot undefined");
        }
    };
    return UndefinedTypeOfTypeScript;
})();
window.onload = function () {
    var value = new UndefinedTypeOfTypeScript();
    value.Myfunction();
};
خروجی این مثال نیز به صورت زیر است:

ادامه دارد...
مطالب
بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net

شاید بعضی از سایت‌ها را دیده باشید که در حین ثبت نام، پس از وارد کردن یک نام کاربری و سپس مشغول شدن به پر کردن فیلد کلمه‌ی عبور، در قسمت نام کاربری شروع به جستجو در مورد آزاد بودن نام کاربری درخواستی می‌کنند یا نمونه‌ای دیگر، فرم پرداخت الکترونیکی بانک سامان. پس از اینکه شماره قبض را وارد کردید، بلافاصله بدون ریفرش صفحه به شما پیغام می‌دهد که این شماره معتبر است یا خیر. امروز قصد داریم این قابلیت را با استفاده از کتابخانه‌ی Ajax مجموعه jQuery در ASP.Net پیاده سازی کنیم (بدون استفاده از ASP.Net Ajax مایکروسافت).
ابتدا سورس کامل را ملاحظه نمائید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AjaxTest.aspx.cs" Inherits="testWebForms87.AjaxTest" %>
<!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>jQuery Ajax Text</title>

<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#<%= TextBox1.ClientID %>").blur(function(event) {
$.ajax({
type: "POST",
url: "AjaxTest.aspx/IsUserAvailable",
data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
$('#valid').html("<img src='ajaxImages/waiting.gif' alt='لطفا کمی تامل کنید'>");
var delay = function() {
AjaxSucceeded(msg);
};

setTimeout(delay, 2000); //remove this
},
error: AjaxFailed
});
});
});
function AjaxSucceeded(result) {
if (result.d == true)
$('#msg').html("<img src='ajaxImages/available.gif' alt='نام کاربری درخواستی موجود است'>");
else
$('#msg').html("<img src='ajaxImages/taken.gif' alt='متاسفانه نام کاربری مورد نظر پیشتر دریافت شده‌است'>");
}
function AjaxFailed(result) {
alert(result.status + ' ' + result.statusText);
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
user name:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<span id="msg"></span>
<br />
pass:
<asp:TextBox ID="TextBox2" TextMode="Password" runat="server"></asp:TextBox>
</div>
<!-- preload -->
<div style="display: none">
<img src="ajaxImages/available.gif" alt="available" />
<img src="ajaxImages/taken.gif" alt="taken" />
<img src="ajaxImages/waiting.gif" alt="waiting" />
</div>
</form>
</body>
</html>


using System;
using System.Web.Services;

namespace testWebForms87
{
public partial class AjaxTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

[WebMethod]
public static bool IsUserAvailable(string username)
{
// این مورد را با خواندن اطلاعات از دیتابیس می‌شود تعویض کرد
return username != "test";
}
}
}

توضیحات:
همانطور که ملاحظه می‌کنید صفحه‌ی ASP.Net ما بسیار ساده است و از دو تکست باکس استاندارد تشکیل می‌شود، به همراه تصاویر مربوط به Ajax که یک سری تصاویر ساده چرخان معروف منتظر بمانید ، یافت شد یا موجود نیست می‌باشند. این تصاویر در یک div مخفی (display: none) در صفحه قرار گرفته‌اند و در هنگام بارگذاری صفحه، این‌ها نیز بارگذاری شده و حاضر و آماده خواهند بود. بنابراین هنگام استفاده از آن‌ها، کاربر تاخیری را مشاهده نخواهد کرد. همچنین یک span با id مساوی msg‌ را هم پس از تکست باکس اضافه کرده‌ایم تا تصاویر مربوط به رخدادهای Ajax را با استفاده از توانایی‌های jQuery به آن اضافه کنیم.

اسکریپت Ajax ما با دراختیار گرفتن روال رخداد گردان blur شیء textBox1 شروع می‌شود. همانطور که در مقالات پیشین سایت نیز ذکر شد، روش صحیح دریافت ID یک کنترل ASP.Net در کدهای سمت کلاینت جاوا اسکریپتی، بر اساس خاصیت ClientID آن است که در اولین سطر کدهای ما مشخص است (زیرا در ASP.Net نام و ID یک کنترل در هنگام رندر شدن به همراه ID کنترل‌های دربرگیرنده آن نیز خواهد بود، بنابراین بهتر است این مورد را داینامیک کرد).

کار بررسی موجود بودن نام کاربری (یا مثلا یک شماره قبض و امثال آن) توسط WebMethod ایی به نام IsUserAvailable در code behind صفحه انجام می‌شود که پیاده سازی آن‌را ملاحظه می‌کنید. بدیهی است در این مثال ساده، تنها نام کاربری از پیش رزرو شده، کلمه‌ی test است و در یک کد واقعی این مورد با مقایسه‌ی نام کاربری با اطلاعات موجود در دیتابیس باید صورت گیرد (و حملات تزریق اس کیوال را هم فراموش نکنید. برای رهایی از آن‌ها "حتما" باید از پارامترهای ADO.Net استفاده کرد و گرنه کد شما مستعد به این نوع حملات خواهد بود).

سؤال: چرا از web method استفاده شد و همچنین چرا این متد static است؟
زمانیکه یک متد با کلمه کلیدی static‌ مشخص می‌شود حالت state less پیدا می‌کند یعنی مستقل از وهله‌ی کلاس عمل می‌کند. در این حالت نیازی به ارسال ViewState نبوده (بنابراین در کوئری مورد نظر ما بسیار بهینه و سبک عمل می‌کنند) و همچنین نیازی به ایجاد یک وهله‌ای از کلاس صفحه‌ی ما نیز نخواهد بود. برای توضیحات بیشتر به این مقاله مراجعه نمائید. (به صورت خلاصه، دلیل اصلی، کارآیی بالا و بهینه بودن این روش در این مساله ویژه است و در ASP.Net Ajax مایکروسافت به صورت گسترده‌ای در پشت صحنه مورد استفاده قرار می‌گیرد)
استفاده از ویژگی WebMethod عملکرد صفحه‌ی ما را شبیه به یک وب سرویس خواهد کرد و امکان دسترسی به آن در متدهای استاندارد POST به صورت ارسال دیتا به آدرس WebService.asmx/WebMethodName‌ خواهد بود. یک مثال ساده و عملی

بررسی تابع Ajax بکار رفته:
این تابع هنگام فراخوانی رخداد blur تکست‌باکس ما (مطابق کد فوق) فراخوانی می‌شود. ساختار ساده‌ای دارد که به شرح زیر است:
type: "POST"
نحوه‌ی ارسال داده را به متد وب سرویس ما مشخص می‌کند.

url: "AjaxTest.aspx/IsUserAvailable"
اطلاعات پست شده، به صفحه‌ی AjaxTest.aspx و وب متد IsUserAvailable ارسال خواهد شد

data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
داده‌ای که به آرگومان username ما ارسال می‌شود، همان مقدار تایپ شده در TextBox1 است (که باز هم دریافت ID آن به صورت داینامیک صورت گرفته تا مشکل زا نشود)

contentType: "application/json; charset=utf-8",
dataType: "json",
در این دو سطر از نوع داده‌ی json استفاده شده است که فرمت بسیار سبک و بهینه‌ای برای تبادل اطلاعات در وب به‌شمار می‌آید و توسط کتابخانه‌های جاوا اسکریپتی به سادگی پردازش شده و تبدیل به اشیاء مورد نظر خواهند شد. برای مثال اگر خروجی یک وب سرویس در حالت xml به صورت زیر باشد:

<xx yy="nn"></xx>
معادل json آن به شرح زیر است:
{ "xx": {"yy":"nn"} }
success: function(msg)
Success ، پس از موفقیت آمیز بودن عملیات ajax در jQuery فراخوانی می‌شود
error: AjaxFailed
و اگر در این بین خطایی رخ داده باشد، قسمت error فراخوانی می‌شود.

در این مثال برای نمایش بهتر عملیات، یک وقفه‌ی 2 ثانیه‌ای توسط setTimeout ایجاد شده و بدیهی است در یک مثال واقعی باید آن‌را حذف نمود.

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





مطالب
توسعه سرویس‌های مبتنی بر REST در AngularJs با استفاده از RestAngular : بخش دوم
در بخش پیشین  کلیات کتابخانه‌ی Restangular  را بررسی کردیم. در این بخش قصد داریم تا در طی یک پروژه، امکانات و قابلیت‌های بی‌نظیر این سرویس را در یک پروژه‌ی واقعی مشاهده کنیم.

کلیات پروژه

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

پایگاه داده‌ی برنامه با نام LibDb درون پوشه‌ی app_data قرار داده شده‌است. این پایگاه داده تنها دارای یک جدول است؛ با نام Books که دیاگرام آن را در شکل زیر مشاهده می‌کنید:


پیاده سازی

در ابتدا یک پروژه‌ی Empty را با رفرنس web API ایجاد می‌کنیم. حال درون فایل WebApiConfig.cs تکه کد زیر را اضافه می‌کنیم. این تکه کد فیلدها و آبجکت‌هایی را که از سمت سرور بازگشت داده می‌شوند، به صورت camelCase تبدیل می‌کند.
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();

jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
در ادامه مدل edmx را ساخته و پس از آن Books Web Api را درون پوشه‌ی کنترلر ایجاد می‌کنیم. این کنترلر به صورت Default web Api Scaffold ساخته شده است و به دلیل اینکه به بحث ما مرتبط نیست؛ از توضیح بیشتر آن می‌گذریم.
حال پوشه‌ای را با نام app برای نگه داری فایل‌های AngularJs اضافه می‌کنیم و درون آن فایل app.js را ایجاد می‌کنیم:
var angularExample = angular.module('angularexample', ["restangular"])
angularExample.config(["RestangularProvider", function (RestangularProvider) {
    //RestangularProvider.setRestangularFields({
    //    id: "id"
    //});
    RestangularProvider.setBaseUrl('/api');
}]);

angularExample.controller("MainCtrl", ["Restangular", "$scope", function (Restangular, $scope) {
    var resource = Restangular.all('books');
    resource.getList().then(function (response) {
        $scope.books = response;
    });
    $scope.add = function () {
        resource.post($scope.newBook).then(function (newResource) {
            $scope.books.push(newResource);
        })
    }
}]);
محتویات فایل app.js را مشاهده می‌کنید. در ادامه درباره‌ی این قسمت بیشتر صحبت می‌کنیم.
حال در روت پروژه فایل index.html را ایجاد می‌کنیم:
<!DOCTYPE html>
<html ng-app="angularexample" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Rest Angular Sample</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
    <script src="http://cdn.jsdelivr.net/underscorejs/1.5.1/underscore-min.js"></script>
    <script src="/Scripts/restangular.min.js"></script>
    <script src="/app/app.js"></script>
</head>
<body>
    <div ng-controller="MainCtrl">
        <div ng-repeat="item in books">
            name is: {{item.name}}<br />
            change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button>
        </div>
        <div>
            add new: <br />
            <input type="text" ng-model="newBook.name" /><button type="submit" ng-click="add()">add</button>
        </div>
    </div>
</body>
و در نهایت در پوشه‌ی Scripts فایل‌های سرویس Restangular را قرار می‌دهیم.
تا به اینجای کار تمامی کارهای مورد نیاز تشکیل پروژه را انجام داده‌ایم. حال به بررسی بیشتر سرویس‌های این پروژه می‌پردازیم؛ یعنی کدهای درون فایل app.js.
ببینیم که برای دریافت لیست تمامی کتابها، ما چه کارهایی را انجام داده‌ایم! به تکه کد زیر دقت کنید:
    var resource = Restangular.all('books');
    resource.getList().then(function (response) {
        $scope.books = response;
    });
این تمامی آن چیزی است که ما برای دریافت لیست تمامی کتابها انجام داده‌ایم. به همین سادگی! در خط اول از متد all که در بخش قبل توضیح مختصری راجع به آن داده بودم برای دریافت لیست تمامی کتابها استفاده کردم که پارامتر درون آن آدرس Web Api است. البته همانطور که می‌دانید در ASP.NET Web Api ما همیشه به base address یک api نیز اضافه می‌کنیم. حال اینکه چرا api در اینجا نیامده در ادامه و در بخش تنظیمات کلی Restangular توضیح می‌دهم. در خط دوم نیز از اشاره‌گر resources متد getList را فراخوانی کرده و لیست کتابها را در response دریافت می‌کنیم.
پس از آن می‌خواهیم ببینیم که عملیات ایجاد یک کتاب جدید چگونه انجام می‌گردد. تکه کد زیر این عملیات را انجام می‌دهد:
    $scope.add = function () {
        resource.post($scope.newBook).then(function (newResource) {
            $scope.books.push(newResource);
        })
    }
ما یک متد با نام add ایجاد کرده‌ایم که در سمت View توسط دایرکتیو ng-click آن را فراخوانی می‌کنیم.
همانطور که مشاهده میکنید درون app.js متدی برای update موارد قبلی نیست. بیایید سری به View بزنیم. به المنت div زیر توجه کنید. همانطور که می‌بینید تمامی عملیات update با یک دستور ساده item.put حل و فصل شده.
        <div ng-repeat="item in books">
            name is: {{item.name}}<br />
            change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button>
        </div>
تمامی آنچه که گفته شد تنها بخشی از قابلیت‌های شگفت انگیز این افزونه بود. امیدوارم که مطلب مفید واقع شده باشد. سورس این پروژه را می‌توانید از لینک زیر دریافت کنید.

سورس پروژه: RestangularSample.rar  

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

مزیت استفاده از یوزر کنترل‌ها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا ، سخن روز و آمار کاربران تشکیل شود، می‌توان هر کدام را توسط یک یوزر کنترل پیاده سازی کرده و سپس صفحه اصلی را از کنار هم قرار دادن این یوزر کنترل‌ها تهیه نمود.
با این توضیحات اکنون می‌خواهیم یک یوزکنترل ASP.Net را توسط jQuery Ajax بارگذاری کرده و نمایش دهیم. حداقل دو مورد کاربرد را می‌توان برای آن متصور شد:
الف) در اولین باری که یک صفحه در حال بارگذاری است، قسمت‌های مختلف آن‌را بتوان از یوزر کنترل‌های مختلف خواند و تا زمان بارگذاری کامل هر کدام، یک عبارت لطفا منتظر بمانید را نمایش داد. نمونه‌ی آن‌را شاید در بعضی از CMS های جدید دیده باشید. صفحه به سرعت بارگذاری می‌شود. در حالیکه مشغول مرور صفحه جاری هستید، قسمت‌های مختلف صفحه پدیدار می‌شوند.
ب) بارگذاری یک قسمت دلخواه صفحه بر اساس درخواست کاربر. مثلا کلیک بر روی یک دکمه و امثال آن.

روش کلی کار:
1) تهیه یک متد وب سرویس که یوزر کنترل را بر روی سرور اجرا کرده و حاصل را تبدیل به یک رشته کند.
2) استفاده از متد Ajax جی‌کوئری برای فراخوانی این متد وب سرویس و افزودن رشته دریافت شده به صفحه.
بدیهی است زمانیکه متد Ajax فراخوانی می‌شود می‌توان عبارت یا تصویر منتظر بمانید را نمایش داد و پس از پایان کار این متد، عبارت (یا تصویر) را مخفی نمود.

پیاده سازی:
قسمت تبدیل یک یوزر کنترل به رشته را قبلا در مقاله "تهیه قالب برای ایمیل‌های ارسالی یک برنامه ASP.Net" مشاهده کرده‌اید. در این‌جا برای استفاده از این متد در یک وب سرویس نیاز به کمی تغییر وجود داشت (KeyValuePair ها درست سریالایز نمی‌شوند) که نتیجه نهایی به صورت زیر است. یک فایل Ajax.asmx را به برنامه اضافه کرده و سپس در صفحه Ajax.asmx.cs کد آن به صورت زیر می‌تواند باشد:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.UI;
using System.Web.UI.HtmlControls;

namespace AjaxTest
{
public class KeyVal
{
public string Key { set; get; }
public object Value { set; get; }
}

/// <summary>
/// Summary description for Ajax
/// </summary>
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Ajax : WebService
{
/// <summary>
/// Removes Form tags using Regular Expression
/// </summary>
private static string cleanHtml(string html)
{
return Regex.Replace(html, @"<[/]?(form)[^>]*?>", string.Empty, RegexOptions.IgnoreCase);
}

/// <summary>
/// تبدیل یک یوزر کنترل به معادل اچ تی ام ال آن
/// </summary>
/// <param name="path">مسیر یوزر کنترل</param>
/// <param name="properties">لیست خواص به همراه مقادیر مورد نظر</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string RenderUserControl(string path,
List<KeyVal> properties)
{
Page pageHolder = new Page();

UserControl viewControl =
(UserControl)pageHolder.LoadControl(path);

viewControl.EnableViewState = false;

Type viewControlType = viewControl.GetType();

if (properties != null)
foreach (var pair in properties)
{
if (pair.Key != null)
{
PropertyInfo property =
viewControlType.GetProperty(pair.Key);

if (property != null)
{
if (pair.Value != null) property.SetValue(viewControl, pair.Value, null);
}
else
{
throw new NotImplementedException(string.Format(
"UserControl: {0} does not have a public {1} property.",
path, pair.Key));
}
}
}

//Form control is mandatory on page control to process User Controls
HtmlForm form = new HtmlForm();

//Add user control to the form
form.Controls.Add(viewControl);

//Add form to the page
pageHolder.Controls.Add(form);

//Write the control Html to text writer
StringWriter textWriter = new StringWriter();

//execute page on server
HttpContext.Current.Server.Execute(pageHolder, textWriter, false);

// Clean up code and return html
return cleanHtml(textWriter.ToString());
}
}
}
تا این‌جا متد وب سرویسی را داریم که می‌تواند مسیر یک یوزر کنترل را به همراه خواص عمومی آن‌را دریافت کرده و سپس یوزر کنترل را رندر نموده و حاصل را به صورت HTML به شما تحویل دهد. با استفاده از reflection خواص عمومی یوزر کنترل یافت شده و مقادیر لازم به آن‌ها پاس می‌شوند.

چند نکته:
الف) وب کانفیگ برنامه ASP.Net شما اگر با VS 2008 ایجاد شده باشد مداخل لازم را برای استفاده از این وب سرویس توسط jQuery Ajax دارد در غیر اینصورت موفق به استفاده از آن نخواهید شد.
ب) هنگام بازگرداندن این اطلاعات با فرمت json = ResponseFormat.Json جهت استفاده در jQuery Ajax ، گاهی از اوقات بسته به حجم بازگردانده شده ممکن است خطایی حاصل شده و عملیات متوقف شد. این طول پیش فرض را (maxJsonLength) در وب کانفیگ به صورت زیر تنظیم کنید تا مشکل حل شود:

<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="10000000"></jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>

برای پیاده سازی قسمت Ajax آن برای اینکه کار کمی تمیزتر و با قابلیت استفاده مجدد شود یک پلاگین تهیه شده (فایلی با نام jquery.advloaduc.js) که سورس آن به صورت زیر است:

$.fn.advloaduc = function(options) {
var defaults = {
webServiceName: 'Ajax.asmx', //نام فایل وب سرویس ما
renderUCMethod: 'RenderUserControl', //متد وب سرویس
ucMethodJsonParams: '{path:\'\'}',//پارامترهایی که قرار است پاس شوند
completeHandler: null //پس از پایان کار وب سرویس این متد جاوا اسکریپتی فراخوانی می‌شود
};
var options = $.extend(defaults, options);

return this.each(function() {
var obj = $(this);
obj.prepend("<div align='center'> لطفا اندکی تامل بفرمائید... <img src=\"images/loading.gif\"/></div>");

$.ajax({
type: "POST",
url: options.webServiceName + "/" + options.renderUCMethod,
data: options.ucMethodJsonParams,
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
obj.html(msg.d);

// if specified make callback and pass element
if (options.completeHandler)
options.completeHandler(this);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
obj.html("امکان اتصال به سرور در این لحظه مقدور نیست. لطفا مجددا سعی کنید.");
}
});
});
};
برای اینکه با کلیات این روش آشنا شوید می‌توان به مقاله "بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net" مراجعه نمود که از ذکر مجدد آن‌ها خودداری می‌شود. همچنین در مورد نوشتن یک پلاگین جی‌کوئری در مقاله "افزونه جملات قصار jQuery" توضیحاتی داده شده است.
عمده کاری که در این پلاگین صورت می‌گیرد فراخوانی متد Ajax جی‌کوئری است. سپس به متد وب سرویس ما (که در اینجا نام آن به صورت پارامتر نیز قابل دریافت است)، پارامترهای لازم پاس شده و سپس نتیجه حاصل به یک شیء در صفحه اضافه می‌شود.
completeHandler آن اختیاری است و پس از پایان کار متد اجکس فراخوانی می‌شود. در صورتیکه به آن نیازی نداشتید یا مقدار آن را null قرار دهید یا اصلا آن‌را ذکر نکنید.

مثالی در مورد استفاده از این وب سرویس و همچنین پلاگین جی‌کوئری نوشته شده:

الف) یوزر کنترل ساده زیر را به پروژه اضافه کنید:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="part1.ascx.cs" Inherits="TestJQueryAjax.part1" %>
<asp:Label runat="server" ID="lblData" ></asp:Label>
بدیهی است یک یوزر کنترل می‌تواند به اندازه یک صفحه کامل پیچیده باشد به همراه انواع و اقسام ارتباطات با دیتابیس و غیره.

سپس کد آن‌را به صورت زیر تغییر دهید:

using System;
using System.Threading;

namespace TestJQueryAjax
{
public partial class part1 : System.Web.UI.UserControl
{
public string Text1 { set; get; }
public string Text2 { set; get; }

protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(3000);
if (!string.IsNullOrEmpty(Text1) && !string.IsNullOrEmpty(Text2))
lblData.Text = Text1 + "<br/>" + Text2;
}
}
}
این یوزر کنترل دو خاصیت عمومی دارد که توسط وب سرویس مقدار دهی خواهد شد و نهایتا حاصل نهایی را در یک لیبل در دو سطر نمایش می‌دهد.
عمدا یک sleep سه ثانیه‌ای در اینجا در نظر گرفته شده تا اثر آن‌را بهتر بتوان مشاهده کرد.

ب) اکنون کد مربوط به صفحه‌ای که قرار است این یوزر کنترل را به صورت غیرهمزمان بارگذاری کند به صورت زیر خواهد بود (مهم‌ترین قسمت آن نحوه تشکیل پارامترها و مقدار دهی خواص یوزر کنترل است):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestJQueryAjax._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="js/jquery.js" type="text/javascript"></script>
<script src="js/jquery.advloaduc.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>

<script type="text/javascript">
function showAlert() {
alert('finished!');
}

//تشکیل پارامترهای متد وب سرویس جهت ارسال به آن
var fileName = 'part1.ascx';
var props = [{ 'Key': 'Text1', 'Value': 'سطر یک' }, { 'Key': 'Text2', 'Value': 'سطر 2'}];
var jsonText = JSON.stringify({ path: fileName, properties: props });

$(document).ready(function() {
$("#loadMyUc").advloaduc({
webServiceName: 'Ajax.asmx',
renderUCMethod: 'RenderUserControl',
ucMethodJsonParams: jsonText,
completeHandler: showAlert
});
});

</script>

</head>
<body>
<form id="form1" runat="server">
<div id="loadMyUc">
</div>
</form>
</body>

</html>
نکته:
برای ارسال صحیح و امن اطلاعات json به سرور، از اسکریپت استاندارد json2.js استفاده شد.

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


دریافت مثال فوق


مطالب
ارتباط بین Controller و Directive در AngularJs
در پست قبلی با کلیات مفاهیم دیرکتیو‌ها آشنا شدید. در این پست قصد داریم برخی توابع  کنترلرهای تعریف شده در Angular را به وسیله دیرکتیو‌های تعریف شده در ماژول فراخوانی نماییم. در ادامه این موضوع را طی یک مثال بررسی خواهیم کرد.
ابتدا View مورد نظر را به صور زیر ایجاد می‌کنیم:
<script type="text/javascript" src="~/scripts/Modules/module4.js"></script>

<div ng-app="myApp">
    <div ng-controller="myCtrl">
        <span enter>Load More Books</span>
    </div>
</div>
برنامه به این صورت است که با ورود نشانگر ماوس بر روی تگ span (فراخوانی رویداد mouseenter برای تگ هایی که دارای دیرکتیو enter باشند) یک تابع به نام loadMoreBook در کنترلر myCtrl فراخوانی می‌شود.
بک فایل جاوااسکریپتی به نام myModule بسازید و ماژول مورد نظر را ایجاد نمایید:
var app = angular.module('myApp', []);
کنترلر مورد نظر به همراه تابع loadMoreBook را به صورت زیر ایجاد می‌کنیم(البته در اینجا به جای لود واقعی داده از یک alert استفاده کردم):
app.controller('myCtrl', function ($scope) {
    $scope.loadMoreBook = function () {
        alert('Loading Books...');
    }
});
حال نوبت به دیرکتیو مورد بحث می‌رسد که به صورت زیر ایجاد می‌شود:
app.directive('enter', function () {
    return function (scope, element) {
        element.bind('mouseenter', function () {
            scope.loadMoreBook();
        })        
    }
});
اولین نکته این است که به در تابع سازنده دیرکتیو به جای برگشت آبجک مورد نظر یک تابع برگشت داه می‌شود. برای اینکه بتوان به توابع کنترلر محصور کننده دیرکتیو دسترسی داشت آرگومان اول تابع معادل scope مورد استفاده در کنترلر خواهد بود. آرگومان دوم معادل المانی است که  دارای دیرکتیو enter است. در این تابع ابتدا برای رویداد mouseenter رویدادگردان آن پیاده سازی شده است که در آن تابع loadMoreBook کنترلر مورد نظر فراخوانی می‌شود.
خروجی


حال فرض بر این است که در کنترلر بالا تابع دیگری به نام loadMoreAuthor برای فراخوانی نویسندگان نیز وجود دارد. به صورت زیر:
app.controller('myCtrl', function ($scope) {
    $scope.loadMoreBook = function () {
        alert('Loading Books...');
    }

    $scope.loadMoreAuthor = function () {
        alert('Loading Authors...');
    }
});
اما برای انعطاف پذیری بیشتر برنامه، قصد داریم دیرکتیو بالا را به گونه ای تغییر دهیم که نام تابع مورد نظر در کنترلر را به عنوان مقدار یک ویژگی دریافت کند. به صورت زیر:

<script type="text/javascript" src="~/scripts/Modules/module4.js"></script>

<div ng-app="myApp">
    <div ng-controller="myCtrl">
        <span enter="loadMoreBook()">Load More Book</span>         
        <hr>
       <span enter="loadMoreAuthor()">Load More Author</span>
    </div>
</div>
برای به دست آوردن مقدار دیرکتیوی که به عنوان ویژگی در المان تعیین شده، باید از آرگومان سوم در تابع سازنده دیرکتیو به صورت زیر استفاده کرد.
app.directive('enter', function () {
    return function (scope, element , attrs) {
        element.bind('mouseenter', function () {
            scope.$apply(attrs.enter);
        })        
    }   
});
در  کد‌های بالا، برای اینکه بتوان بر اساس نام یک تابع آن را فراخوانی کرد، از سرویس apply$ که به صورت توکار در angular تعبیه شده است استفاده کردم. برای به دست آوردن نام تابع، باید از آرگومان سوم تابع (attrs) به همراه نام دیرکتیو استفاده کرد. به دلیل اینکه نام دیرکتیو enter است باید پارامتر سرویس apply$ به صورت attrs.enter باشد. خروجی نیز مانند حالت قبل خواهد بود.
مطالب
اعتبارسنجی سایتهای چند زبانه در ASP.NET MVC - قسمت اول

اگر در حال تهیه یک سایت چند زبانه هستید و همچنین سری مقالات Globalization در ASP.NET MVC رو دنبال کرده باشید میدانید که با تغییر Culture فایلهای Resource مورد نظر بارگذاری و نوشته‌های سایت تغییر میابند ولی با تغییر Culture رفتار اعتبارسنجی در سمت سرور نیز تغییر و اعتبارسنجی بر اساس Culture فعلی سایت انجام میگیرد. بررسی این موضوع را با یک مثال شروع میکنیم.

یک پروژه وب بسازید سپس به پوشه Models یک کلاس با نام ValueModel اضافه کنید. تعریف کلاس به شکل زیر هست: 

public class ValueModel
{
    [Required]
    [Display(Name = "Decimal Value")]
    public decimal DecimalValue { get; set; }

    [Required]
    [Display(Name = "Double Value")]
    public double DoubleValue { get; set; }

    [Required]
    [Display(Name = "Integer Value")]
    public int IntegerValue { get; set; }

    [Required]
    [Display(Name = "Date Value")]
    public DateTime DateValue { get; set; }
}

به سراغ کلاس HomeController بروید و کدهای زیر را اضافه کنید: 

[HttpPost]
public ActionResult Index(ValueModel valueModel)
{
    if (ModelState.IsValid)
    {
        return Redirect("Index");
    }

    return View(valueModel);
}

Culture را به fa-IR تغییر میدهیم، برای اینکار در فایل web.config در بخش system.web کد زیر اضافه نمایید: 

<globalization culture="fa-IR" uiCulture="fa-IR" />

و در نهایت به سراغ فایل Index.cshtml بروید کدهای زیر رو اضافه کنید:

@using (Html.BeginForm())
{
    <ol>
        <li>
            @Html.LabelFor(m => m.DecimalValue)
            @Html.TextBoxFor(m => m.DecimalValue)
            @Html.ValidationMessageFor(m => m.DecimalValue)
        </li>
        <li>
            @Html.LabelFor(m => m.DoubleValue)
            @Html.TextBoxFor(m => m.DoubleValue)
            @Html.ValidationMessageFor(m => m.DoubleValue)
        </li>
        <li>
            @Html.LabelFor(m => m.IntegerValue)
            @Html.TextBoxFor(m => m.IntegerValue)
            @Html.ValidationMessageFor(m => m.IntegerValue)
        </li>
        <li>
            @Html.LabelFor(m => m.DateValue)
            @Html.TextBoxFor(m => m.DateValue)
            @Html.ValidationMessageFor(m => m.DateValue)
        </li>
        <li>
            <input type="submit" value="Submit"/>
        </li>
    </ol>
}

پرژه را اجرا نمایید و در ٢ تکست باکس اول ٢ عدد اعشاری را و در ٢ تکست باکس آخر یک عدد صحیح و یک تاریخ وارد نمایید و سپس دکمه Submit را بزنید. پس از بازگشت صفحه از سمت سرور در در ٢ تکست باکس اول با این پیامها روبرو میشوید که مقادیر وارد شده نامعتبر میباشند. 

اگر پروژه رو در حالت دیباگ اجرا کنیم و نگاهی به داخل ModelState بیاندازیم، میبینیم که کاراکتر جدا کننده قسمت اعشاری برای fa-IR '/' میباشد که در اینجا برای اعداد مورد نظر کاراکتر '.' وارد شده است. 

برای فایق شدن بر این مشکل یا باید سمت سرور اقدام کرد یا در سمت کلاینت. در بخش اول راه حل سمت کلاینت را بررسی مینماییم. 

در سمت کلاینت برای اینکه کاربر را مجبور به وارد کردن کاراکترهای مربوط به Culture فعلی سایت نماییم باید مقادیر وارد شده را اعتبارسنجی و در صورت معتبر نبودن مقادیر پیام مناسب نشان داده شود. برای اینکار از کتابخانه jQuery Globalize استفاده میکنیم. برای اضافه کردن jQuery Globalize از طریق کنسول nuget فرمان زیر اجرا نمایید: 

PM> Install-Package jquery-globalize

 پس از نصب کتابخانه  اگر به پوشه Scripts نگاهی بیاندازید میبینید که پوشەای با نام jquery.globalize اضافه شده است. درداخل پوشه زیر پوشەی دیگری با نام cultures وجود دارد که در آن Cultureهای مختلف وجود دارد و بسته به نیاز میتوان از آنها استفاده کرد. دوباره به سراغ فایل Index.cshtm بروید و فایلهای جاوا اسکریپتی زیر را به صفحه اضافه کنید:

<script src="~/Scripts/jquery.validate.js"> </script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"> </script>
<script src="~/Scripts/jquery.globalize/globalize.js"> </script>
<script src="~/Scripts/jquery.globalize/cultures/globalize.culture.fa-IR.js"> </script>

در فایل globalize.culture.fa-IR.js کاراکتر جدا کننده اعشاری '.' در نظر گرفته شده است که مجبور به تغییر آن هسیتم. برای اینکار فایل را باز کرده و numberFormat را پیدا کنید و آن را به شکل زیر تغییر دهید: 

numberFormat: {
    pattern: ["n-"],
    ".": "/",
    currency: {
        pattern: ["$n-", "$ n"],
        ".": "/",
        symbol: "ریال"
    }
},

و در نهایت کدهای زیر را به فایل Index.cshtml اضافه کنید و برنامه را دوباره اجرا نمایید:

Globalize.culture('fa-IR');
$.validator.methods.number = function(value, element) {
    if (value.indexOf('.') > 0) {
        return false;
    }
    var splitedValue = value.split('/');
    if (splitedValue.length === 1) {
        return !isNaN(Globalize.parseInt(value));
    } else if (splitedValue.length === 2 && $.trim(splitedValue[1]).length === 0) {
        return false;
    }
    return !isNaN(Globalize.parseFloat(value));
};
};

در خط اول Culture را ست مینمایم و در ادامه نحوه اعتبارسنجی را در unobtrusive validation تغییر میدهیم. از آنجایی که برای اعتبارسنجی عدد وارد شده از تابع parseFloat استفاده میشود، کاراکتر جدا کننده قسمت اعشاری قابل قبول برای این تابع '.' است پس در داخل تابع دوباره '/' به '.' تبدیل میشود و سپس اعتبارسنجی انجام میشود از اینرو اگر کاربر '.' را نیز وارد نماید قابل قبول است به همین دلیل با این خط کد if (value.indexOf('.') > 0) وجود نقطه را بررسی میکنیم تا در صورت وجود '.' پیغام خطا نشان داده شود.در خط بعدی بررسی مینماییم که اگر عدد وارد شده اعشاری نباشد از تابع parseInt  استفاده نماییم. در خط بعدی این حالت را بررسی مینماییم که اگر کاربر عددی همچون /١٢ وارد کرد پیغام خطا صادر شود. 

برای اعتبارسنجی تاریخ شمسی متاسفانه توابع کمکی برای تبدیل تاریخ در فایل globalize.culture.fa-IR.js وجود ندارد ولی اگر نگاهی به فایلهای Culture عربی بیاندازید همه دارای توابع کمکی برای تبدیل تاریج هجری به میلادی هستند به همین دلیل امکان اعتبارسنجی تاریخ شمسی با استفاده از jQuery Globalize میسر نمیباشد. من خودم تعدادی توابع کمکی را به globalize.culture.fa-IR.js اضافه کردەام که از تقویم فارسی آقای علی فرهادی برداشت شده است و با آنها کار اعتبارسنجی را انجام میدهیم. لازم به ذکر است این روش ١٠٠% تست نشده است و شاید راه کاملا اصولی نباشد ولی به هر حال در اینجا توضیح میدهم. در فایل globalize.culture.fa-IR.js قسمت Gregorian_Localized را پیدا کنید و آن را با کدهای زیر جایگزین کنید: 

Gregorian_Localized: {
    firstDay: 6,
    days: {
        names: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"],
        namesAbbr: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"],
        namesShort: ["ی", "د", "س", "چ", "پ", "ج", "ش"]
    },
    months: {
        names: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""],
        namesAbbr: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""]
    },
    AM: ["ق.ظ", "ق.ظ", "ق.ظ"],
    PM: ["ب.ظ", "ب.ظ", "ب.ظ"],
    patterns: {
        d: "yyyy/MM/dd",
        D: "yyyy/MM/dd",
        t: "hh:mm tt",
        T: "hh:mm:ss tt",
        f: "yyyy/MM/dd hh:mm tt",
        F: "yyyy/MM/dd hh:mm:ss tt",
        M: "dd MMMM"
    },
    JalaliDate: {
        g_days_in_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
        j_days_in_month: [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
    },
    gregorianToJalali: function (gY, gM, gD) {
        gY = parseInt(gY);
        gM = parseInt(gM);
        gD = parseInt(gD);
        var gy = gY - 1600;
        var gm = gM - 1;
        var gd = gD - 1;

        var gDayNo = 365 * gy + parseInt((gy + 3) / 4) - parseInt((gy + 99) / 100) + parseInt((gy + 399) / 400);

        for (var i = 0; i < gm; ++i)
            gDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i];
        if (gm > 1 && ((gy % 4 == 0 && gy % 100 != 0) || (gy % 400 == 0)))
            /* leap and after Feb */
            ++gDayNo;
        gDayNo += gd;

        var jDayNo = gDayNo - 79;

        var jNp = parseInt(jDayNo / 12053);
        jDayNo %= 12053;

        var jy = 979 + 33 * jNp + 4 * parseInt(jDayNo / 1461);

        jDayNo %= 1461;

        if (jDayNo >= 366) {
            jy += parseInt((jDayNo - 1) / 365);
            jDayNo = (jDayNo - 1) % 365;
        }

        for (var i = 0; i < 11 && jDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i]; ++i) {
            jDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i];
        }
        var jm = i + 1;
        var jd = jDayNo + 1;

        return [jy, jm, jd];
    },
    jalaliToGregorian: function (jY, jM, jD) {
        jY = parseInt(jY);
        jM = parseInt(jM);
        jD = parseInt(jD);
        var jy = jY - 979;
        var jm = jM - 1;
        var jd = jD - 1;

        var jDayNo = 365 * jy + parseInt(jy / 33) * 8 + parseInt((jy % 33 + 3) / 4);
        for (var i = 0; i < jm; ++i) jDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i];

        jDayNo += jd;

        var gDayNo = jDayNo + 79;

        var gy = 1600 + 400 * parseInt(gDayNo / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */
        gDayNo = gDayNo % 146097;

        var leap = true;
        if (gDayNo >= 36525) /* 36525 = 365*100 + 100/4 */ {
            gDayNo--;
            gy += 100 * parseInt(gDayNo / 36524); /* 36524 = 365*100 + 100/4 - 100/100 */
            gDayNo = gDayNo % 36524;

            if (gDayNo >= 365)
                gDayNo++;
            else
                leap = false;
        }

        gy += 4 * parseInt(gDayNo / 1461); /* 1461 = 365*4 + 4/4 */
        gDayNo %= 1461;

        if (gDayNo >= 366) {
            leap = false;

            gDayNo--;
            gy += parseInt(gDayNo / 365);
            gDayNo = gDayNo % 365;
        }

        for (var i = 0; gDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap) ; i++)
            gDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap);
        var gm = i + 1;
        var gd = gDayNo + 1;

        return [gy, gm, gd];
    },
    checkDate: function (jY, jM, jD) {
        return !(jY < 0 || jY > 32767 || jM < 1 || jM > 12 || jD < 1 || jD >
            (Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[jM - 1] + (jM == 12 && !((jY - 979) % 33 % 4))));
    },
    convert: function (value, format) {
        var day, month, year;

        var formatParts = format.split('/');
        var dateParts = value.split('/');
        if (formatParts.length !== 3 || dateParts.length !== 3) {
            return false;
        }

        for (var j = 0; j < formatParts.length; j++) {
            var currentFormat = formatParts[j];
            var currentDate = dateParts[j];
            switch (currentFormat) {
                case 'dd':
                    if (currentDate.length === 2 || currentDate.length === 1) {
                        day = currentDate;
                    } else {
                        year = currentDate;
                    }
                    break;
                case 'MM':
                    month = currentDate;
                    break;
                case 'yyyy':
                    if (currentDate.length === 4) {
                        year = currentDate;
                    } else {
                        day = currentDate;
                    }
                    break;
                default:
                    return false;
            }
        }

        year = parseInt(year);
        month = parseInt(month);
        day = parseInt(day);
        var isValidDate = Globalize.culture().calendars.Gregorian_Localized.checkDate(year, month, day);
        if (!isValidDate) {
            return false;
        }

        var grDate = Globalize.culture().calendars.Gregorian_Localized.jalaliToGregorian(year, month, day);
        var shDate = Globalize.culture().calendars.Gregorian_Localized.gregorianToJalali(grDate[0], grDate[1], grDate[2]);

        if (year === shDate[0] && month === shDate[1] && day === shDate[2]) {
            return true;
        }

        return false;
    }
},

روال کار در تابع convert به اینصورت است که ابتدا تاریخ وارد شده را بررسی مینماید تا معتبر بودن آن معلوم شود به عنوان مثال اگر تاریخی مثل 1392/12/31 وارد شده باشد و در ادامه برای بررسی بیشتر تاریخ یک بار به میلادی و تاریخ میلادی دوباره به شمسی تبدیل میشود و با تاریخ وارد شده مقایسه میشود و در صورت برابری تاریخ معتبر اعلام میشود. در فایل Index.cshtml کدهای زیر اضافی نمایید:

$.validator.methods.date = function (value, element) {
    return Globalize.culture().calendars.Gregorian_Localized.convert(value, 'yyyy/MM/dd');
};

برای اعتبارسنجی تاریخ میتوانید از ٢ فرمت استفاده کنید:

١ – yyyy/MM/dd

٢ – dd/MM/yyyy

البته از توابع اعتبارسنجی تاریخ میتوانید به صورت جدا استفاده نمایید و لزومی ندارد آنها را همراه با jQuery Globalize بکار ببرید. در آخر خروجی کار به این شکل است:

در کل استفاده از jQuery Globalize برای اعتبارسنجی در سایتهای چند زبانه به نسبت خوب میباشد و برای هر زبان میتوانید از culture مورد نظر استفاده نمایید. در قسمت دوم این مطلب به بررسی بخش سمت سرور میپردازیم.

مطالب
ایجاد کپچایی (captcha) سریع و ساده در ASP.NET MVC 5

در این مثال به کمک MVC5، یک کپچای ساده و قابل فهم را تولید و استفاده خواهیم کرد. این نوشته بر اساس این مقاله  ایجاد شده و جزئیات زیادی برای درک افراد مبتدی به آن افزوده شده است که امیدوارم راهنمای مفیدی برای علاقمندان باشد.

با کلیک راست بر روی پوشه کنترلر، یک کنترلر به منظور ایجاد کپچا بسازید و اکشن متد زیر را در آن کنترلر ایجاد کنید: 

public class CaptchaController : Controller
    {
        public ActionResult CaptchaImage(string prefix, bool noisy = true)
        {
            var rand = new Random((int)DateTime.Now.Ticks);
            //generate new question
            int a = rand.Next(10, 99);
            int b = rand.Next(0, 9);
            var captcha = string.Format("{0} + {1} = ?", a, b);

            //store answer
            Session["Captcha" + prefix] = a + b;

            //image stream
            FileContentResult img = null;

            using (var mem = new MemoryStream())
            using (var bmp = new Bitmap(130, 30))
            using (var gfx = Graphics.FromImage((Image)bmp))
            {
                gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                gfx.SmoothingMode = SmoothingMode.AntiAlias;
                gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height));

                //add noise
                if (noisy)
                {
                    int i, r, x, y;
                    var pen = new Pen(Color.Yellow);
                    for (i = 1; i < 10; i++)
                    {
                        pen.Color = Color.FromArgb(
                        (rand.Next(0, 255)),
                        (rand.Next(0, 255)),
                        (rand.Next(0, 255)));

                        r = rand.Next(0, (130 / 3));
                        x = rand.Next(0, 130);
                        y = rand.Next(0, 30);

                        gfx.DrawEllipse(pen, x - r, y - r, r, r);
                    }
                }

                //add question
                gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3);

                //render as Jpeg
                bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
                img = this.File(mem.GetBuffer(), "image/Jpeg");
            }

            return img;
        }

همانطور که از کد فوق پیداست، دو مقدار a و b، به شکل اتفاقی ایجاد می‌شوند و حاصل جمع آنها در یک Session نگهداری خواهد شد. سپس تصویری بر اساس تصویر a+b ایجاد می‌شود (مثل 3+4). این تصویر خروجی این اکشن متد است. به سادگی می‌توانید این اکشن را بر اساس خواسته خود اصلاح کنید؛ مثلا به جای حاصل جمع دو عدد، از کاربرد چند حرف یا عدد که بصورت اتفاقی تولید کرده‌اید، استفاده نمائید.

فرض کنید می‌خواهیم کپچا را هنگام ثبت نام استفاده کنیم.

در فایل AccountViewModels.cs در پوشه مدل‌ها در کلاس RegisterViewModel  خاصیت زیر را اضافه کنید:

[Required(ErrorMessage = "لطفا {0} را وارد کنید")]
         [Display(Name = "حاصل جمع")]
         public string Captcha { get; set; }

حالا در پوشه View/Account به فایل Register.Cshtml خاصیت فوق را اضافه کنید:

<div class="form-group">
                        <input type="button" value="" id="refresh" />

                        @Html.LabelFor(model => model.Captcha)

                        <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" style="" />
                    </div>

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

حالا کد ایجکسی برای آپدیت کپچا توسط دکمه refresh را  به شکل زیر بنویسید (من در پایین ویوی Register، اسکریپت زیر را قرار دادم): 

<script type="text/javascript">
    $(function () {
        $('#refresh').click(function () {


            $.ajax({
                url: '@Url.Action("CaptchaImage","Captcha")',
                type: "GET",
                data: null
            })
            .done(function (functionResult) {
                $("#imgcpatcha").attr("src", "/Captcha/CaptchaImage?" + functionResult);
            });

        });
    });
</script>

آنچه در url نوشته شده است، شاید اصولی‌ترین شکل فراخوانی یک اکشن متد باشد. این اکشن در ابتدای مقاله تحت کنترلری به نام Captcha معرفی شده بود و خروجی آن آدرس یک فایل تصویری است. نوع ارتباط، Get است و هیچ اطلاعاتی به اکشن متد فرستاده نمیشود، اما اکشن متد ما آدرسی را به ما برمی‌گرداند که تحت نام FunctionResult آن را دریافت کرده و به کمک کد جی کوئری، مقدارش را در ویژگی src تصویر موجود در صفحه جاری جایگزین می‌کنیم. دقت کنید که برای دسترسی به تصویر، لازم است جایگزینی آدرس، در ویژگی src به شکل فوق صورت پذیرد.*

تنها کار باقیمانده اضافه کردن کد زیر به ابتدای اکشن متد Register درون کنترلر Account است. 

if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha)
            {
                ModelState.AddModelError("Captcha", "مجموع اشتباه است");
            }

واضح است که اینکار پیش از شرط if(ModelState.IsValidate) صورت میگیرد و وظیفه شرط فوق، بررسی ِ برابریِ مقدار Session تولید شده در اکشن CaptchaImage  (ابتدای این مقاله) با مقدار ورودی کاربر است. (مقداری که از طریق خاصیت تولیدی خودمان  به آن دسترسی داریم) . بدیهی‌است اگر این دو مقدار نابرابر باشند، یک خطا به ModelState اضافه می‌شود و شرط ModelState.IsValid که در اولین خط بعد از کد فوق وجود دارد، برقرار نخواهد بود و پیغام خطا در صفحه ثبت نام نمایش داده خواهد شد.

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


* اصلاح : دقت کنید بدون استفاده از ایجکس هم میتوانید تصویر فوق را آپدیت کنید:

  $('#refresh').click(function () {
         
            var d = new Date();
            $("#imgcpatcha").attr("src", "Captcha/CaptchaImage?" + d.getTime());

        });

رویداد کلیک را با کد فوق جایگزین کنید؛ دو نکته در اینجا وجود دارد :

یک. استفاده از زمان در انتهای آدرس به خاطر مشکلاتیست که فایرفاکس یا IE با اینگونه آپدیت‌های تصویری دارند. این دو مرورگر (بر خلاف کروم) تصاویر را نگهداری میکنند و آپدیت به روش فوق به مشکل برخورد میکند مگر آنکه آدرس را به کمک اضافه کردن زمان آپدیت کنید تا مرورگر متوجه داستان شود

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

نظرات مطالب
مبانی TypeScript؛ ماژول‌ها
- با هربار تغییر فایل tsconfig.json، کامپایل دوباره‌ی پروژه را فراموش نکنید (مهم). از منوی build گزینه‌ی rebuild solution را انتخاب کنید. این rebuild، کار کامپایل مجدد فایل‌های ts. را هم انجام می‌دهد.
- commonjs بیشتر برای برنامه‌های nodejs استفاده می‌شود. اگر علاقمند باشید که با سیستمی شبیه به AngularJS 2.0 کار کنید، از یک module loader ویژه، به نام SystemJS استفاده کنید (که قابلیت بارگذاری خودکار ES6 modules, AMD, CommonJS را دارد). بنابراین فایل tsconfig.json را به این صورت تغییر دهید:
{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "es5",
        "module": "system",
        "sourceMap": true
    }
}
بعد فایل index.html شما چنین شکلی را پیدا می‌کند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css"/>

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.22/system.js"></script>
    <script type="text/javascript">
        System.defaultJSExtensions = true;
        System.import('app');
    </script>
</head>
<body>
    <h1>TypeScript HTML App</h1>

    <div id="content"></div>
</body>
</html>
در اینجا System.JS کار بارگذاری اولین ماژول برنامه یا همان app.js را به صورت خودکار انجام می‌دهد (و همچنین تمام ماژول‌های مرتبط با آن‌را). بنابراین دیگر نیازی به ذکر اسکریپت‌های برنامه در اینجا نیست (هیچکدام از آن‌ها، منهای موارد عمومی مثل خود system.js).
بعد فایل app.ts را هم به این صورت تغییر دهید، چون این کدها پس از onload اجرا می‌شوند:
import {Book} from "./testmd";

let book: Book = new Book();
console.log(book.bookName);
document.getElementById("content").innerText = book.GetbookNmae;
مطالب
مدیریت کلیدهای کیبرد در جاوا اسکریپت
با پیشرفت بسترهای موجود در زمینه شبکه و اینترنت، گرایش به استفاده از اپلیکیشنهای تحت وب روز به روز بیشتر میشود. با گسترش این برنامه‌ها نیازها و درنتیجه ابزارهای موجود توسعه پیدا می‌کنند. درحال حاضر ابزارها و نیز محیطهای توسعه مختلفی برای تولید این اپلیکیشنها وجود دارد. به دلیل نوع رابط کاربری موجود در این برنامه‌ها (اکثراً مرورگرهای وب مثل اینترنت اکسپلورر، گوگل کروم، فایرفاکس و ...) استفاده از زبانهای سمت کلاینت (مثل جاوا اسکریپت که در تمامی مرورگرهای مدرن پشتیبانی کاملی از آن میشود) جایگاه ویژه ای در این نوع برنامه‌ها دارد. درضمن وقتی صحبت از اپلیکیشن به میان می‌آید استفاده از کلیدهای میانبر کیبرد برای راحتی کار کاربران کاربرد ویژه ای دارد. اما متاسفانه زبان جاوا اسکریپت به دلیل محدودیتهایی منطقی موجود، پشتیبانی مناسبی از رویدادهای کیبرد ندارد و مشکل تفاوتها و تناقضات میان سخت افزارها، سیستم عامل‌ها و مرورگرها هم به این مسئله بیشتر دامن میزند. در مطلب جاری هدف این است تا آشنایی مقدماتی با این مبحث فراهم شود.
رویدادهای کیبرد
در جاوا اسکریپت سه رویداد زیر برای کلیدهای کیبرد وجود دارد (به ترتیب زمان رخ دادن):
keydown: زمانی که یک کلید فشرده می‌شود.
keypress: زمانی که یک کلید کاراکتری فشرده می‌شود.
keyup: زمانی که یک کلیدِ فشرده شده، رها می‌شود.
یک تفاوت اساسی میان رویدادهای keydown و keypress در جاوا اسکریپت وجود دارد: رویداد keydown پس از فشردن هر کلیدی روی کیبرد رخ میدهد و یک کد مخصوص آن کلید (scan code ^) را ارائه میدهد. اما رویداد keypress که بعد از keydown رخ میدهد کد کاراکتر آن کلید (char code) را ارائه میدهد، بنابراین تنها برای کلیدهای کاراکتری بدرستی کار میکند. برای درک بهتر کد زیر را در یک فایل html ذخیره کرده و در مرورگرهای مختلف آزمایش کنید:
<html>
<body>
  <div>
    Prevent default:
    <input type="checkbox" id="keydownStop" value="1" />
    keydown&nbsp;&nbsp;&nbsp;
    <input type="checkbox" id="keypressStop" value="1" />
    keypress&nbsp;&nbsp;&nbsp;
    <input type="checkbox" id="keyupStop" value="1" />
    keyup
  </div>
  Ignore:
  <input type="checkbox" id="keydownIgnore" value="1" />
  keydown &nbsp;&nbsp;&nbsp;
  <input type="checkbox" id="keypressIgnore" value="1" />
  keypress &nbsp;&nbsp;&nbsp;
  <input type="checkbox" id="keyupIgnore" value="1" />
  keyup
  <div>
    Focus on the input below and press any key.
  </div>
  <div>
    <input type="text" style=" width: 600px" id="keyInput" />
  </div>
  Log:
  <div>
    <textarea id="keyLogger" rows="18" onfocus="this.blur()" style="width: 600px; border: 1px solid black"></textarea>
  </div>
  <input type="button" value="Clear" onclick="clearLog()" />
  <script type="text/javascript">
    document.getElementById('keyInput').onkeydown = keyHandler;
    document.getElementById('keyInput').onkeyup = keyHandler;
    document.getElementById('keyInput').onkeypress = keyHandler;
    document.getElementById('keyInput').focus();
    function keyHandler(e) {
      e = e || window.event;
      if (document.getElementById(e.type + 'Ignore').checked) return;
      var evt = e.type;
      while (evt.length < 10) evt += ' ' +
        log(evt +
          ' keyCode=' + e.keyCode +
          ' which=' + e.which +
          ' charCode=' + e.charCode +
          ' char=' + String.fromCharCode(e.keyCode || e.charCode) +
          (e.shiftKey ? ' +shift' : '') +
          (e.ctrlKey ? ' +ctrl' : '') +
          (e.altKey ? ' +alt' : '') +
          (e.metaKey ? ' +meta' : ''));
      if (document.getElementById(e.type + 'Stop').checked) {
        e.preventDefault ? e.preventDefault() : (e.returnValue = false);
      }
    }
    function clearLog() {
      document.getElementById('keyLogger').value = '';
      document.getElementById('keyInput').focus();
    }
    function log(text) {
      var area = document.getElementById('keyLogger');
      area.value += text + '\n';
      area.scrollTop = area.scrollHeight;
    }
  </script>
</body>
</html>
نکته: برای جلوگیری از اجرای مرورگرها در حالت Quirks حتما از تگ doctype در ابتدای فایلهای html خود استفاده کنید. درغیراینصورت رفتارهای غیرمنتظره ای (مخصوصا در IE) مشاهده خواهید کرد. برای اجرای مرورگرها در حالت استاندارد html5 (بهترین حالت در حال حاضر) میتوانید از تگ زیر استفاده کنید:
<!doctype html>
دقت کنید که قبل از این خط هیچ چیز دیگری نوشته نشود وگرنه در IE از آن صرفنظر میشود!
یا اینکه در IE با استفاده از developer tools (دکمه F12) برای Document Mode گزینه ای غیر از Quirks mode (بهتر است از حالت IE9 یا بالاتر استفاده کنید) را انتخاب کنید.

برای کسب اطلاعات بیشتر راجع به doctypeهای مختلف و نیز حالت quirks میتوانید به ^ و ^ و ^ و ^ رجوع کنید. پیشنهاد میکنم که این منابع را حتما مطالعه کنید.
نکته: در کد بالا متد preventDefault در  -8 IE تعریف نشده است (درواقع در IE تنها در نسخه 9 تعریف شده است). همچنین استفاده از پراپرتی returnValue در فایرفاکس و IE9 کار نمیکند! از این خط کد برای جلوگیری از رفتار پیشفرض رویداد استفاده شده است. همانطور که در ادامه میخوانید راه حل ساده‌تری نیز برای اینکار وجود دارد.
متد String.fromCharCode برای نمایش کاراکتر کلید فشرده شده استفاده شده است. البته اگر کلید غیرکاراکتری فشرده شود ممکن است با نتایج غیرمنتظره ای روبرو شوید.
با استفاده از html تولیدی در مرورگرهای مختلف سعی کنید موارد زیر را آزمایش کنید:
کلیدهای کاراکتری چون a / | { 6 را  بفشارید. در این حالت رویدادهای keydown و سپس keypress رخ خواهند داد. پس از رها کردن کلیدها نیز رویداد keyup رخ میدهد.
یکی از کلیدهای غیرکاراکتری مثل ctrl یا alt را بفشارید. در این حالت تنها رویدادهای keydown و keyup رخ خواهند داد و خبری از رویداد keypress نیست.
نکته: مرورگرهای FireFox و Opera در مورد بیشتر کلیدهای غیرکاراکتری نیز رویداد keypress را صدا خواهند زد! مرورگر IE این رفتار را تنها در مورد کلید Esc نشان میدهد. همچنین در IE و Opera کلید PrtScr هیچ رویدادی را فرا نمیخواند. ظاهرا تنها مرورگر Chrome بدرستی عمل میکند. 
درحالت کلی فشردن کلیدهای غیرکاراکتری نباید رویداد keypress را فراخوانی کند.
بنابراین:
keydown و keyup برای همه کلیدها
keypress برای کلیدهای کاراکتری

پراپرتی‌های رویدادهای کیبرد
برخلاف نسخه‌های قدیمی مرورگرها که هرکدام راه و روش خودشان را برای تعامل با این رویدادها برگزیده بودند، امروزه تمامی مرورگرها تقریبا از یک روش استاندارد و مشترک برای اینکار استفاده میکنند. در تصاویر زیر تمام اجزای آبجکت KeyboardEvent با استفاده از کد زیر در مرورگرهای اپرا و کروم نشان داده شده است. استفاده از کد زیر در مرورگرهای فایرفاکس و IE نتایج جالبی مانند تصاویر زیر فراهم نمیکند!
    document.onkeydown = function (e) {
      e = e || event;
      console.log(e);
    }

همانطور که مشاهده میکنید تفاوتهایی بین مرورگرها در این آبجکت به چشم میخورد. برای کسب اطلاعات بیشتر در مورد این آبجکت و اجزای استاندارد آن در DOM Level 3 به اینجا مراجعه کنید. در ادامه به بررسی پراپرتی‌های مهم آرگومان این رویدادها (همان KeyboardEvent) میپردازیم.
keyCode
همان scan code کلید فشرده شده است. برای مثال اگر کلید a فشرده شود کاراکتر تولیدی ممکن است a یا A یا 'ش' (یا کاراکتری دیگر در زبانهای مختلف) باشد اما در تمامی حالات scan code مربوطه یا همان keyCode همیشه یکسان (65 برای کلید a) خواهد بود. این کد تنها به کلید فشرده شده بستگی دارد و نه به کاراکتر حاصله! البته در IE به هنگام رخ دادن رویداد keypress کد کاراکتر (همان char code) کلید فشرده شده در این پراپرتی قرار میگیرد!
در این بین میان مرورگرهای مختلف تفاوتهایی وجود دارد که با یک جستجو در اینترنت میتوان به تمامی این کدها دسترسی پیدا کرد. خواندن مقاله کامل JavaScript Madness: Keyboard Events نیز خالی از لطف نیست.
charCode
همان کد ASCII (یا کد UTF-16 برای کاراکترهای یونیکد. اطلاعات بیشتر ^ و ^) کاراکتر کلید فشرده شده است. ممکن است با keyCode برابر باشد. این پراپرتی در IE و Opera تعریف نشده است.
در عمل ممکن است keyCode و charCode در پلتفرمهای مختلف و حتی بین سیستم عامل‌های مختلف در یک سخت افزار نتایج متفاوتی ارائه دهند. بنابراین آزمودن هر مورد مشکوک قبل از ریلیز نهایی محصول میتواند مفید باشد.
which
یک پراپرتی نسبتا غیراستاندارد! است که ترکیبی از keyCode و charCode را برمیگرداند (اطلاعات بیشتر در ^ و ^). این پراپرتی در IE تعریف نشده است.
shiftKey, ctrlKey, altKey, metaKey 
پراپرتی هایی از نوع بولین که وضعیت کلیدهای Shift, Ctrl, Alt و Command (تنها در مک) را نشان میدهند. 
نکته: بدستن آوردن کد درست کاراکتر فشرده شده با توجه به وضعیت کلیدها و زبان انتخابی تنها با استفاده از رویداد keypress امکان پذیر است. با توجه به تفاوتهایی که بین مرورگرهای مختلف وجود دارد برای یافتن کاراکتر فشرده شده در رویداد keypress استفاده از متد زیر توصیه میشود:
function getCharacter(event) {
  if (event.which == null)
     return String.fromCharCode(event.keyCode);    // IE
  else if (event.which != 0 && event.charCode != 0)
     return String.fromCharCode(event.which);  // All others
  return null; // special key
}
توضیحات بیشتر و کاملتر این مبحث را میتوانید از Document Object Model (DOM) Level 3 Events Specification که آخرین نسخه آن در زمان تهیه این مطلب در تاریخ 14 June 2012 انتشار یافته تهیه کنید. بطور ویژه برای مبحث رویدادهای کیبرد (^) اطلاعات بسیار بیشتری در دسترس است.
نکته: با توجه به اطلاعاتی که در سند فوق وجود دارد، به دلیل ماهیت همزمانی (Sync) رویدادهای کیبرد تا زمانی که تمام عملیات موجود در متد تعیین شده برای این رویدادها انجام نشود، رویداد بعدی (با توجه به ترتیبی که در ابتدای این مطلب آورده شده است) رخ نخواهد داد. برای تست این موضوع قطعه کد زیر را آماده کردم:
<html>
<body>
  <input id="inputText" type="text"  />
  <script>
    document.onkeydown = keyDown;
    document.onkeypress = keyPress;
    document.onkeyup = keyUp;
    function keyDown(e) {
      var input = document.getElementById('inputText');
      input.value = 'keyDown started ...';
      input.disabled = true;
      var j = 0;
      for (var i = 0; i < 999999999; i++) {
        j = i - j;
      }
      console.log(j);
      //alert('keyDown');
      input.value = 'keyDown finished.';
      input.disabled = false;
    }
    function keyPress(e) {
      alert('keyPress');
      //console.log('keyPress');
    } function keyUp(e) {
      alert('keyUp');
      //console.log('keyUp');
    }
  </script>
</body>
</html>
اگر کد بالا در مرورگرهای مختلف امتحان کنید مشاهده میکنید که انجام عملیات سنگین در رویدادهای کیبرد موجب ایجاد وقفه در فراخوانی سایر رویدادها میشود.
با اجرای کد فوق در مرورگرهای مختلف نکات جالب زیر بدست آمد:
- در IE و کروم نمایش یک alert موجب از دست دادن فوکس document شده و بنابراین رویدادهای کیبرد بعد از نمایش alert کار نخواهند کرد! مثلا اگر در keydown یک alert نمایش داده شود چون رویداد keyup بر روی پنجره alert رخ میدهد بنابراین keyup فراخوانی نمیشود ولی چون رویداد keypress با keydown همزمان است این اتفاق برای keypress نمی‌افتد. این مشکل در فایرفاکس پیش نمی‌آید. در اپرا در این حالت رویداد keypress هم رخ نمیدهد! البته رفتار IE در اجرای کد فوق کمی غیرمنتظره‌تر است و ظاهرا رویداد keypress هم رخ نمیدهد.
- در اجرای کد فوق در FireFox ظاهرا alert مربوط به keyup قبل از keypress نمایش داده میشود. البته اگر به جای alert از console.log (البته نیاز به نصب Firebug است) استفاده شود این به هم خوردگی ترتیب رویدادها وجود ندارد.
- در مرورگر Opera پس از فشردن کلید enter در نوار آدرس و پس از بارگذاری صفحه، فوکس بلافاصله به عنصر document سپرده میشود طوریکه که رویداد keyup کلید enter در document بارگذاری شده فراخوانی میشود و درصورت سرعت بالای بارگذاری صفحه، کدهای مروبوط به این رویداد اجرا میشوند. در سایر مرورگرها این مورد مشاهده نشد. 
- ظاهرا در تمام مرورگرها به غیر Opera کدهای جاوا اسکریپ در ثرد UI اجرا شده و موجب قفل شدن document میشود. بنابراین وسط اجرای کدهای سنگین نمیتوان مثلا خواص عناصر UI را تغییر داد. درواقع مرورگر اپرا برخلاف سایر مرورگرها، رفتار ویژه ای در برخورد با جاوا اسکریپت و رویدادها و تغییرات عناصر DOM دارد. برای کسب اطلاعات بیشتر در این زمینه به اینجا مراجعه کنید. درضمن این مبحث کمی پیچیده‌تر از آن است که به نظر می‌آید(^). برای بررسی بیشتر میتوانید کد زیر را در مرورگرهای مختلف آزمایش کنید:
<html>
<head>
  <script type="text/javascript">
    function process() {
      var above = 0, below = 0;
      for (var i = 0; i < 200000; i++) {
        if (Math.random() * 2 > 1) {
          above++;
        }
        else {
          below++;
        }
      }
    }
    function test() {
      var result1 = document.getElementById('log');
      var start = new Date().getTime();
      console.log('start');
      for (var i = 0; i < 200; i++) {
        result1.value = 'time=' + (new Date().getTime() - start) + ' [i=' + i + ']';
        process();
      }
      result1.value = 'time=' + (new Date().getTime() - start) + ' [done]';
      console.log('end');
    }
    window.onload = test;
  </script>
</head>
<body>
  <input id='log' />
</body>
</html>
اگر کد فوق را در مرورگرهایی غیر از اپرا اجرا کنید میبینید که تنها نتیجه نهایی نمایش داده میشود و فرایندهای میانی درون حلقه نمیتوانند تغییری در محتوای UI ایجاد کنند. طبق انتظار کروم از بقیه بسیار سریعتر بوده و سپس IE و پس از آن فایرفاکس قرار دارد. اما در مورد Opera وضع کاملا فرق میکند و به دلیل به روز رسانی همزمان UI عملیات بسیار بسیار کندتر از بقیه مرورگرها به اتمام میرسد.
نکته: حالات استثنایی دیگری هم در اجرای کدهای مشابه در مرورگرهای مختلف پیش می‌آید که به دلیل پیچیده کردن بیش از حد بحث آورده نشده اند. فقط ذکر این نکته الزامی است که درحال حاضر میزان تفاوت رفتار مرورگرهای مختلف دربرخورد با کدهای یکسان قابل ملاحظه است. بنابراین در هنگام توسعه سعی کنید حداقل در این چهار مرورگر معروف آزمایشات خود را به سرانجام برسانید.
و در ادامه چند مثال ...

غیرفعال کردن ورودی کاربر
برای اینکار فقط کافی است در رویدادهای keydown یا keypress مقدار false برگشت داده شود. البته در مروگر Opera برخی از کلیدها از این رفتار پیروی نمیکنند. مثل کاراکترهای '`' و '+' و '=' که درصورت برگشت false در رویداد keydown باز هم رفتار پیش فرض را از خود نشان میدهد. اما برگرداندن مقدار false در رویداد keypress این مشکل را حل میکند(این موارد در نسخه 11.51 تست شدند). میتوانید با استفاده از کد زیر در تمام مرورگرها این موارد را آزمایش کنید:
<input onkeydown="return false" />
<input onkeypress="return false" /> 

تبدیل به حروف بزرگ
document.getElementById('myInputText').onkeypress = function (e) {
  var char = getCharacter(e || window.event);
  if (!char) return; // special key
  this.value += char.toUpperCase();
  return false; // برای اینکه کاراکتر اضافی نمایش داده نشود
}
در کد فوق متد getCharacter در بالا در قسمت پراپرتی‌های آرگومان رویداد نشان داده شده است. کد فوق چندان کامل نیست و همیشه کاراکتر فشرده شده را بدون توجه به موقعیت کرسر کیبرد در انتهای متن قرار میدهد.
نکته: استفاده از عبارتی چون e || window.event به این دلیل است که در مرورگر IE آرگومان رویداد (همان e که به آن implicit event object نیز میگویند) به متد مربوطه ارسال نمیشود (تا نسخه 9 که تست کردم مسئله به همین صورت است) در عوض پراپرتی‌های این آرگومان از طریق window.event (یا همان event که به آن explicit event object نیز میگویند) در دسترس هستند. این فیلد در واقع آرگومان آخرین رویداد رخ داده در پنجره جاری را در خود ذخیره میکند. اما در سایر مرورگرها به صورت استاندارد مقدار این آرگومان به عنوان پارامتر رویداد به متد مربوطه ارسال میشود. جالب است که بدانید مرورگرهای Opera و Chrome از هر دو روش پشتیبانی میکنند. مرورگر فایرفاکس تنها از ارسال آرگومان رویداد به متد مربوطه پشتیبانی میکند.
عبارت e ||window.event درواقع شکل دیگر عبارت زیر است:
e ? e : window.event;
در جاوا اسکریپت اگر عملگر مقایسه ای در عبارت مقایسه آورده نشود مقدار عبارت با false مقایسه میشود. این مقایسه از نوع abstract است (در ادامه این مطلب توضیح داده شده است). در جاوا اسکریپت 0 و رشته خالی و null و undefined و امثال اینها در مقایسه abstract برابر false درنظر گرفته میشوند. بنابراین در عبارت مقایسه بالا اگر مقدار e مثلا undefined (در IE) باشد مقدار window.event بازگشت داده میشود و درغیراینصورت خود e برگشت داده میشود.

تنها عدد
document.getElementById('numberInputText').onkeypress = function (e) {
  e = e || window.event;
  var chr = getCharacter(e);
  if (!isNumeric(chr) && chr !== null) return false;
}
function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}
function isNumber(val) {
  return val !== "NaN" && (+val) + '' === val + ''
}
در کد بالا متد isNumeric از اینجا گرفته شده است. متد isNumeric جی کوئری (که از نسخه 1.7 اضافه شده است) هم دقیقا از این روش استفاده میکند.
متد دوم یعنی  isNumber (که در کد فوق از آن استفاده نشده است) روش دیگری را برای اطمینان از مقدار عددی بودن استفاده میکند. در این روش درصورتیکه val یک مقدار عددی نباشد val+ برابر NaN میشود که در نهایت عبارت مقایسه ای مقدار false را برمیگرداند. البته به غیر از خود مقدار NaN که در شرط اول مورد بررسی قرار گرفته است. برای کسب اطلاعات بیشتر در مورد رفتار نسبتا عجیب جاوا اسکریپت با NaN و متد isNaN به اینجا سر بزنید.
نکته: تفاوت == با === (یا =! و ==!) - در جاوا اسکریپت دو روش برای مقایسه مقادیر متغیرها وجود دارد. اولی (== یا =!) مقایسه را با تبدل نوع داده‌ها انجام میدهد (که اصطلاحا به آن type-converting equality comparison یا abstract comparison میگویند) و دومی (=== یا ==!) مقایسه را با مقادیر واقعی و بدون تبدیل نوع داده انجام میدهد (که به آن equality without type coercion یا strict equality comparison گفته میشود). در واقع در مقایسه strict تنها وقتی که دو متغیر از یک نوع باشند ممکن است مقدار true برگشت داده شود. برای روشنتر شدن مطلب به مثالهای زیر توجه کنید (^):
0==false   // true
0===false  // false, because they are of a different type
1=="1"     // true, auto type coercion
1==="1"    // false, because they are of a different type 
توضیحات بهتری در اینجا آورده شده است.
برای کسب اطلاعات کاملتر میتوانید به ECMAScript Language Specification و قسمت مقایسه strict و abstract مراجعه کنید. (رابطه‌ها و تفاوت‌های میان ECMAScript و JavaScript و JScript و ActionScript در اینجا آورده شده است.)

کار با Scan Code و Char Code
همانطور که قبلا هم اشاره شد میان کد کاراکتر و کلید فشرده شده بر روی کیبرد تفاوت وجود دارد. برای بهره برداری از کلیدهای میانبر در صفحات وب به کد کلید فشرده شده (همان scan code) نیاز است و نه به کد کاراکتر آن. همچنین کلیدهای ویژه و غیرکاراکتری دارای کد کاراکتر نیستند. بیشتر مرورگرها رویداد Keypress را برای کلیدهای غیرکاراکتری فرا نمیخوانند. بنابراین برای این موارد رویدادهای keydown و keyup مفید هستند. امروز تمام مرورگرها از جدول کدهای یکسانی برای scan codeها استفاده میکنند که نمونه‌های آن را میتوانید در ^ و ^ و ^ مشاهده کنید. نکته ای که باید درباره پراپرتی keyCode یادآوری شود این است که جدای از وضعیت کیبرد (مثل زبان یا موقعیت کلید capsLock) در تمام حالات در ازای فشردن شده یک کلید خاص (یا ترکیبی از کلیدهای کنترلی با سایر کلیدها) همواره باید یک کد یکسان برگشت داده شود.
پس یک charCode کد یونیکد کاراکتر کلید فشرده شده است که تنها در رویداد keypress در دسترس است. یک keyCode کد خود کلید فشرده شده یا همان scan code است که در رویدادهای keydown و keyup در دسترس است.
نکته: برای تمام کلیدهای الفبایی-عددی scan code با char code یکی است. مثلا برای حروف الفبایی scan code یک کلید با کد ASCII حرف انگلیسی بزرگ آن کلید برابر است.
بنابراین برای بررسی کلید ترکیبی ctrl + a میتوان از کد زیر در رویداد keydown استفاده کرد:
e.ctrlKey && e.keyCode == 'A'.charCodeAt(0)
و فرقی نمیکند که کاراکتر حاصله 'a' یا 'A' یا 'ش' باشد. 
نکته: برای تمام کلیدهای کیبرد به غیر از ';' و '=' و '-' تمام مرورگرها از کدهای یکسانی استفاده میکنند. میتوانید این کلیدها را با استفاده از قطعه کد اول این مطلب در مرورگرهای مختلف آزمایش کنید.
نکته: با برگشت مقدار false در رویداد keydown یا keypress رفتار پیشفرض کلید یا ترکیب کلیدهای مربوطه غیرفعال میشود. البته به غیر از عملیاتهای سطح سیستم عامل (مثل alt+F4).
چند مثال دیگر در ادامه ...

جابجایی تصویر
کدهای زیر را در یک فایل html ذخیره کرده و توسط یک مرورگر فایل حاصله را باز کنید.
<html>
<body>
  <div id="dotnettips" style="width: 35px; height: 35px; background-image: url(https://www.dntips.ir/favicon.ico);
    position: absolute; left: 10px; top: 10px;" tabindex="0">
  </div>
  <script>
    document.getElementById('dotnettips').onkeydown = function (e) {
      e = e || event;
      switch (e.keyCode) {
        case 37: // left
          this.style.left = parseInt(this.style.left) - this.offsetWidth + 'px';
          return false;
        case 38: // up
          this.style.top = parseInt(this.style.top) - this.offsetHeight + 'px';
          return false;
        case 39: // right
          this.style.left = parseInt(this.style.left) + this.offsetWidth + 'px';
          return false;
        case 40: // down
          this.style.top = parseInt(this.style.top) + this.offsetHeight + 'px';
          return false;
      }
    }
  </script>
</body>
</html>
بر روی تصویر کلید کرده (یا با استفاده از Tab فوکس را روی تصویر قرار دهید) و با استفاده از کلیدهای مکان نما (Arrow keys) سعی کنید تصویر را در صفحه جابجا کنید.
در کد فوق برای جلوگیری از رفتار پیش فرض کلیدهای مکان نما (جابجایی محتوای صفحه در صورت وجود اسکرول) مقدار false برگشت داده شده است.
نکته: استفاده از خاصیت tabindex برای امکان فوکس بر روی div اجباری است.
نکته: استفاده از event به جای window.event تا زمانی که یک متغیر در اسکوپ جاری با نام event وجود نداشته باشد مشکلی ایجاد نمیکند.

Caps Lock
متاسفانه راه مستقیمی برای دریافت وضعیت کلید caps lock در جاوا اسکریپت وجود ندارد. ظاهرا تنها راه حل موجود برای این مسئله بررسی کد کاراکتر کلید فشرده شده در رویداد keypress و بررسی آن با توجه به وضعیت پراپرتی shift این رویداد است. بدین ترتیب که اگر کد کاراکتر مربوط به حروف بزرگ بوده درحالیکه کلید شیفت نگه داشته نشده است بنابراین کلید Caps Lock روشن است و بالعکس. البته با ترکیب این روش و نیز رصد scan code کلید Caps Lock (کد آن برابر 20 است) در رویداد keydown میتوان وضعیت بهتری پدید آورد. قطعه کد زیر برای اینکار است. در این کد از یک متغیر گلوبال برای نگهداری وضعیت دکمه caps lock استفاده میشود.
<html>
<body>
  <input id="inputText" type="text" />
  <div id="divCapsLock" style="color: Red; display: none;">Caps Lock is ON!</div>
  <script>
    var capsLock = null;
    document.onkeypress = keyPress;
    document.onkeydown = keyDown;
    function keyDown(e) {
      e = e || event;
      capsLock = (e.keyCode == 20 && capsLock !== null) ? !capsLock : capsLock;
      document.getElementById('divCapsLock').style.display = capsLock ? 'block' : 'none';
    }
    function keyPress(e) {
      if (capsLock != null) return;
      e = e || window.event;
      var charCode = e.charCode || e.keyCode;
      capsLock = (charCode >= 97 && charCode <= 122 && e.shiftKey) || (charCode >= 65 && charCode <= 90 && !e.shiftKey);
      document.getElementById('divCapsLock').style.display = capsLock ? 'block' : 'none';
    }
  </script>
</body>
</html>
در متد رویداد kepress تکست باکس ابتدا بررسی میشود که آیا قبلا متغیر گلوبال capsLock مقداری غیرنال دارد. اگر مقدار غیرنال داشته باشد دیگر نیازی نیست تا کد کاراکتر کلید بررسی شود زیرا وضعیت جاری کلید معلوم است. بنابراین در ادامه کار صرفه جویی میشود. دلیل استفاده از رویدادهای سطح docment به جای خود تکست باکس این است تا از کوچکترین فرصتها! برای تعیین وضعیت جاری کلید caps lock استفاده شود. یعنی بتوان پس از فشرده شدن اولین کلید کاراکتری بدون توجه به موقعیت فوکس در صفحه، این وضعیت را از حالت نامشخص خارج کرد.
نکته: در سلسله مراتب رویدادهای اجزای یک document همیشه ابتدا رویدادهای مربوط به عناصر فرزند فراخوانده میشود و رویدادهای مربوط به عناصر document و window در پایان صدا زده میشوند. برای آزمودن این مورد قطعه کد زیر را در مرورگرهای مختلف امتحان کنید:
<html>
<body>
  <div id='divInput'>
    <input id="inputText" type="text" />
  </div>
  <script>
    document.getElementById('inputText').onkeydown = inputKeyDown;
    document.getElementById('divInput').onkeydown = divKeyDown;
    document.onkeydown = documentKeyDown;
    window.onkeydown = windowKeyDown;
    function divKeyDown(e) {
      console.log('divKeyDown');
    }
    function inputKeyDown(e) {
      console.log('inputKeyDown');
    }
    function documentKeyDown(e) {
      console.log('documentKeyDown');
    }
    function windowKeyDown(e) {
      console.log('windowKeyDown');
    }
  </script>
</body>
</html>
نکته: اگر از تگ doctype استفاده نشود (همانطور که در ابتدای این مطلب اشاره شد)، در IE رویدادهای عنصر window فراخوانی نمیشوند.
درهرصورت برای مشخص کردن وضعیت کلید caps lock نیاز است تا کاربر ابتدا کلیدی را بفشارد و در حال حاضر روش و راه حل دیگری وجود ندارد! درضمن اگر صفحه کلیدی غیر از انگلیسی استفاده شود روش فوق جواب نخواهد داد و باید بررسی‌های بیشتری انجام شود. البته در مورد زیانهایی چون فارسی که روشن یا خاموش بودن caps lock تاثیری در کاراکتر حاصله ندارد کاری نمیتوان کرد و نمیتوان از وضعیت caps lock باخبر شد! هرچند در این موارد معمولا وضعیت این دکمه مهم نیست زیرا بیشترین کاربرد این گونه هشدارها در ورودی‌های رمز عبور است در صورتیکه زبان صفحه کلید انگلیسی (یا مشابه آن) باشد. برای اینکار کد زیر به عنوان راه حل بهتر پیشنهاد میشود:
<html>
<body>
  <input id="inputText" type="text" />
  <div id="divCapsLock" style="color: Red; display: none;">Caps Lock is ON!</div>
  <script>
    var capsLock = null;
    var hasFocus = false;
    document.onkeyup = keyUp;
    document.onkeypress = keyPress;
    document.getElementById('inputText').onfocus = focus;
    document.getElementById('inputText').onblur = focus;
    function warnCapsLock() {
      document.getElementById('divCapsLock').style.display = (capsLock != null && capsLock && hasFocus) ? 'block' : 'none';
    }
    function focus() {
      hasFocus = !hasFocus;
      warnCapsLock();
    }
    function keyUp(e) {
      e = e || event;
      capsLock = (e.keyCode == 20 && capsLock !== null) ? !capsLock : capsLock;
      warnCapsLock();
    }
    function keyPress(e) {
      if (capsLock != null) return;
      e = e || window.event;
      var charCode = e.charCode || e.keyCode;
      capsLock = (charCode >= 97 && charCode <= 122 && e.shiftKey) || (charCode >= 65 && charCode <= 90 && !e.shiftKey);
      warnCapsLock();
    }
  </script>
</body>
</html>
اگر دقت کنین میبیند که رویداد keydown در این کد نهایی با keyup جایگزین شده است. درصورت استفاده از رویداد keypress یک مشکل کوچک بوجود می‌آید و آن این است که اگر کاربر کلید caps lock را فشرده و سپس آن را در حالت فشرده نگه دارد اتفاق بدی خواهد افتاد! در این حال چون رویداد keydown مرتبا فراخوانده میشود وضعیت متغیر capsLock در این کد کاملا نامعتبر خواهد بود. بنابراین بهتر است تا از رویداد keyup که تنها یکبار فراخوانده میشود استفاده شود.
نکته: اگر کاربر پس از مشخص شدن وضعیت کلید Capa Lock در این کد، فوکس را به پنجره دیگری تغییر داده و سپس وضعیت این دکمه در آنجا تغییر دهد این کد دیگر درست کار نخواهد کرد. برای حل این مشکل هم راه حل زیر وجود دارد:
window.onblur = function () { capsLock = null; }
با این کار وضعیت متغیر capsLock ریست میشود.
نکته: در آزمایش این کد دقت کنید که زبان کیبرد حتما انگلیسی باشد.

مدیریت کلیدها
برای مدیریت کلیدهای کیبرد راههای متنوعی وجود دارد. مثلا میتوان از قطعه کد زیر استفاده کرد:
var Client = {};
Client.Keyboard = {};
Client.Keyboard.EnableKeyDown = true;
Client.Keyboard.EnableKeyUp = false;
Client.Keyboard.CurrentKeyEvent = null;

window.onkeydown = function (event) {
  if (!Client.Keyboard.EnableKeyDown) return true;
  return KeyboardEvents(event);
};

window.onkeyup = function (event) {
  if (!Client.Keyboard.EnableKeyUp) return true;
  return KeyboardEvents(event);
};

function Rise(event) {
  var e = Client.Keyboard.CurrentKeyEvent;
  if (event) {
    var data = { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey };
    if (!event(data)) return false;
    return event(data);
  }
  return true;
}

function KeyboardEvents(e) {
  e = e || window.event;
  Client.Keyboard.CurrentKeyEvent = e;
  switch (e.keyCode) {
    case 8:
      return Rise(Client.Keyboard.Backspace);
    case 9:
      return Rise(Client.Keyboard.Tab);
    case 13:
      return Rise(Client.Keyboard.Enter);
    case 16:
      return Rise(Client.Keyboard.Shift);
    case 17:
      return Rise(Client.Keyboard.Ctrl);
    case 18:
      return Rise(Client.Keyboard.Alt);
    case 19:
      return Rise(Client.Keyboard.Pause);
    case 20:
      return Rise(Client.Keyboard.CapsLock);
    case 27:
      return Rise(Client.Keyboard.Esc);
    case 33:
      return Rise(Client.Keyboard.PageUp);
    case 34:
      return Rise(Client.Keyboard.PageDown);
    case 35:
      return Rise(Client.Keyboard.End);
    case 36:
      return Rise(Client.Keyboard.Home);
    case 37:
      return Rise(Client.Keyboard.Left);
    case 38:
      return Rise(Client.Keyboard.Up);
    case 39:
      return Rise(Client.Keyboard.Right);
    case 40:
      return Rise(Client.Keyboard.Down);
    case 44:
      return Rise(Client.Keyboard.PrtScr);
    case 45:
      return Rise(Client.Keyboard.Insert);
    case 46:
      return Rise(Client.Keyboard.Delete);
      //////////////////////////////////////////////////////////////////////////////////////////////////
    case 48:
      return Rise(Client.Keyboard.Num0);
    case 49:
      return Rise(Client.Keyboard.Num1);
    case 50:
      return Rise(Client.Keyboard.Num2);
    case 51:
      return Rise(Client.Keyboard.Num3);
    case 52:
      return Rise(Client.Keyboard.Num4);
    case 53:
      return Rise(Client.Keyboard.Num5);
    case 54:
      return Rise(Client.Keyboard.Num6);
    case 55:
      return Rise(Client.Keyboard.Num7);
    case 56:
      return Rise(Client.Keyboard.Num8);
    case 57:
      return Rise(Client.Keyboard.Num9);
      //////////////////////////////////////////////////////////////////////////////////////////////////
    case 65:
      return Rise(Client.Keyboard.A);
    case 66:
      return Rise(Client.Keyboard.B);
    case 67:
      return Rise(Client.Keyboard.C);
    case 68:
      return Rise(Client.Keyboard.D);
    case 69:
      return Rise(Client.Keyboard.E);
    case 70:
      return Rise(Client.Keyboard.F);
    case 71:
      return Rise(Client.Keyboard.G);
    case 72:
      return Rise(Client.Keyboard.H);
    case 73:
      return Rise(Client.Keyboard.I);
    case 74:
      return Rise(Client.Keyboard.J);
    case 75:
      return Rise(Client.Keyboard.K);
    case 76:
      return Rise(Client.Keyboard.L);
    case 77:
      return Rise(Client.Keyboard.M);
    case 78:
      return Rise(Client.Keyboard.N);
    case 79:
      return Rise(Client.Keyboard.O);
    case 80:
      return Rise(Client.Keyboard.P);
    case 81:
      return Rise(Client.Keyboard.Q);
    case 82:
      return Rise(Client.Keyboard.R);
    case 83:
      return Rise(Client.Keyboard.S);
    case 84:
      return Rise(Client.Keyboard.T);
    case 85:
      return Rise(Client.Keyboard.U);
    case 86:
      return Rise(Client.Keyboard.V);
    case 87:
      return Rise(Client.Keyboard.W);
    case 88:
      return Rise(Client.Keyboard.X);
    case 89:
      return Rise(Client.Keyboard.Y);
    case 90:
      return Rise(Client.Keyboard.Z);
      ////////////////////////////////////////////////////////////////////////////
    case 91:
      //case 219: // opera
      return Rise(Client.Keyboard.LeftWindow);
    case 92:
      return Rise(Client.Keyboard.RightWindow);
    case 93:
      return Rise(Client.Keyboard.ContextMenu);
      ////////////////////////////////////////////////////////////////////////////
    case 96:
      return Rise(Client.Keyboard.NumPad0);
    case 97:
      return Rise(Client.Keyboard.NumPad1);
    case 98:
      return Rise(Client.Keyboard.NumPad2);
    case 99:
      return Rise(Client.Keyboard.NumPad3);
    case 100:
      return Rise(Client.Keyboard.NumPad4);
    case 101:
      return Rise(Client.Keyboard.NumPad5);
    case 102:
      return Rise(Client.Keyboard.NumPad6);
    case 103:
      return Rise(Client.Keyboard.NumPad7);
    case 104:
      return Rise(Client.Keyboard.NumPad8);
    case 105:
      return Rise(Client.Keyboard.NumPad9);
      ////////////////////////////////////////////////////////////////////////////
    case 106:
      return Rise(Client.Keyboard.Multiply);
    case 107:
      return Rise(Client.Keyboard.Add);
    case 109:
      return Rise(Client.Keyboard.Subtract);
    case 110:
      return Rise(Client.Keyboard.DecimalPoint);
    case 111:
      return Rise(Client.Keyboard.Divide);
      ////////////////////////////////////////////////////////////////////////////
    case 112:
      return Rise(Client.Keyboard.F1);
    case 113:
      return Rise(Client.Keyboard.F2);
    case 114:
      return Rise(Client.Keyboard.F3);
    case 115:
      return Rise(Client.Keyboard.F4);
    case 116:
      return Rise(Client.Keyboard.F5);
    case 117:
      return Rise(Client.Keyboard.F6);
    case 118:
      return Rise(Client.Keyboard.F7);
    case 119:
      return Rise(Client.Keyboard.F8);
    case 120:
      return Rise(Client.Keyboard.F9);
    case 121:
      return Rise(Client.Keyboard.F10);
    case 122:
      return Rise(Client.Keyboard.F11);
    case 123:
      return Rise(Client.Keyboard.F12);
      ////////////////////////////////////////////////////////////////////////////
    case 144:
      return Rise(Client.Keyboard.NumLock);
    case 145:
      return Rise(Client.Keyboard.ScrollLock);
    case 186:
    case 59: // opera & firefox
      return Rise(Client.Keyboard.SemiColon);
    case 187:
    case 61: // opera
      //case 107: //firefox
      return Rise(Client.Keyboard.Equal);
    case 188:
      return Rise(Client.Keyboard.Camma);
    case 189:
      return Rise(Client.Keyboard.Dash);
    case 190:
      return Rise(Client.Keyboard.Period);
    case 191:
      return Rise(Client.Keyboard.Slash);
    case 192:
      return Rise(Client.Keyboard.GraveAccent);
    case 219:
      return Rise(Client.Keyboard.OpenBracket);
    case 220:
      return Rise(Client.Keyboard.BackSlash);
    case 221:
      return Rise(Client.Keyboard.CloseBracket);
    case 222:
      return Rise(Client.Keyboard.SingleQuote);
  }
};
نحوه استفاده از این کد به صورت زیر است:
  // ctrl + s
  Client.Keyboard.S = function (e) {
    if (e.ctrl) {
      // انجام عملیات موردنظر
    }
    return true;
  }
اگر در متد الصاق شده به پراپرتیهای Client.Keyboard هیچ مقداری برگشت داده نشود، باتوجه به کد موجود در متد Rise، عملیات پیشفرض کلید مربوطه در پنجره مرورگر غیرفعال خواهد شد. بنابراین در کد بالا بعد از شرط، مقدار true برگشت داده شده است.
هرچند سعی کردم که تفاوت میان مرورگرهای مختلف را درنظر بگیرم ولی احتمال اینکه برخی از کدها به دلیل تفاوت میان مرورگرهای مختلف در کد بالا آورده نشده باشد وجود دارد!
نکته: کد فوق بیشتر برای روشن‌تر شدن موضوع ارائه شده است چون راه حل‌های بهتری هم برای مدیریت کلیدهای کیبرد وجود دارد. مثل استفاده از کتابخانه های Mousetrap یا HotKey یا jQuery Hotkeys یا KeyboardJS. البته در این میان بهترین کتابخانه موجود به نظر من همان Mousetrap است که تنها کتابخانه موجود است که علاوه بر پشتیبانی از ترکیب کلیدها با کلیدهای کنترلی، از توالی کلیدها (keys sequence) نیز پشتیبانی میکند که کار جالبی است.
در پایان این نکته را یادآور میشوم که امروزه توسعه اپلیکیشنهای تحت وب بدون استفاده مناسب از امکانات سمت کلاینت طرفداری ندارد. پس بهتر است هرچه بیشتر در مورد این زبان مرموز و پر از رمز و راز (JavaScript) بدانیم.
نظرات مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
یک نکته‌ی تکمیلی: نحوه‌ی ارسال Anti forgery token توسط Action Link ای‌جکسی 

برای اینکار نیاز است متد Ajax begin آن‌را تکمیل کرد:
<a data-ajax="true" data-ajax-begin="onBegin"
در این حالت امضای متد onBegin به صورت ذیل خواهد بود:
<script type=text/javascript>
    function onBegin(xhr, settings) {
        var token = $('input[name=__RequestVerificationToken]').val();
        settings.data = settings.data + '&__RequestVerificationToken=' + token;
    }
</script>