مطالب
مدیریت کلیدهای کیبرد در جاوا اسکریپت
با پیشرفت بسترهای موجود در زمینه شبکه و اینترنت، گرایش به استفاده از اپلیکیشنهای تحت وب روز به روز بیشتر میشود. با گسترش این برنامه‌ها نیازها و درنتیجه ابزارهای موجود توسعه پیدا می‌کنند. درحال حاضر ابزارها و نیز محیطهای توسعه مختلفی برای تولید این اپلیکیشنها وجود دارد. به دلیل نوع رابط کاربری موجود در این برنامه‌ها (اکثراً مرورگرهای وب مثل اینترنت اکسپلورر، گوگل کروم، فایرفاکس و ...) استفاده از زبانهای سمت کلاینت (مثل جاوا اسکریپت که در تمامی مرورگرهای مدرن پشتیبانی کاملی از آن میشود) جایگاه ویژه ای در این نوع برنامه‌ها دارد. درضمن وقتی صحبت از اپلیکیشن به میان می‌آید استفاده از کلیدهای میانبر کیبرد برای راحتی کار کاربران کاربرد ویژه ای دارد. اما متاسفانه زبان جاوا اسکریپت به دلیل محدودیتهایی منطقی موجود، پشتیبانی مناسبی از رویدادهای کیبرد ندارد و مشکل تفاوتها و تناقضات میان سخت افزارها، سیستم عامل‌ها و مرورگرها هم به این مسئله بیشتر دامن میزند. در مطلب جاری هدف این است تا آشنایی مقدماتی با این مبحث فراهم شود.
رویدادهای کیبرد
در جاوا اسکریپت سه رویداد زیر برای کلیدهای کیبرد وجود دارد (به ترتیب زمان رخ دادن):
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) بدانیم.
مطالب
ASP.NET MVC #6

آشنایی با انواع ActionResult

در قسمت چهارم، اولین متد یا اکشنی که به صورت خودکار توسط VS.NET به برنامه اضافه شد، اینچنین بود:

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}

توضیحات تکمیلی مرتبط با خروجی از نوع ActionResult ایی را که مشاهده می‌کنید، در این قسمت ارائه خواهد شد.
رفتار یک کنترلر توسط متدهایی که در آن کلاس تعریف می‌شوند، مشخص می‌گردد. هر متد هم از طریق یک URL مجزا قابل دسترسی و فراخوانی خواهد بود. این متدها که به آن‌ها اکشن نیز گفته می‌شود باید عمومی بوده، استاتیک یا متد الحاقی (extension method) نباشند و همچنین دارای پارامترهایی از نوع ref و out نیز نباشند.
هر درخواست رسیده، به یک کنترلر و متدی عمومی در آن توسط سیستم مسیریابی، نگاشت خواهد شد. اگر علاقمند باشید که در یک کلاس کنترلر، متدی عمومی را از این سیستم خارج کنید، تنها کافی است آن‌را با ویژگی (attribute) به نام NonAction مزین کنید:

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
[NonAction]
public string ShowData()
{
return "Text";
}

public ActionResult Index()
{
ViewBag.Message = string.Format("{0}/{1}/{2}",
RouteData.Values["controller"],
RouteData.Values["action"],
RouteData.Values["id"]);
return View();
}

public ActionResult Search(string data = "*")
{
// do something ...
return View();
}
}
}

چند نکته در این مثال قابل ذکر است:
الف) در اینجا اگر شخصی آدرس http://localhost/home/showdata را درخواست نماید، با توجه به استفاده از ویژگی NonAction، با پیغام یافت نشد یا 404 مواجه می‌گردد.
ب) صرفنظر از پارامترهای یک متد و ساختار کلاس جاری، اطلاعات مسیریابی از طریق شیء RouteData.Values نیز در دسترس هستند که نمونه‌ای از آن‌را در اینجا بر اساس مقادیر پیش فرض تعاریف مسیریابی یک پروژه ASP.NET MVC ملاحظه می‌نمائید.
ج) در متد Search، از قابلیت امکان تعریف مقداری پیش فرض جهت آرگومان‌ها در سی شارپ 4 استفاده شده است. به این ترتیب اگر شخصی آدرس http://localhost/home/search را وارد کند، چون پارامتری را ذکر نکرده است، به صورت خودکار از مقدار پیش فرض آرگومان data استفاده می‌گردد.


انواع Action Results در ASP.NET MVC

در ASP.NET MVC بجای استفاده مستقیم از شیء Response، از شیء ActionResult جهت ارائه خروجی یک متد استفاده می‌شود و مهم‌ترین دلیل آن هم مشکل بودن نوشتن آزمون‌های واحد برای شیء Response است که وهله سازی آن مساوی است با به کار اندازی موتور ASP.NET و Http Runtime آن توسط یک وب سرور (بنابراین در ASP.NET MVC سعی کنید شیء Response را فراموش کنید).
سلسه مراتب ActionResult‌های قابل استفاده در ASP.NET در تصویر زیر مشخص شده‌اند:


و در مثال زیر تقریبا انواع و اقسام ActionResult‌های مهم و کاربردی ASP.NET MVC را می‌توانید مشاهده کنید:

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
public class ActionResultsController : Controller
{
//http://localhost/actionresults/welcome
public string Welcome()
{
return "Hello, World";
}

//http://localhost/actionresults/index
public ActionResult Index() // or ContentResult
{
return Content("Hello, World");
}

//http://localhost/actionresults/SendMail
public void SendMail()
{
}

public ActionResult SendMailCompleted() // or EmptyResult
{
// do whatever
return new EmptyResult();
}

public ActionResult GetFile() // or FilePathResult
{
return File(Server.MapPath("~/content/site.css"), "text/css", "mySite.css");
}

public ActionResult UnauthorizedStatus() // or HttpStatusCodeResult/HttpUnauthorizedResult
{
return new HttpUnauthorizedResult("You need to login first.");
}

public ActionResult Status() // or HttpStatusCodeResult
{
return new HttpStatusCodeResult(501, "Server Error");
}

public ActionResult GetJavaScript() // or JavaScriptResult
{
return JavaScript("...JavaScript...");
}

public ActionResult GetJson() // or JsonResult
{
var obj = new { prop1 = 1, prop2 = "data" };
return Json(obj, JsonRequestBehavior.AllowGet);
}

public ActionResult RedirectTo() // or RedirectResult
{
return RedirectPermanent("http://www.site.com");
//return RedirectToAction("Home", "Index");
}

public ActionResult ShowView() // or ViewResult
{
return View();
}
}
}

چند نکته در این مثال وجود دارد:
1) مثلا متد GetJavaScript را درنظر بگیرید. در این متد خاص، چه بنویسید public ActionResult GetJavaScript یا بنویسید public JavaScriptResult GetJavaScript تفاوتی نمی‌کند. در سایر موارد هم به همین ترتیب است. علت را در تصویر سلسله مراتبی ActionResult‌ها می‌توان جستجو کرد. تمام این کلاس‌ها نوعی ActionResult هستند و از یک کلاس پایه به ارث رسیده‌اند.
2) مثلا ContentResult شبیه به همان Response.Write سابق ASP.NET عمل می‌کند. علت وجودی آن هم عدم وابستگی مستقیم به شیء Response و ساده‌تر سازی نوشتن آزمون‌های واحد برای این نوع اکشن متدها است.
3) منهای متد آخری که نمایش داده شده (ShowView)، هیچکدام از متدهای دیگر نیازی به View متناظر ندارند. یعنی نیازی نیست تا روی متد کلیک راست کرده و Add view را انتخاب کنیم. چون در همین متد کنترلر، کار Response به پایان می‌رسد و مرحله بعدی ندارد. مثلا در حالت return File، یک فایل به درون مرورگر کاربر Flush خواهد شد و تمام.
4) متد Welcome و متد Index در اینجا به یک صورت تفسیر می‌شوند. به این معنا که اگر خروجی متد تعریف شده در یک کنترلر از نوع ActionResult نباشد، به صورت پیش فرض درون یک ContentResult محصور خواهد شد.
5) اگر خروجی متدی در اینجا از نوع void باشد، با ActionResult ایی به نام EmptyResult یکسان خواهد بود. بنابراین با متدهای SendMail و SendMailCompleted به یک نحو رفتار می‌گردد.
6) return Json یاد شده که خروجی‌اش از نوع JsonResultاست در پیاده سازی‌های Ajax ایی کاربرد دارد.
7) جهت بازگرداندن حالت وضعیت 403 یا غیرمجاز می‌توان از return new HttpUnauthorizedResult استفاده کرد.
8) یا جهت اعلام مشکلی در سمت سرور به کمک return new HttpStatusCodeResultکد ویژه‌ای را می‌توان به کاربر نمایش داد.
9) به کمک return RedirectToAction می‌توان به یک کنترلر و متدی خاص در آن، کاربر را هدایت کرد.

و خلاصه اینکه تمام کارهایی را که پیشتر در ASP.NET Web forms ، مستقیما به کمک شیء Response انجام می‌دادید (Response.Write، Response.End، Response.Redirect و غیره)، اینبار به کمک یکی از ActionResult‌های یاد شده انجام دهید تا بتوان بدون نیاز به راه اندازی یک وب سرور، برای متدهای کنترلرها آزمون واحد نوشت. برای مثال:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var controller = new ActionResultsController();

    // Act
    var result = controller.Index() as ContentResult;

    // Assert
    Assert.NotNull(result);
    Assert.AreEqual( "Hello, World", result.Content);
}



مطالب
C# 7 - Tuple return types and deconstruction
روش‌های زیادی برای بازگشت چندین مقدار از یک متد وجود دارند؛ مانند استفاده‌ی از آرایه‌ها برای بازگشت اشیایی از یک جنس، ایجاد یک کلاس سفارشی با خواص متفاوت و استفاده از پارامترهای out و ref همانند روش‌های متداول در C و ++C. در این بین روش دیگری نیز به نام Tuples از زمان NET 4.0. برای بازگشت چندین شیء با نوع‌های مختلف، ارائه شده‌است که در C# 7 نحوه‌ی تعریف و استفاده‌ی از آن‌ها بهبود قابل ملاحظه‌ای یافته‌است.


Tuple چیست؟

هدف از کار با Tupleها، عدم تعریف یک کلاس جدید به همراه خواص آن، جهت بازگشت بیش از یک مقدار از یک متد، توسط وهله‌ای از این کلاس جدید می‌باشد. برای مثال اگر بخواهیم از متدی، دو مقدار شهر و ناحیه را بازگشت دهیم، یک روش آن، ایجاد کلاس مکان زیر است:
public class Location   
{ 
     public string City { get; set; } 
     public string State { get; set; } 
 
     public Location(string city, string state) 
     { 
           City = city; 
           State = state; 
     } 
}
و سپس، وهله سازی و بازگشت آن:
 var location = new Location("Lake Charles","LA");
اما توسط Tuples، بدون نیاز به تعریف یک کلاس جدید، باز هم می‌توان به همین دو خروجی، دسترسی یافت:
 var location = new Tuple<string,string>("Lake Charles","LA");   
// Print out the address
var address = $"{location.Item1}, {location.Item2}";


مشکلات نوع Tuple در نگارش‌های قبلی دات نت

هرچند Tuples از زمان دات نت 4 در دسترس هستند، اما دارای این کمبودها و مشکلات می‌باشند:
static Tuple<int, string, string> GetHumanData()
{
   return Tuple.Create(10, "Marcus", "Miller");
}
الف) پارامترهای خروجی آن‌ها ثابت و با نام‌هایی مانند Item1، Item2 و امثال آن هستند که در حین استفاده، به علت ضعف نامگذاری، کاربرد آن‌ها دقیقا مشخص نیست و کاملا بی‌معنا هستند:
 var data = GetHumanData();
Console.WriteLine("What is this value {0} or this {1}",  data.Item1, data.Item3);
ب) Reference Type هستند (کلاس هستند) و در زمان وهله سازی، میزان مصرف حافظه‌ی بیشتری را نسبت به Value Types (معادل Tuples در C# 7) دارند.
ج) Tuples در دات نت 4، صرفا یک کتابخانه‌ی اضافه شده‌ی به فریم ورک بوده و زبان‌های دات نتی، پشتیبانی توکاری را از آن‌ها جهت بهبود و یا ساده سازی تعریف آن‌ها، ارائه نمی‌دهند.


ایجاد Tuples در C# 7

برای ایجاد Tuples در سی شارپ 7، از پرانتزها به همراه ذکر نام و نوع پارامترها استفاده می‌شود.
(int x1, string s1) = (3, "one");
Console.WriteLine($"{x1} {s1}");
در مثال فوق، یک Tuple ایجاد شده‌است و در آن مقدار 3 به x1 و مقدار "one" به s1 انتساب داده شده‌اند. به این عملیات deconstruction هم می‌گویند.
دسترسی به این مقادیر نیز همانند متغیرهای معمولی است.

اگر سعی کنیم این قطعه کد را کامپایل نمائیم، با خطای ذیل متوقف خواهیم شد:
 error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
برای رفع این مشکل نیاز است بسته‌ی نیوگت ذیل را نیز نصب کرد:
 PM> install-package System.ValueTuple

تعاریف متغیرهای بازگشتی، خارج از پرانتزها هم می‌توانند صورت گیرند:
int x2;
string s2;
(x2, s2) = (42, "two");
Console.WriteLine($"{x2} {s2}");


بازگشت Tuples از متدها

متد ذیل، دو خروجی نتیجه و باقیمانده‌ی تقسیم دو عدد صحیح را باز می‌گرداند:
static (int, int) Divide(int x, int y)
{
   int result = x / y;
   int reminder = x % y;
 
   return (result, reminder);
}
برای این منظور، نوع خروجی متد به صورت (int, int) و همچنین مقدار بازگشتی نیز به صورت یک Tuple از نتیجه و باقیمانده‌ی تقسیم، تعریف شده‌است.
در ادامه نحوه‌ی استفاده‌ی از این متد را مشاهده می‌کنید:
 (int result, int reminder) = Divide(11, 3);
Console.WriteLine($"{result} {reminder}");

در اینجا امکان استفاده‌ی از var نیز برای تعریف نوع متغیرهای دریافتی از یک Tuple نیز وجود دارد و کامپایلر به صورت خودکار نوع آن‌ها را بر اساس نوع خروجی tuple مشخص می‌کند:
 (var result1, var reminder1) = Divide(11, 3);
Console.WriteLine($"{result1} {reminder1}");
و یا حتی چون نوع var پارامترها در اینجا یکی است و در هر دو حالت به int اشاره می‌کند، می‌توان این var را در خارج از پرانتز هم قرار داد:
 var (result1, reminder1) = Divide(11, 3);

و یا برای نمونه متد GetHumanData دات نت 4 ابتدای بحث را به صورت ذیل می‌توان در C# 7 بازنویسی کرد:
static (int, string, string) GetHumanData()
{
   return (10, "Marcus", "Miller");
}
و سپس به نحو واضح‌تری از آن استفاده نمود؛ بدون استفاده‌ی اجباری از Item1 و غیره (هرچند هنوز هم می‌توان از آن‌ها استفاده کرد):
 (int Age, string FirstName, string LastName) results = GetHumanData();
Console.WriteLine(results.Age);
Console.WriteLine(results.FirstName);
Console.WriteLine(results.LastName);


پشت صحنه‌ی Tuples در C# 7

همانطور که عنوان شد، برای اینکه بتوانید قطعه کدهای فوق را کامپایل کنید، نیاز به بسته‌ی نیوگت System.ValueTuple است. در حقیقت کامپایلر خروجی متد فوق را به نحو ذیل تفسیر می‌کند:
 ValueTuple<int, int> tuple1 = Divide(11, 3);
برای مثال قطعه کد
 (int, int) n = (1,1);
System.Console.WriteLine(n.Item1);
توسط کامپایلر به قطعه کد ذیل ترجمه می‌شود:
 ValueTuple<int, int> n = new ValueTuple<int, int>(1, 1);
System.Console.WriteLine(n.Item1);
- برخلاف نگارش‌های پیشین دات نت که Tuples در آن‌ها reference type بودند، این ValueTuple یک struct است و به همین جهت سربار تخصیص حافظه‌ی کمتری را به همراه داشته و از لحاظ کارآیی و میزان مصرف حافظه بهینه‌تر عمل می‌کند.
- همچنین در اینجا محدودیتی از لحاظ تعداد پارامترهای ذکر شده‌ی در یک Tuple وجود ندارد.
 (int,int,int,int,int,int,int,(int,int))
در اینجا هم مانند قبل (دات نت 4) 8 آیتم را می‌توان تعریف کرد؛ اما چون آخرین آیتم ValueTuple تعریف شده نیز یک Tuple است، در عمل محدودیتی از نظر تعداد پارامتر نخواهیم داشت.


مفهوم Tuple Literals

همانند نگارش‌های پیشین دات نت، خروجی یک Tuple را می‌توان به یک متغیر از نوع var و یا ValueType نیز نسبت داد:
 var tuple2 = ("Stephanie", 7);
Console.WriteLine($"{tuple2.Item1}, {tuple2.Item2}");
در این حالت برای دسترسی به مقادیر Tuple همانند قبل باید از فیلدهای Item1 و Item2 و ... استفاده کرد.
به علاوه در سی شارپ 7  می‌توان برای اعضای یک Tuple نام نیز تعریف کرد که به آن‌ها Tuple literals گویند:
 var tuple3 = (Name: "Matthias", Age: 6);
Console.WriteLine($"{tuple3.Name} {tuple3.Age}");
در این حالت زمانیکه Tuple به یک متغیر از نوع var نسبت داده می‌شود، می‌توان به خروجی آن بر اساس نام‌های اعضای Tuple، بجای ذکر Item1 و ... دسترسی یافت که خوانایی بیشتری دارند.

و یا هنگام تعریف نوع خروجی، می‌توان نام پارامترهای متناظر را نیز ذکر کرد که به آن named elements هم می‌گویند:
static (int radius, double area) CalculateAreaOfCircle(int radius)
{
   return (radius, Math.PI * Math.Pow(radius, 2));
}
و نمونه‌ای از کاربرد آن به صورت ذیل است که در اینجا خروجی Tuple صرفا به یک متغیر از نوع var نسبت داده شده‌است و توسط نام پارامترهای خروجی متد، می‌توان به اعضای Tuple دسترسی یافت.
 var circle = CalculateAreaOfCircle(2);
Console.WriteLine($"A circle of radius, {circle.radius}," +
 $" has an area of {circle.area:N2}.");


مفهوم Deconstructing Tuples

مفهوم deconstruction که در ابتدای بحث عنوان شد صرفا مختص به Tuples نیست. در C# 7 می‌توان مشخص کرد که چگونه یک نوع خاص، به اجزای آن تجزیه شود. برای مثال کلاس شخص ذیل را درنظر بگیرید:
class Person
{
    private readonly string _firstName;
    private readonly string _lastName;
 
    public Person(string firstname, string lastname)
    {
        _firstName = firstname;
        _lastName = lastname;
    }
 
    public override String ToString() => $"{_firstName} {_lastName}";
 
    public void Deconstruct(out string firstname, out string lastname)
    {
        firstname = _firstName;
        lastname = _lastName;
    }
}
- در اینجا یک متد جدید را به نام Deconstruct مشاهده می‌کنید. کار این متد جدید که توسط کامپایلر استفاده خواهد شد، ارائه‌ی روشی است برای «تجزیه‌ی» یک نوع، به یک Tuple‌. متد Deconstruct تعریف شده‌ی در اینجا توسط پارامترهایی از نوع out، دو خروجی را مشخص می‌کنند. امکان تعریف این متد ویژه، به صورتیکه یک Tuple را بازگرداند، وجود ندارد.
- علت تعریف این دو خروجی هم به constructor و یا سازنده‌ی کلاس بر می‌گردد که دو ورودی را دریافت می‌کند. اگر یک کلاس چندین سازنده داشته باشد، به همان تعداد می‌توان متد Deconstruct تعریف کرد؛ به همراه خروجی‌هایی متناظر با نوع پارامترهای سازنده‌ها.
- علت استفاده‌ی از نوع خروجی out نیز این است که در #C نمی‌توان چندین overload را صرفا بر اساس نوع خروجی‌های متفاوت متدها تعریف کرد.
- متد Deconstruct به صورت خودکار در زمان تجزیه‌ی یک شیء به یک tuple فراخوانی می‌شود. در مثال زیر، شیء p1 به یک Tuple تجزیه شده‌است و این تجزیه بر اساس متد Deconstruct این کلاس مفهوم پیدا می‌کند:
 var p1 = new Person("Katharina", "Nagel");
(string first, string last) = p1;
Console.WriteLine($"{first} {last}");


امکان تعریف متد Deconstruct‌، به صورت یک متد الحاقی

روش اول تعریف متد ویژه‌ی Deconstruct را در مثال قبل، در داخل کلاس اصلی مشاهده کردید. روش دیگر آن، استفاده‌ی از متدهای الحاقی است که در این مورد خاص نیز مجاز است:
public class Rectangle
{
    public Rectangle(int height, int width)
    {
        Height = height;
        Width = width;
    }
 
    public int Width { get; }
    public int Height { get; }
}
 
public static class RectangleExtensions
{
    public static void Deconstruct(this Rectangle rectangle, out int height, out int width)
    {
        height = rectangle.Height;
        width = rectangle.Width;
    }
}
در اینجا کلاس مستطیل دارای سازنده‌ای با دو پارامتر است؛ اما متد Deconstruct آن به صورت یک متد الحاقی، خارج از کلاس اصلی تعریف شده‌است.
اکنون امکان انتساب وهله‌ای از این کلاس به یک Tuple وجود دارد:
 var r1 = new Rectangle(100, 200);
(int height, int width) = r1;
Console.WriteLine($"height: {height}, width: {width}");


امکان جایگزین کردن Anonymous types با Tuples

قطعه کد ذیل را در نظر بگیرید:
List<Employee> allEmployees = new List<Employee>()
{
  new Employee { ID = 1L, Name = "Fred", Salary = 50000M },
  new Employee { ID = 2L, Name = "Sally", Salary = 60000M },
  new Employee { ID = 3L, Name = "George", Salary = 70000M }
};
var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  select new { EmpName = oneEmployee.Name,
               Income = oneEmployee.Salary };
در اینجا خروجی LINQ تهیه شده یک لیست anonymously typed است؛ با محدودیت‌هایی مانند عدم امکان استفاده‌ی از خروجی آن در سایر اسمبلی‌ها. این نوع‌های ویژه تنها محدود هستند به همان اسمبلی که در آن تعریف می‌شوند. اما در C# 7 می‌توان قطعه کد فوق را با Tuples به صورت ذیل بازنویسی کرد که این محدودیت‌ها را هم ندارد (با هدف به حداقل رساندن تعداد ViewModel‌های تعریفی یک برنامه):
var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  orderby oneEmployee.Salary descending
  select (EmpName: oneEmployee.Name,
          Income: oneEmployee.Salary);
var highestPaid = wellPaid.First().EmpName;


سایر کاربردهای Tuples

از Tuples صرفا برای تعریف چندین خروجی از یک متد استفاده نمی‌شود. در ذیل نحوه‌ی استفاده‌ی از آن‌ها را جهت تعریف کلید ترکیبی یک شیء دیکشنری و یا استفاده‌ی از آن‌ها را در آرگومان جنریک یک متد async هم مشاهده می‌کنید:
public Task<(int index, T item)> FindAsync<T>(IEnumerable<T> input, Predicate<T> match)
{
   var dictionary = new Dictionary<(int, int), string>();
   throw new NotSupportedException();
}
نظرات مطالب
استفاده از reCAPTCHA در ASP.NET
این مورد بیشتر به تداخل css کلی سایت با این کنترل مربوط می‌شود. بهترین کار استفاده از firebug است. ابتدا در برگه net آن بررسی کنید پیام 404 ایی مشاهده می‌شود؟ ممکن است چیزی از قلم افتاده باشد. سپس به وسیله firebug به صورت live شروع به ویرایش css المان انتخاب شده کنید تا به نتیجه برسید.

مطالب
React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1
برای کار با React، نیاز است با ES6 آشنایی داشته باشید که در این سایت، یک سری کامل بررسی مقدمات آن‌را پیشتر مرور کرده‌ایم. علاوه بر توصیه‌ی مطالعه‌ی این سری (اینکار الزامی است)، در این قسمت خلاصه‌ی بسیار سریع و کاربردی آن‌را که بیشتر در برنامه‌های مبتنی بر React مورد استفاده قرار می‌گیرند، با هم مرور خواهیم کرد. در قسمت‌های بعدی، اهمیت ذکر این خلاصه بیشتر مشخص می‌شود.

برای بررسی ویژگی‌های جاوا اسکریپت مدرن، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-02
> cd sample-02
> npm start
سپس تمام کدهای داخل index.js را نیز حذف می‌کنیم. اکنون تمام کدهای خالص جاوا اسکریپتی خود را داخل این فایل خواهیم نوشت.
به علاوه چون در این قسمت خروجی UI نخواهیم داشت، تمام خروجی را در کنسول developer tools مرورگر خود می‌توانید مشاهده کنید (فشردن دکمه‌ی F12).


var، let و const

در اکثر زبان‌های برنامه نویسی، متغیرها در محدوده‌ی دید قطعه کدی که تعریف شده‌اند (scope)، قابل دسترسی هستند. برای نمونه محتوای فایل index.js پروژه را به صورت زیر تغییر داده و با فرض اجرای دستور npm start، خروجی آن‌را می‌توان در کنسول مرورگر مشاهده کرد.
function sayHello() {
  for (var i = 0; i < 5; i++) {
    console.log(i);
  }
  console.log(i);
}

sayHello();
در این مثال متغیر i، مخصوص قطعه کد حلقه، تعریف شده‌است. بنابراین به ظاهر نباید خارج از این حلقه نیز قابل دسترسی باشد. اما خروجی آن به صورت زیر است:


در آخرین پیمایش حلقه، i مساوی 5 شده و از حلقه خارج می‌شود. اما چون در اینجا برای تعریف متغیر از واژه‌ی کلیدی var استفاده شده‌است، محدوده‌ی دید آن به کل تابعی که در آن تعریف شده‌است، بسط پیدا می‌کند. به همین جهت در این خروجی، عدد 5 را نیز مشاهده می‌کند که حاصل دسترسی به i، خارج از حلقه‌است.
برای یک دست سازی این رفتار با سایر زبان‌های برنامه نویسی، در ES6، واژه‌ی کلیدی جدیدی به نام let تعریف شده‌است که میدان دید متغیر را به قطعه کدی که در آن تعریف شده‌است، محدود می‌کند. اکنون اگر در حلقه‌ی فوق بجای var از let استفاده شود، یک چنین خطایی در مرورگر ظاهر خواهد شد که عنوان می‌کند، i استفاده شده‌ی در خارج از حلقه، تعریف نشده‌است.
./src/index.js
  Line 14:15:  'i' is not defined  no-undef

Search for the keywords to learn more about each error.

علاوه بر let، واژه‌ی کلیدی جدید const نیز به ES6 اضافه شده‌است که از آن برای تعریف ثوابت استفاده می‌شود. constها نیز همانند let، میدان دید محدود شده‌ای به قطعه کد تعریف شده‌ی در آن دارند؛ اما قابلیت انتساب مجدد را ندارند:
 const x = 1;
x = 2; // Attempting to override 'x' which is a constant.
اگر یک چنین قطعه کدی را اجرا کنیم، خطای x is const را در مرورگر می‌توان مشاهده کرد.

به صورت خلاصه از این پس واژه‌ی کلیدی var را فراموش کنید. همیشه با const جهت تعریف متغیرها شروع کنید. اگر به خطا برخوردید و نیاز به انتساب مجدد وجود داشت، آن‌را به let تغییر دهید. بنابراین استفاده از const همیشه نسبت به let ارجحیت دارد.


اشیاء در جاوا اسکریپت

اشیاء در جاوا اسکریپت به صورت مجموعه‌ای از key/value‌ها تعریف می‌شوند:
const person = {
  name: "User 1",
  walk: function() {}, // method
  talk() {} // concise method
};
در اینجا امکان تعریف یک تابع نیز وجود دارد که چون درون یک شیء قرار می‌گیرد، اینبار «متد» نامیده می‌شود. همچنین در ES6 می‌توان این متدها را به صورت معمولی، مانند متد talk نیز تعریف کرد که به آن‌ها concise method می‌گویند. بنابراین نحوه‌ی تعریف فوق را به نحو زیر نیز می‌توان خلاصه کرد:
const person = {
  name: "User 1",
  walk() {},
  talk() {}
};
پس از تعریف این شیء، روش دسترسی به اجزای آن به صورت زیر است:
person.talk();
person.name = "User 3";
person["name"] = "User 2";
به دو مورد اول، روش dot notation می‌گویند که از همان ابتدا دقیقا مشخص است کدامیک از خواص و متدهای شیء تعریف شده، مورد استفاده قرار می‌گیرند.
مورد آخر همان روش استفاده از key/valueها است که اساس اشیاء جاوا اسکریپتی را تشکیل می‌دهد. البته از این روش فقط زمانی استفاده کنید که قرار است یکسری کار پویا صورت گیرند (مقدار key به صورت متغیر دریافت شود) و از ابتدا مشخص نیست که کدام خاصیت یا متد قرار است تعریف و استفاده شود:
const targetMember = "name";
person[targetMember] = "User 2";


واژه‌ی کلیدی this در جاوا اسکریپت

از واژه‌ی کلیدی this، در قسمت‌های بعدی زیاد استفاده خواهیم کرد. به همین جهت نیاز است تفاوت‌های اساسی آن‌را با سایر زبان‌های برنامه نویسی بررسی کنیم.
همان شیء person را که پیشتر تعریف کردیم درنظر بگیرید. در متد walk آن، مقدار this را لاگ می‌کنیم:
const person = {
  name: "User 1",
  walk() {
    console.log(this);
  },
  talk() {}
};

person.walk();
خروجی این قطعه، به صورت زیر است:


شیء this در جاوا اسکریپت، همانند سایر زبان‌های برنامه نویسی مانند سی‌شارپ و یا جاوا رفتار نمی‌کند. در سایر زبان‌های نامبرده شده، this همواره ارجاعی را به وهله‌ای از شیء جاری، باز می‌گرداند؛ دقیقا همانند تصویری که در بالا مشاهده می‌کنید. در اینجا نیز this جاوا اسکریپتی لاگ شده، ارجاعی را به وهله‌ی جاری شیء person، بازگشت داده‌است. اما مشکل اینجا است که this در جاوا اسکریپت، همیشه به این صورت رفتار نمی‌کند!
برای نمونه در ادامه یک ثابت را به نام walk تعریف کرده و آن‌را به person.walk مقدار دهی می‌کنیم:
const walk = person.walk;
console.log(walk);
دقت داشته باشید که در اینجا از () استفاده نشده‌است (متد walk اجرا نشده‌است). یعنی صرفا «ارجاعی» از متد walk شیء person را به ثابت walk نسبت داده‌ایم. بنابراین اکنون ثابت walk نیز یک function است که حاصل console.log آن به صورت زیر است:


سؤال: اکنون اگر این function را با فراخوانی ()walk اجرا کنیم، چه خروجی را می‌توان مشاهده کرد؟


اینبار this لاگ شده، به شیء person اشاره نمی‌کند و شیء استاندارد window مرورگر را بازگشت داده‌است!
اگر یک function به صورت متدی از یک شیء فراخوانی شود، مقدار this همواره اشاره‌گری به وهله‌ای از آن شیء خواهد بود. اما اگر این تابع به صورت متکی به خود و به صورت یک function و نه متد یک شیء، فراخوانی شود، اینبار this، شیء سراسری جاوا اسکریپت یا همان شیء window را بازگشت می‌دهد.

یک نکته: اگر strict mode جاوا اسکریپت را در پروژه‌ی جاری فعال کنیم، بجای شیء window، مقدار undefined را در خروجی فوق شاهد خواهیم بود.


اتصال مجدد this به شیء اصلی در جاوا اسکریپت

تا اینجا دریافتیم که اگر یک function را به صورت متکی به خود و نه جزئی از یک شیء فراخوانی کنیم، شیء this در این حالت به شیء window سراسری مرورگر اشاره می‌کند و اگر strict mode فعال باشد، فقط undefined را بازگشت می‌هد. اکنون می‌خواهیم بررسی کنیم که چگونه می‌توان این مشکل را برطرف کرد؛ یعنی صرف‌نظر از نحوه‌ی فراخوانی متدها یا تابع‌ها، this همواره ارجاعی را به شیء person بازگشت دهد.
در جاوا اسکریپت، تابع‌ها نیز شیء هستند. برای مثال person.walk نوشته شده نیز یک شیء است. برای اثبات ساده‌ی آن فقط یک دات را پس از person.walk قرار دهید:


همانطور که مشاهده می‌کنید، شیء person.walk مانند تمام اشیاء دیگر جاوا اسکریپت، به همراه متد bind نیز هست. کار آن، انقیاد یک تابع، به یک شیء است. یعنی هرچیزی را که به عنوان آرگومان آن، به آن ارسال کنیم، به عنوان مقدار شیء this درنظر می‌گیرد:
const walk2 = person.walk.bind(person);
console.log(walk2);
walk2();
در اینجا متد bind، یک وهله‌ی جدید از person.walk را بازگشت می‌دهد که در آن شیء person را به عنوان شیء this، تنظیم کرده‌است. به همین جهت اینبار فراخوانی walk2 که به شیء person متصل شده‌است، به this صحیحی بجای window سراسری اشاره می‌کند. از این روش در برنامه‌های مبتنی بر React زیاد استفاده می‌شود.


Arrow functions

تابع زیر را درنظر بگیرید که به یک ثابت انتساب داده شده‌است:
const square = function(number) {
  return number * number;
};
در ES6، روش ساده‌تر و تمیزتری برای این نوع تعاریف، ذیل ویژگی جدید Arrow functions اضافه شده‌است. برای تبدیل قطعه کد فوق به یک arrow function، ابتدا واژه‌ی کلیدی function را حذف می‌کنیم. سپس بین پارامتر تابع و {}، یک علامت <= (که به آن fat arrow هم می‌گویند!) قرار می‌دهیم:
const square2 = (number) => {
  return number * number;
};
اگر مانند اینجا تنها یک تک پارامتر وجود داشته باشد، می‌توان پرانتزهای ذکر شده را نیز حذف کرد:
const square2 = number => {
  return number * number;
};
و اگر این متد پارامتری نداشت، از () استفاده می‌شود.
در ادامه اگر بدنه‌ی این تابع، فقط حاوی یک return بود، می‌توان آن‌را به صورت زیر نیز خلاصه کرد (در اینجا {} به همراه واژه‌ی کلیدی return حذف می‌شوند):
const square3 = number => number * number;
console.log(square3(5));
این یک سطر ساده شده، دقیقا معادل اولین const square ای است که نوشتیم. نحوه‌ی فراخوانی آن نیز مانند قبل است.

اکنون مثال مفید دیگری را در مورد Arrow functions بررسی می‌کنیم که بیشتر شبیه به عبارات LINQ در #C است:
const jobs = [
  { id: 1, isActive: true },
  { id: 2, isActive: true },
  { id: 3, isActive: true },
  { id: 4, isActive: true },
  { id: 5, isActive: false }
];
در اینجا آرایه‌ای از اشیاء job را مشاهده می‌کنید که مورد آخر آن، فعال نیست. اکنون می‌خواهیم لیست کارهای فعال را گزارشگیری کنیم:
var activeJobs = jobs.filter(function(job) {
  return job.isActive;
});
متد filter در جاوا اسکریپت، بر روی تک تک عناصر آرایه‌ی jobs حرکت می‌کند. سپس هر job را به پارامتر متد ارسالی آن که predicate نام دارد، جهت دریافت یک خروجی true و یا false، ارائه می‌دهد. اگر خروجی این متد true باشد، آن job انتخاب خواهد شد و در لیست نهایی گزارش، ظاهر می‌شود.
در ادامه می‌توان این تابع را توسط arrow functions به صورت ساده‌تر زیر نیز نوشت:
var activeJobs2 = jobs.filter(job => job.isActive);
ابتدا واژه‌ی کلیدی function را حذف می‌کنیم. سپس چون یک تک پارامتر را دریافت می‌کند، نیازی به ذکر پرانتزهای آن نیز نیست. در ادامه چون یک تک return را داریم، { return }  را با یک <= جایگزین خواهیم کرد. در اینجا نیازی به ذکر سمی‌کالن انتهای return هم نیست. نوشتن یک چنین کدی تمیزتر و خواندن آن، ساده‌تر است.


ارتباط بین arrow functions و شیء this

نکته‌ی مهمی را که باید در مورد arrow functions دانست این است که شیء this را rebind نمی‌کنند (rebind: مقدار دهی مجدد؛ ریست کردن).
در مثال زیر، ابتدا شیء user با متد talk که در آن شیء this، لاگ شده، ایجاد شده و سپس این متد فراخوانی گردیده‌است:
const user = {
  name: "User 1",
  talk() {
    console.log(this);
  }
};
user.talk();
همانطور که انتظار می‌رود، this ای که در اینجا لاگ می‌شود، دقیقا ارجاعی را به وهله‌ی جاری شیء user دارد.
اکنون اگر متد لاگ کردن را داخل یک تایمر قرار دهیم چه اتفاقی رخ می‌دهد؟
const user = {
  name: "User 1",
  talk() {
    setTimeout(function() {
      console.log(this);
    }, 1000);
  }
};
user.talk();
متد setTimeout، متدی را که به عنوان آرگومان اول آن دریافت کرده، پس از 1 ثانیه اجرا می‌کند.
در این حالت خروجی console.log، مجددا همان شیء سراسری window مرورگر است و دیگر به وهله‌ی جاری شیء user اشاره نمی‌کند. علت اینجا است که پارامتر اول متد setTimeout که یک callback function نام دارد، جزئی از هیچ شیءای نیست. بنابراین دیگر مانند فراخوانی متد ()user.talk در مثال قبلی کار نمی‌کند؛ چون متکی به خود است. هر زمان که یک متد متکی به خود غیر وابسته‌ی به یک شیء را اجرا کنیم، به صورت پیش‌فرض this آن، به شیء window مرورگر اشاره می‌کند.

سؤال: چگونه می‌توان درون یک callback function متکی به خود، به this همان شیء user جاری دسترسی یافت؟
یک روش حل این مساله، ذخیره this شیء user در یک متغیر و سپس ارسال آن به متد متکی به خود setTimeout است:
const user2 = {
  name: "User 2",
  talk() {
    var self = this;
    setTimeout(function() {
      console.log(self);
    }, 1000);
  }
};
user2.talk();
این روشی است که سال‌ها است وجود دارد؛ با ارائه‌ی arrow functions، دیگر نیازی به اینکار نیست و می‌توان از روش زیر استفاده کرد:
const user3 = {
  name: "User 3",
  talk() {
    setTimeout(() => console.log(this), 1000);
  }
};
user3.talk();
در اینجا callback function را تبدیل به یک arrow function کرده‌ایم و چون arrow functions شیء this را rebind نمی‌کنند، یعنی شیء this را به ارث می‌برند. بنابراین console.log مثال فوق، دقیقا به this شیء user اشاره می‌کند و دوباره آن‌را مقدار دهی مجدد نمی‌کند و از همان نمونه‌ی موجود قطعه کد تعریف شده، استفاده خواهد کرد.


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

در قسمت بعد نیز بررسی پیشنیازهای جاوا اسکریپتی شروع به کار با React را ادامه خواهیم داد.
مطالب
CoffeeScript #8

اصطلاحات عمومی CoffeeScript

Includes

برای چک کردن وجود یک مقدار در یک آرایه به طور معمول از indexOf استفاده می‌شود؛ در حالی که تمامی نسخه‌های IE به طور کامل از آن پشتیبانی نمی‌کنند.

var included = (array.indexOf("test") != -1)
CoffeeScript برای حل این مشکل، کلمه‌ی کلیدی in را ارائه کرده است:
included = "test" in array
متاسفانه برای چک کردن یک کلمه در یک متن می‌بایست از indexOf استفاده کرد و از کلمه‌ی کلیدی in نمی‌توان استفاده کرد. همچنین در صورتیکه بخواهید نبود چیزی را چک کنید نیز باید از indexOf استفاده کنید.
included = "a long test string".indexOf("test") isnt -1
و روش بهتر بجای مقایسه با مقدار 1-، استفاده از هک‌های اپراتور بیتی است:
string   = "a long test string"
included = !~ string.indexOf "test"

تکرار Propertyها

در صورتی که به خصوصیات یک شیء چندین بار نیاز داشته باشید، در جاوااسکریپت باید از کلمه‌ی کلیدی in استفاده کنید:

var object = {one: 1, two: 2}
for(var key in object) alert(key + " = " + object[key])
در حالیکه برای پیاده سازی توسط CoffeeScript باید از کلمه‌ی کلیدی of استفاده کرد:
object = {one: 1, two: 2}
alert("#{key} = #{value}") for key, value of object
نتیجه‌ی کامپایل آن می‌شود:
var key, object, value;

object = {
  one: 1,
  two: 2
};

for (key in object) {
  value = object[key];
  alert(key + " = " + value);
}
همانطور که در مثال بالا مشاهده می‌کنید، شما می‌توانید برای دسترسی به کلید و مقدار خصوصیات موجود در شیء، متغیری را برای هر کدام تعریف کنید که ما در اینجا از key  و value استفاده کرده‌ایم.

Min/Max

درست است که این تکنیک مخصوص CoffeeScript نیست، اما اشاره به آن می‌تواند مفید باشد. تابع Math.max و Max.min می‌توانند چندین آرگومان یا یک آرایه را به عنوان ورودی گرفته و بر روی آن محاسبات خود را انجام داده و خروجی را نشان دهند:

Math.max [14, 35, -7, 46, 98]... # 98
Math.min [14, 35, -7, 46, 98]... # -7
نتیجه‌ی آن پس از کامپایل می‌شود:
Math.max.apply(Math, [14, 35, -7, 46, 98]);

Math.min.apply(Math, [14, 35, -7, 46, 98]);
نکته: در صورتیکه آرگومان‌ها یا تعداد خانه‌های آرایه ارسالی زیاد باشند، چون مرورگرها محدودیتی را در تعداد پارامترهای ارسالی به یک تابع دارند، خروجی تولید نخواهد شد.

نظرات مطالب
پایان پروژه ASP.NET Ajax Control Toolkit !
مایکروسافت ASP.NET Ajax را با اون عظمت و زحمتی که براش کشیده بودند، در مقابل jQuery بازنده اعلام کرده الان شما صحبت از Anthem.Net می‌کنید که فقط منحصر است به ASP.NET web forms آن هم نگارش‌های قبل از سه آن + پشتیبانی از مرورگرهای مختلف هم در آن ضعیف است.
هدف مایکروسافت از اینکار مدیریت هر دو پروژه MVC و Web forms است آن هم با هزینه کم، کیفیت بالا و سازگار با تمام مرورگرها.
ضمنا این رو در نظر باشید که یکی از توانمندی‌های jQuery، Ajax است (از کار با DOM گرفته تا دستکاری CSS نمایش داده شده، تا Animation ، مدیریت ساده‌تر رخدادها، اعمال قالب به سایت و غیره) و این مورد مزیت مهمی است نسبت به تمام کتابخانه‌هایی که فقط برای یک کار و آن هم سهولت تولید برنامه‌های مبتنی بر Ajax ایجاد شده‌اند و از چند مشکل مهم رنج می‌برند:
- تک کاره‌اند. فقط Ajax .
- مشکل سازگاری با مرورگرهای مختلف را دارند.
- به صورت فعال توسعه داده نمی‌شوند؛ رفع باگ نمی‌شوند و غیره. برای مثال فواصل به روز رسانی همان Anthem.Net را بررسی کنید.
- توسعه پذیر نیستند. برای مثال آیا می‌توان برای Anthem.Net افزونه نوشت؟
- حجم بالایی دارند.
- سرعت پایینی دارند.

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

و RAD هزینه بر است. یعنی چی؟ یعنی حجم بالای کدهای اسکریپتی که باید به برنامه‌ی شما مثلا توسط ASP.NET Ajax تزریق شود که مبادا شما بخواهید دست خودتان را به نوشتن چند سطر کد جاوا اسکریپتی آلوده کنید. همچنین حجم تبادل اطلاعات ASP.NET Ajax را هم که مبتنی است بر RAD را هم با حجم اطلاعات مبادله شده توسط jQuery مقایسه کنید (با پلاگین فایرباگ مربوط به فایرفاکس). این حجم واقعا زیاد است و قابل مقایسه نیست. (تمام این‌ها هزینه‌های RAD است)
ضمنا وجود 100 ها افزونه و پلاگین نوشته شده برای jQuery کار شما را بسیار بسیار ساده می‌کنند، مانند پاسخ قبلی من در این مطلب.
فقط باید کمی وقت بگذارید و چیزی را بیاموزید که واقعا ارزش دارد. گیرم فردا نخواستید با ASP.NET کار کنید. تمام این اطلاعات در PHP هم به درد شما می‌خورد، چون قسمت سمت کلاینت آنچنان تفاوتی نمی‌کند و jQuery یک کتابخانه‌ی سمت کلاینت است.
مطالب
معرفی Kendo UI
Kendo UI چیست؟
Kendo UI یک فریم ورک جاوا اسکریپتی ساخت برنامه‌های مدرن و تعاملی وب است و برای رسیدن به این مقصود، از JavaScript، CSS 3، HTML 5 و jQuery کمک می‌گیرد.


امکانات فراهم شده توسط Kendo UI
1) انواع و اقسام ویجت‌ها: کنترل‌های وب تهیه شده برفراز jQuery
ویجت‌های آن در سه گروه کلی قرار می‌گیرند:
- گروه وب، مانند گرید، tree-view و غیره.
- گروه DataViz که جهت نمایش بصری اطلاعات و ترسیم انواع و اقسام نمودارها کاربرد دارد.
- گروه موبایل که با استفاده از فناوری adaptive rendering، در سیستم عامل‌های مختلف موبایل، مانند اندروید و آی او اس، ظاهری بومی و هماهنگ با آن‌ها را ارائه می‌دهد.

2) منبع داده سمت کاربر (Client side data source)
منبع داده سمت کاربر Kendo UI، از انواع و اقسام منابع داده محلی مانند آرایه‌های جاوا اسکریپتی تا منابع داده راه دور، مانند JSON، XML و JSONP، جهت نمایش اطلاعات و data binding پشتیبانی می‌کند. این منبع داده، مواردی مانند صفحه بندی، مرتب سازی اطلاعات و گروه بندی آن‌ها را نیز فراهم می‌کند. به علاوه با عملیات ثبت، ویرایش و حذف اطلاعات نیز هماهنگی کاملی را دارد.

3) به همراه یک فریم ورک MVVM توکار است
این فریم ورک MVVM مواردی مانند two way data binding و همچنین declarative binding را نیز پشتیبانی می‌کند.

4) امکان تعویض قالب
5) پویا نمایی، کشیدن و رها کردن
6) فریم ورک اعتبارسنجی


چرا Kendo UI؟
-  مهم‌ترین مزیت کار با Kendo UI، فراهم آوردن تمام نیازهای توسعه‌ی یک برنامه‌ی مدرن وب، تنها در یک بسته است. به این ترتیب دیگر نیازی نیست تا گرید را از یک‌جا، tree-view را از جایی دیگر و کتابخانه‌های رسم نمودار را از منبعی ناهمگون با سایر عناصر برنامه دریافت و استفاده کنید؛ در اینجا تمام این‌ها در قالب یک بسته‌ی آماده برای شما فراهم شده‌است و همچنین با یکدیگر سازگاری کاملی دارند.
- تمام ویجت‌های آن برای نمایش سریع با کارآیی بالا طراحی شده‌اند.
- پشتیبانی خوب آن. این فریم ورک محصول شرکتی است که به صورت تخصصی کار تهیه کامپوننت‌های وب و دسکتاپ را انجام می‌دهد.


مرورگرهای پشتیبانی شده
یکی دیگر از مزایای مهم کار با Kendo UI پشتیبانی گسترده‌ی آن از اکثر مرورگرهای موجود است. این فریم ورک با مرورگرهای زیر سازگار است:
- IE 7 به بعد
- فایرفاکس 10 به بعد
- تمام نگارش‌های کروم
- اپرا 10 به بعد
- سفاری 4 به بعد


مجوز استفاده از Kendo UI
Kendo UI با سه مجوز ذیل ارائه می‌شود:
- 30 روزه آزمایشی رایگان
- تجاری
- سورس باز با مجوز Apache

پیشتر نسخه‌ی تجاری آن تحت مجوز GPL نیز در دسترس بود. اما اخیرا مجوز GPL آن حذف شده و به Apache تغییر یافته است. اما باید در نظر داشت که نسخه‌ی سورس باز آن شامل کنترل‌های مهمی مانند «گرید» نیست و این موارد تنها در نسخه‌ی تجاری آن لحاظ شده‌اند.


مثال‌های Kendo UI
پس از دریافت بسته‌ی کامل آن، پوشه‌هایی مانند js، styles و امثال آن قابل مشاهده هستند؛ به همراه پوشه‌ی examples آن که حداقل 86 پوشه‌ی دیگر در آن جهت ارائه مثال‌هایی از نحوه‌ی کاربرد المان‌های مختلف آن تدارک دیده شده‌اند.


نحوه‌ی افزودن Kendo UI به صفحه
از آنجائیکه Kendo UI یک فریم ورک جاوا اسکریپتی است، همانند سایر برنامه‌های وب، افزودن تعاریف فایل‌های js، css و تصاویر مرتبط با آن، برای شروع به کار کفایت می‌کند. برای این منظور ابتدا پوشه‌های js و styles بسته‌ی دریافتی آن‌را به برنامه‌ی خود اضافه کنید (این پوشه‌ها در فایل پیوست انتهای بحث موجود هستند).
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>

    <!--KendoUI: Web-->
    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.web.min.js" type="text/javascript"></script>

    <!--KendoUI: DataViz-->
    <link href="styles/kendo.dataviz.min.css" rel="stylesheet" type="text/css" />
    <script src="js/kendo.dataviz.min.js" type="text/javascript"></script>

    <!--KendoUI: Mobile-->
    <link href="styles/kendo.mobile.all.min.css" rel="stylesheet" type="text/css" />
    <script src="js/kendo.mobile.min.js" type="text/javascript"></script>

    <script type="text/javascript">
        $(function() {
            $("#pickDate").kendoDatePicker();
        });
    </script>
</head>
<body>
    <span>
        Pick a date: <input id="pickDate" type="text"/>
    </span>
</body>
</html>
در اینجا یک مثال ساده‌ی استفاده از date picker کندو یو آی را ملاحظه می‌کنید. در قسمت head صفحه، نحوه‌ی ثبت سه گروه اسکریپت و شیوه نامه، مشخص شده‌اند. اگر نیاز به کامپوننت‌های وب آن‌را دارید باید اجزایی مانند kendo.common.min.css، kendo.default.min.css، jquery.min.js و kendo.web.min.js به صفحه اضافه شوند. اگر نیاز به رسم نمودار هست، فایل‌ها kendo.dataviz.min.css و kendo.dataviz.min.js باید تعریف شوند و برای فعال سازی اجزای موبایل آن فایل‌های kendo.mobile.all.min.css و kendo.mobile.min.js نیاز است به صفحه پیوست شوند. در هر سه حالت ذکر jquery.min.js الزامی است.

دریافت سورس کامل این قسمت که حاوی فایل‌های اصلی kendoui.professional.2014.2.1008 نیز می‌باشد:
KendoUI01.7z
نظرات مطالب
کنترل عمومی فایل‌های آپلودی در ASP.NET MVC
معادل این مطلب در برنامه‌های ASP.NET Core

ابتدا بسته‌ی نیوگت DNTCommon.Web.Core را نصب کنید:
PM> Install-Package DNTCommon.Web.Core
سپس مثالی از UploadFileExtensions و AllowUploadSafeFiles آن‌را در اینجا می‌توانید مشاهده کنید.