مطالب
آموزش 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 با JavaScript خالص - قسمت پنجم - درخواست‌های Ajax
AJAX و یا «Asynchronous JavaScript and XML» قابلیتی است که توسط web API جاوا اسکریپتی مرورگرها برای دریافت و یا به روز رسانی اطلاعات، بدون بارگذاری مجدد و کامل صفحه، ارائه می‌شود. این قابلیت اولین بار در سال 1999 توسط مایکروسافت با ارائه‌ی مرورگر IE 5.0 و معرفی شیء XMLHTTP که توسط یک ActiveX control ارائه می‌شد، میسر گردید و این روزها توسط استاندارد XMLHttpRequest در تمام مرورگرها قابل استفاده است. این استاندارد نیز مدتی است که توسط Fetch API مرورگرهای جدید جایگزین شده‌است (پس از 15 سال از ارائه‌ی استاندارد XMLHttpRequest) و در این لحظه در تمام مرورگرهای غالب وب پشتیبانی می‌شود.
در ادامه روش‌های مختلف ارسال درخواست‌های Ajax را توسط jQuery و همچنین معادل‌های XMLHttpRequest و Fetch API آن‌ها بررسی می‌کنیم.


ارسال درخواست‌های GET

توسط استاندارد جدید Fetch API 
  توسط XMLHttpRequest استاندارد  توسط jQuery 
 fetch('/my/name').then(function(response) {
  if (response.ok) {
   return response.text();
  }
  else {
   throw new Error();
  }
 }).then(
  function success(name) {
  console.log('my name is ' + name);
  },
  function failure() {
  console.error('Name request failed!');
  }
 );
 var xhr = new XMLHttpRequest();
  xhr.open('GET', '/my/name');
 xhr.onload = function() {
  if (xhr.status >= 400) {
   console.error('Name request failed!');
  }
  else {
   console.log('my name is ' + xhr.responseText);
  }
 };
 xhr.onerror = function() {
  console.error('Name request failed!');
 };
 xhr.send();
 $.get('/my/name').then(
  function success(name) {
  console.log('my name is ' + name);
  },
  function failure() {
  console.error('Name request failed!');
  }
 );

jQuery برای پشتیبانی از درخواست‌های Ajax، متد ویژه‌ای را به نام ()ajax ارائه می‌کند که برای ارسال درخواست‌هایی از هر نوع، مانندGET، POST و غیره کاربرد دارد. همچنین برای بعضی از نوع‌ها، متدهای کوتاه‌تری را مانند ()get و ()post نیز در اختیار برنامه نویس قرار می‌دهد. جاوا اسکریپت خالص و Web API مرورگرها نیز دو شیء XMLHttpRequest و fetch را برای ارسال درخواست‌های غیرهمزمان، ارائه می‌کند. XMLHttpRequest در تمام مرورگرهای قدیمی و جدید پشتیبانی می‌شود، اما fetch API مدتی است که در غالب مرورگرهای امروزی در دسترس است. در جدول فوق روش ارسال درخواست‌های Ajax از نوع GET را توسط این سه روش مشاهده می‌کنید.
در این مثال‌ها درخواستی به آدرس my/name سمت سرور ارسال شده و انتظار می‌رود که یک plaintext حاوی متن کاربر بازگشت داده شود که در نهایت در console لاگ می‌شود.
- در حالت استفاده‌ی از jQuery در صورت بازگشت موفقیت آمیز پاسخی از طرف سرور، متد success و در غیراینصورت متد failure اجرا می‌شود. باید درنظر داشت که متد ajax جی‌کوئری، چیزی بیشتر از یک محصور کننده‌ی اشیاء XMLHttpRequest نیست.
- در حالت کار با XMLHttpRequest باید اندکی بیشتر تایپ کرد؛ اما اصول کار یکی است. در اینجا onload زمانی فراخوانی می‌شود که پاسخی از سرور دریافت شده و عملیات خاتمه یافته‌است؛ هرچند این پاسخ می‌تواند یک خطا نیز باشد. به همین جهت باید status آن‌را بررسی کرد. اگر رخ‌داد onerror فراخوانی شد، یعنی درخواست، در سطوح بسیار پایین آن مانند بروز یک خطای CORS با شکست مواجه شده‌است.
همانطور که مشاهده می‌کنید در حالت کار با XMLHttpRequest جاوا اسکریپت از اشیاء Promise پشتیبانی نمی‌کند که این کمبود با معرفی fetch API برطرف شده‌است که نمونه‌ای از آن‌را با متد then متصل به fetch مشاهده می‌کنید؛ دقیقا مشابه متد ajax جی‌کوئری که آن نیز یک Promise را بازگشت می‌دهد.
تفاوت Promise جی‌کوئری با fetch API در این است که جی‌کوئری در صورتیکه یک status code بیانگر خطا را دریافت کند، قسمت failure را اجرا می‌کند؛ اما fetch API مانند اشیاء XMLHttpRequest تنها در صورت بروز خطاهای سطح پایین درخواست، این متد را فراخوانی خواهد کرد. هرچند اگر در اینجا response.ok نبود، می‌توان با صدور یک استثناء به رفتاری شبیه به jQuery رسید و قسمت then failure را به صورت خودکار اجرا کرد.


ارسال درخواست‌های Ajax از نوع POST ، PUT و DELETE

در اینجا اطلاعاتی با MIME type از نوع plaintext به سمت سرور ارسال می‌شود. جهت سهولت توضیح و تمرکز بر روی قسمت‌های مهم آن، بخش مدیریت پاسخ آن حذف شده‌است و این مورد دقیقا با مثال قبلی که در مورد درخواست‌های از نوع GET بود، یکی است.
توسط استاندارد جدید Fetch API 
توسط XMLHttpRequest استاندارد 
توسط jQuery 
 fetch('/user/name', {
  method: 'POST',
  body: 'some data'
 });
 var xhr = new XMLHttpRequest();
 xhr.open('POST', '/user/name');
 xhr.send('some data');
 $.ajax({
  method: 'POST',
  url: '/user/name',
  contentType: 'text/plain',
  data: 'some data'
 });
در حالت ارسال اطلاعات به سرور با متد POST، نیاز است contentType متد ajax جی‌کوئری حتما ذکر شود. در غیراینصورت آن‌را به application/x-www-form-urlencoded تنظیم می‌کند که ممکن است الزاما مقداری نباشد که مدنظر ما است. در اینجا بدنه‌ی درخواست به خاصیت data انتساب داده می‌شود.
اگر از شیء XMLHttpRequest استفاده شود، Content-Type آن به صورت پیش‌فرض به text/plain تنظیم شده‌است. در اینجا بدنه‌ی درخواست به متد send ارسال می‌شود.
متد fetch نیز همانند شیء XMLHttpRequest دارای Content-Type پیش‌فرض از نوع text/plain است. در اینجا بدنه‌ی درخواست به خاصیت body انتساب داده می‌شود.

درخواست‌های از نوع POST عموما برای ایجاد رکوردی جدید در سمت سرور مورد استفاده قرار می‌گیرند و از درخواست‌های PUT بیشتر برای به روز رسانی مقادیر موجود یک رکورد کمک گرفته می‌شود. درخواست‌های از نوع PUT نیز دقیقا مانند درخواست‌های از نوع POST در اینجا مدیریت می‌شوند و در هر سه حالت، متد ارسال اطلاعات، به مقدار PUT تنظیم خواهد شد:
توسط استاندارد جدید Fetch API 
توسط XMLHttpRequest استاندارد 
 توسط jQuery 
 fetch('/user/1', {
  method: 'PUT',
  body: //record including new mobile number 
 });
 var xhr = new XMLHttpRequest();
 xhr.open('PUT', '/user/1');
 xhr.send(/* record including new data */);
 $.ajax({
  method: 'PUT',
  url: '/user/1',
  contentType: 'text/plain',
  data: //record including new data
 });
درخواست‌های از نوع DELETE نیز مانند قبل بوده و تنها تفاوت آن، نداشتن بدنه‌ی درخواست است:
 توسط استاندارد جدید Fetch API   توسط XMLHttpRequest استاندارد  توسط jQuery 
 fetch('/user/1', {method: 'DELETE'});
 var xhr = new XMLHttpRequest();
 xhr.open('DELETE', '/user/1');
 xhr.send();
 $.ajax('/user/1', {method: 'DELETE'});


مدیریت Encoding درخواست‌های Ajax

در مثال‌های قبل، اطلاعاتی از نوع text/plain را به سمت سرور ارسال کردیم که به آن encoding type نیز گفته می‌شود. برای تکمیل بحث می‌توان حالت‌های دیگری مانند application/x-www-form-urlencoded، application/json و یا multipart/form-data را که در برنامه‌های کاربردی زیاد مورد استفاده قرار می‌گیرند، بررسی کرد.

کار با URL Encoding

عموما URL encoding در دو قسمت آدرس درخواستی به سرور و یا حتی بدنه‌ی درخواست ارسالی تنظیم می‌شود. MIME type آن نیز application/x-www-form-urlencoded است و اطلاعات آن شامل یکسری جفت کلید/مقدار است. برای متدهای ارسال از نوع GET و DELETE، اطلاعات آن در انتهای آدرس درخواستی و برای سایر حالات در بدنه‌ی درخواست ذکر می‌شوند.
در جی‌کوئری با استفاده از متد param آن می‌توان یک شیء جاوا اسکریپتی را به معادل URL-encoded string آن‌ها تبدیل کرد:
$.param({
  key1: 'some value',
  'key 2': 'another value'
});
با این خروجی encode شده:
 key1=some+value&key+2=another+value
البته باید دقت داشت زمانیکه از متد ajax جی‌کوئری استفاده می‌شود، دیگر نیازی به استفاده‌ی مستقیم از متد param نیست:
 $.ajax({
  method: 'POST',
  url: '/user',
  data: {
  name: 'VahidN',
  address: 'Address 1',
  phone: '555-555-5555'
  }
 });
در اینجا یک شیء جاوا اسکریپتی معمولی به خاصیت data آن نسبت داده شده‌است که در پشت صحنه در حین ارسال به سرور، چون Content-Type پیش‌فرض (و ذکر نشده‌ی در اینجا) دقیقا همان application/x-www-form-urlencoded است، به صورت خودکار تبدیل به یک URL-encoded string می‌شود.

برخلاف جی‌کوئری در حین کار با روش‌های جاوا اسکریپتی خالص این encoding باید به صورت دستی و صریحی انجام شود. برای این منظور دو متد استاندارد encodeURI و encodeURIComponent در جاوا اسکریپت مورد استفاده قرار می‌گیرند. هدف متد encodeURI اعمال آن بر روی یک URL کامل است و یا کلید/مقدارهای جدا شده‌ی توسط & مانند first=Vahid&last=N. اما متد encodeURIComponent صرفا جهت اعمال بر روی یک تک مقدار طراحی شده‌است.
به این ترتیب معادل جاوا اسکریپتی قطعه کد جی‌کوئری فوق به صورت زیر است:
 var xhr = new XMLHttpRequest();
 var data = encodeURI('name=VahidN&address=Address 1&phone=555-555-5555');
 xhr.open('POST', '/user');
 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
 xhr.send(data);
که در آن data توسط encodeURI تبدیل به یک رشته‌ی URL-encoded شده و سپس با ذکر صریح Content-Type به سمت سرور ارسال می‌شود.
روش انجام اینکار توسط fetch API به صورت زیر است:
   var data = encodeURI('name=VahidN&address=Address 1&phone=555-555-5555');
  fetch('/user', {
  method: 'POST',
  headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  body: data
 });
معادل متد param جی‌کوئری با جاوا اسکریپت خالص به صورت زیر است؛ برای تبدیل یک شیء جاوا اسکریپتی به معادل URL-encoded string آن:
function param(object) {
    var encodedString = '';
    for (var prop in object) {
        if (object.hasOwnProperty(prop)) {
            if (encodedString.length > 0) {
                encodedString += '&';
            }
            encodedString += encodeURI(prop + '=' + object[prop]);
        }
    }
    return encodedString;
}


کار با JSON Encoding

در عمل JSON نمایش رشته‌ای یک شیء جاوا اسکریپتی است و هدف آن سهولت نقل و انتقالات این اشیاء به سرور و برعکس است. برخلاف حالت application/x-www-form-urlencoded که اطلاعات آن مسطح است، حالت application/json امکان ارسال اطلاعات سلسله مراتبی را نیز میسر می‌کند (مانند مثال زیر که phone آن دیگر مسطح نیست و خود آن نیز یک شیء جاوا اسکریپتی است).
 در جی‌کوئری برای ارسال اشیاء جاوا اسکریپتی JSON Encoded به سمت سرور از روش زیر استفاده می‌شود:
 $.ajax({
  method: 'POST',
  url: '/user',
  contentType: 'application/json',
  data: JSON.stringify({
   name: 'VahidN',
   address: 'Address 1',
   phone: {
   home: '555-555-5555',
   mobile: '444-444-4444'
  }
  });
 });
در اینجا نسبت به مثال قبلی، ذکر Content-Type ضروری بوده و همچنین data نیز باید به صورت دستی encode شود. برای این منظور می‌توان از متد استاندارد JSON.stringify استفاده کرد که از زمان IE 8.0 به بعد در تمام مرورگرها پشتیبانی می‌شود.
پیاده سازی همین مثال با جاوا اسکریپت خالص و XMLHttpRequest استاندارد به صورت زیر است:
  var xhr = new XMLHttpRequest();
 var data = JSON.stringify({
   name: 'VahidN',
   address: 'Address 1',
   phone: {
    home: '555-555-5555',
    mobile: '444-444-4444'
   }
   });
 xhr.open('POST', '/user');
 xhr.setRequestHeader('Content-Type', 'application/json');
 xhr.send(data);
که در اینجا نیز Content-Type به صورت صریحی ذکر و از متد JSON.stringify برای encode دستی اطلاعات کمک گرفته شده‌است.
در این حالت اگر خروجی سرور نیز JSON باشد، روش دریافت و پردازش آن به صورت زیر است:
 var xhr = new XMLHttpRequest();
 xhr.open('GET', '/user/1');
 xhr.onload = function() {
  var user = JSON.parse(xhr.responseText);
  // do something with this user JavaScript object 
 };
 xhr.send();
رخ‌داد onload، پس از پایان درخواست فراخوانی می‌شود. در اینجا برای دسترسی به response body می‌توان از خاصیت responseText استفاده کرد و سپس توسط متد JSON.parse این رشته را تبدیل به یک شیء جاوا اسکریپتی نمود.
اگر از مرورگر IE صرفنظر کنیم، تمام مرورگرهای دیگر دارای خاصیتی به نام xhr.response نیز هستند که نیاز به تبدیل و Parse دستی رشته‌ی دریافتی را حذف می‌کند؛ از این جهت که این خاصیت حاوی شیء جاوا اسکریپتی معادل بدنه‌ی response دریافتی از سمت سرور است. البته با این شرط که سرور، Content-Type مساوی application/json را برای response تنظیم کرده باشد.
و روش انجام این عملیات توسط fetch API به صورت زیر است:
 fetch('/user', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
   name: 'VahidN',
   address: 'Address 1',
   phone: {
   home: '555-555-5555',
   mobile: '444-444-4444'
  }
  });
 });
که در اینجا نیز هدر Content-Type تنظیم و همچنین از متد JSON.stringify برای تبدیل شیء جاوا اسکریپتی به رشته‌ی معادل، استفاده شده‌است.
و یا اگر بخواهیم اطلاعات JSON دریافتی از سمت سرور را در اینجا پردازش کنیم، روش کار به صورت زیر است:
 fetch('/user/1').then(function(response) {
  return response.json();
  }).then(function(userRecord) {
   // do something with this user JavaScript object 
 });
Fetch API به صورت یک Promise، امکان دسترسی به شیء response را مهیا می‌کند. چون می‌دانیم خروجی آن json است، از متد ()json آن که یک Promise را بازگشت می‌دهد استفاده خواهیم کرد. پس از پایان موفقیت آمیز درخواست، then دوم تعریف شده، اجرا و userRecord ارسالی به آن، همان شیء جاوا اسکریپتی دریافتی از سمت سرور است.
همین مثال را اگر بخواهیم توسط ECMAScript 2016 و Arrow functions آن بازنویسی کنیم، به قطعه کد زیر می‌رسیم:
 fetch('/user/1')
  .then(response => response.json()) // Transform the data into json 
  .then(userRecord => {
  // do something with this userRecord object
  });


کار با Multipart Encoding

نوع دیگری از encoding که بیشتر با فرم‌های HTML بکار می‌رود، multipart/form-data نام دارد:
 <form action="my/server" method="POST" enctype="multipart/form-data">
  <label>First Name:
   <input name="first">
  </label>
 
  <label>Last Name:
   <input name="last">
  </label>
 
  <button>Submit</button>
 </form>
با فعال بودن این نوع encoding، اطلاعات نمونه‌ی فرم فوق به شکل زیر به سمت سرور ارسال می‌شوند:
 -----------------------------1686536745986416462127721994
 Content-Disposition: form-data; name="first"

 Vahid
 -----------------------------1686536745986416462127721994
 Content-Disposition: form-data; name="last"

 N
 -----------------------------1686536745986416462127721994--
از این روش نه فقط برای ارسال اطلاعات کلید/مقدارها و اشیاء جاوا اسکریپتی استفاده می‌شود، بلکه از آن برای ارسال اطلاعات فایل‌های باینری نیز کمک گرفته می‌شود.
روش ارسال اطلاعات با این نوع encoding خاص به سمت سرور توسط متد ajax جی‌کوئری به صورت زیر است:
  var formData = new FormData();
 formData.append('name', 'VahidN');
 formData.append('address', 'Address 1');
 formData.append('phone', '555-555-5555');
 
 $.ajax({
  method: 'POST',
  url: '/user',
  contentType: false,
  processData: false,
  data: formData
 });
همانطور که ملاحظه می‌کنید jQuery روش توکاری را برای انجام اینکار نداشته و باید از FormData جاوا اسکریپت یا همان web API مرورگرها به همراه متد ajax آن استفاده کرد. در این حالت اطلاعات به صورت کلید/مقدارها به شیء استاندارد FormData اضافه شده و سپس به سمت سرور ارسال می‌شوند. باید دقت داشت FormData از نگارش‌های پس از IE 9.0 در دسترس است.
در اینجا ذکر processData: false ضروری است. در غیراینصورت jQuery این اطلاعات را به یک URL-encoded string تبدیل می‌کند. همچنین با اعلام contentType: false، جی‌کوئری در کار مرورگر دخالت نمی‌کند. از این جهت که هدر ویژه‌ی این نوع درخواست‌ها توسط خود مرورگر تنظیم می‌شود و برای مثال یک چنین شکلی را دارد:
 multipart/form-data; boundary=—————————1686536745986416462127721994
این عدد انتهای هدر یک unique ID است که جزئی از اطلاعات multipart بوده و توسط مرورگر به انتهای هدر اصلی multipart/form-data اضافه می‌شود.

روش انجام اینکار با XMLHttpRequest و همچنین fetch API به صورت زیر است:
توسط استاندارد جدید Fetch API 
توسط XMLHttpRequest استاندارد 
 var formData = new FormData();
 formData.append('name', 'VahidN');
 formData.append('address', 'Address 1');
 formData.append('phone', '555-555-5555');

 fetch('/user', {
  method: 'POST',
  body: formData
 });
 var formData = new FormData(),
  xhr = new XMLHttpRequest();

 formData.append('name', 'VahidN');
 formData.append('address', 'Address 1');
 formData.append('phone', '555-555-5555');

 xhr.open('POST', '/user');
 xhr.send(formData);
همانطور که مشاهده می‌کنید قسمت استفاده‌ی از FormData استاندارد در اینجا یکسان است و همچنین نیازی به ذکر هدر و یا اطلاعات اضافه‌تری نیست.


آپلود فایل‌ها توسط درخواست‌های Ajax ایی

تنها راه آپلود فایل‌ها در مرورگرهای قدیمی که شامل IE 9.0 هم می‌شود، تعریف المان <"input type="file> در داخل المان <form> و سپس submit مستقیم آن فرم است. برای رفع این مشکل در مرورگرهای پس از IE 9.0 و پشتیبانی از Ajax، جهت آپلود فایل‌ها، استاندارد XMLHttpRequest Level 2 معرفی شده‌است. در این حالت اگر المان <input type=file> در صفحه وجود داشته باشد، روش ارسال Ajax ایی آن به سمت سرور به صورت زیر است:
توسط استاندارد جدید Fetch API 
 توسط XMLHttpRequest استاندارد  توسط jQuery 
var file = document.querySelector(
      'INPUT[type="file"]').files[0];
fetch('/uploads', {
  method: 'POST',
  body: file
  });
 }
var file = document.querySelector(
    'INPUT[type="file"]').files[0],
xhr = new XMLHttpRequest();
xhr.open('POST', '/uploads');
xhr.send(file);

var file = $('INPUT[type="file"]')[0].files[0]; 
$.ajax({
   method: 'POST',
   url: '/uploads',
   contentType: false,
   processData: false,
   data: file
  });
در اینجا روش آپلود یک تک فایل را به سرور، توسط درخواست‌های Ajax ایی مشاهده می‌کنید و توضیحات contentType: false و processData: false آن مانند قبل است تا jQuery این اطلاعات multipart را پیش از ارسال به سرور دستکاری نکند.
یک نکته: اگر نیاز به آپلود بیش از یک فایل را داشتید و همچنین در اینجا نیاز به اطلاعات دیگری مانند سایر فیلدهای فرم نیز وجود داشت، از همان روش تعریف  new FormData و افزودن اطلاعات مورد نیاز به آن استفاده کنید. امکان افزودن شیء file نیز به FormData پیش بینی شده‌است.


دانلود فایل‌ها توسط درخواست‌های Ajax ایی

پیشتر در حین بررسی JSON encoding توسط fetch API از متد ()json برای تبدیل اطلاعات دریافتی از سرور به json و بازگشت آن به صورت یک Promise استفاده کردیم:
  fetch(url) 
  .then((resp) => resp.json()) // Transform the data into json 
  .then(function(data) { 
    // use data object
    }) 
  })
در اینجا علاوه بر ()json، متدهای استاندارد دیگری نیز پیش بینی شده‌اند که همگی یک Promise را بازگشت می‌دهند:
- ()clone یک کپی از response را تهیه می‌کند.
- ()redirect یک response جدید را با URL دیگری ایجاد می‌کند.
- ()arrayBuffer یک Promise را بازگشت می‌دهد که پس از پایان درخواست، response را به یک شیء ArrayBuffer تبدیل می‌کند.
- ()formData یک Promise را بازگشت می‌دهد که پس از پایان درخواست، response را به یک شیء FormData تبدیل می‌کند.
- ()blob یک Promise را بازگشت می‌دهد که پس از پایان درخواست، response را به یک شیء Blob تبدیل می‌کند.
- ()text یک Promise را بازگشت می‌دهد که پس از پایان درخواست، response را به string تبدیل می‌کند.
- ()json یک Promise را بازگشت می‌دهد که پس از پایان درخواست، response را به یک شیء جاوا اسکریپتی تبدیل می‌کند.

در اینجا متدی که می‌تواند برای تبدیل یک byte array بازگشتی از سرور به فایل قابل دریافت در سمت کلاینت مورد استفاده قرار گیرد، متد blob است:
function downloadBlob()
{
       fetch('/Home/InMemoryReport')
   .then(function(response) {
         return response.blob();
   })
   .then(function(xlsxBlob) {
             var a = document.createElement("a");
             document.body.appendChild(a);
             a.style = "display: none";
             let url = window.URL.createObjectURL(xlsxBlob);
             a.href = url;
             a.download = "report.xlsx";
             a.click();
             window.URL.revokeObjectURL(url);
   });
}
فرض کنید مسیر Home/InMemoryReport یک گزارش PDF و یا اکسل را به صورت byte array بازگشت می‌دهد. اولین then نوشته شده، درخواست تبدیل این byte array را پس از پایان response به یک شیء Blob می‌دهد. پس از پایان درخواست، این Promise اجرا شده و نتیجه‌ی آن به صورت خودکار در اختیار then دوم قرار می‌گیرد. در اینجا همانطور که در قسمت قبل نیز بررسی کردیم، یک المان جدید anchor مخفی را ایجاد کرده و به صفحه اضافه می‌کنیم. سپس url آن‌را به این شیء Blob، توسط متد استاندارد window.URL.createObjectURL تنظیم می‌کنیم. با استفاده از متد URL.createObjectURL می‌توان آدرس موقت محلی را برای دسترسی به آن تولید کرد و یک چنین آدرس‌هایی به صورت blob:http تولید می‌شوند. نام این فایل را هم توسط ویژگی download این شیء می‌توان تنظیم نمود. در نهایت بر روی آن، متد click را فراخوانی می‌کنیم. با اینکار مرورگر این فایل را به صورت یک فایل دریافت شده‌ی متداول در لیست فایل‌های آن قرار می‌دهد. این روش در مورد تدارک دکمه‌ی دریافت تمام blobهای دریافتی از سرور کاربرد دارد.


ارسال درخواست‌های Ajax به دومین‌های دیگر (CORS)

گاهی از اوقات نیاز است اطلاعاتی را توسط درخواست‌های Ajax، به سروری دیگر در دومینی دیگر ارسال و یا دریافت کرد. هرچند انجام اینکار به صورت مستقیم و خارج از مرورگر بدون مشکل قابل انجام است، اما مرورگرها برای درخواست‌های جاوا اسکریپتی محدودیت «same-origin policy» را اعمال می‌کنند. به این معنا که XMLHttpRequest بین دومین‌ها به صورت پیش‌فرض ممنوع است. برای ارسال درخواست‌های مجاز و از پیش مشخص شده‌ی Ajax بین دومین‌ها، تاکنون دو روش پیش بینی شده‌است:
الف) روش JSONP
«same-origin policy» از شروع ارسال درخواستی به خارج از دومین جاری، جلوگیری می‌کند. هرچند این مورد به درخواست‌های XMLHttpRequest اعمال می‌شود، اما در مورد المان‌هایی از نوع <a>، <img>  و  <script> صادق نیست و آن‌ها محدود به این سیاست امنیتی نیستند. روش «JavaScript Object Notation with Padding» و یا به اختصار JSONP از یکی از همین استثناءها جهت ارسال درخواست‌هایی به سایر دومین‌ها استفاده می‌کند. البته نام این روش کمی غلط انداز است؛ از این جهت که در این فرآیند اصلا JSON ایی مورد استفاده قرار نمی‌گیرد؛ خروجی سرور در این حالت یک تابع جاوا اسکریپتی است و نه JSON.
روش انجام این نوع درخواست‌ها را توسط جی‌کوئری در ذیل مشاهده می‌کنید:
 $.ajax('http://jsonp-aware-endpoint.com/user/1', {
  jsonp: 'callback',
  dataType: 'jsonp'
 }).then(function(response) {
  // handle user info from server 
 });
در این حالت jQuery پس از اجرای تابع دریافتی از سرور، نتیجه‌ی آن‌را در قسمت then، در اختیار مصرف کننده قرار می‌دهد.
انجام اینکار بدون jQuery و در حقیقت کاری که jQuery در پشت صحنه برای ایجاد تگ script انجام می‌دهد، چنین چیزی است:
 window.myJsonpCallback = function(data) {
  // handle user info from server 
 };

 var scriptEl = document.createElement('script');
 scriptEl.setAttribute('src',
  'http://jsonp-aware-endpoint.com/user/1?callback=myJsonpCallback');
 document.body.appendChild(scriptEl);
هرچند این روش هنوز هم در بعضی از سایت‌ها مورد استفاده‌است، در کل بهتر است از آن دوری کنید؛ چون ممکن است به مشکلات امنیتی دیگری ختم شود. این روش بیشتر در روزهای آغازین معرفی محدودیت «same-origin policy» بکار گرفته می‌شد (در زمان IE 7.0).


ب) روش CORS

CORS  و یا Cross Origin Resource Sharing روش مدرن و پذیرفته شده‌ی ارسال درخواست‌های Ajax در بین دومین‌ها است و دارای دو نوع ساده و غیرساده است. نوع ساده‌ی آن به همراه هدر مخصوص Origin است که جهت بیان دومین ارسال کننده‌ی درخواست بکار می‌رود و تنها از encodingهای “text/plain”  و  “application/x-www-form-urlencoded”  پشتیبانی می‌کند. نوع غیرساده‌ی آن که این روزها بیشتر بکار می‌رود، از نوع «preflight» است. Preflight در اینجا به این معنا است که زمانیکه درخواست Ajax ایی را به دومین دیگری ارسال کردید، پیش از ارسال، مرورگر یک درخواست از نوع OPTIONS را به سمت سرور مقصد ارسال می‌کند. در این حالت اگر سرور مجوز مناسبی را صادر کرد، آنگاه مرورگر اصل درخواست را به سمت آن سرور ارسال می‌کند. به همین جهت در این حالت به ازای هر درخواستی که در برنامه ارسال می‌شود، در برگه‌ی network مرورگر، دو درخواست را مشاهده خواهید کرد. درخواست preflight از نوع OPTIONS به صورت خودکار توسط مرورگر مدیریت می‌شود و نیازی به کدنویسی خاصی ندارد.


مدیریت کوکی‌ها در درخواست‌های Ajax

اگر درخواست Ajax ایی را به دومین دیگری ارسال کنید، به صورت پیش‌فرض به همراه کوکی‌های مرتبط نخواهد بود. برای رفع این مشکل نیاز است خاصیت withCredentials را به true تنظیم کنید:
توسط استاندارد جدید Fetch API 
توسط XMLHttpRequest استاندارد 
 توسط jQuery 
 fetch('http://someotherdomain.com', {
  method: 'POST',
  headers: {
  'Content-Type': 'text/plain'
  },
  credentials: 'include'
 });
 var xhr = new XMLHttpRequest();
 xhr.open('POST', 'http://someotherdomain.com');
 xhr.withCredentials = true;
 xhr.setRequestHeader('Content-Type', 'text/plain');
 xhr.send('sometext');
 $.ajax('http://someotherdomain.com', {
  method: 'POST',
  contentType: 'text/plain',
  data: 'sometext',
  beforeSend: function(xmlHttpRequest) {
  xmlHttpRequest.withCredentials = true;
  }
 });
 
یک نکته‌ی مهم: در fetch API حتی برای درخواست‌های ساده نیز کوکی‌ها ارسال نمی‌شوند. در این حالت برای کار با دومین جاری و ارسال کوکی‌های کاربر به سمت سرور، باید از تنظیم 'credentials: 'same-origin استفاده کرد؛ زیرا مقدار پیش‌فرض آن omit است.
مطالب
نمایش گرادیان در iTextSharp

کتابخانه iTextSharp نمایش گرادیانی از رنگ‌ها را هم پشتیبانی می‌کند و بدیهی است این نمایش برداری است. روش استفاده از آن هم بسیار ساده است؛ مثلا:

PdfShading shading = PdfShading.SimpleAxial(pdfWriter, x0, y0, x1, y1, BaseColor.YELLOW, BaseColor.RED);
PdfShadingPattern pattern = new PdfShadingPattern(shading);
ShadingColor color = new ShadingColor(pattern);

متد PdfShading.SimpleAxial بر اساس شیء PdfWriter که توسط آن به Canvas صفحه دسترسی پیدا می‌کند، در مختصاتی مشخص، یک طیف رنگی را ایجاد می‌کند. بر این اساس می‌توان به یک ShadingColor هم رسید که از آن مثلا به عنوان BackgroundColor یک PdfPCell قابل استفاده است.
تا اینجا ساده به نظر می‌رسد اما واقعیت این است که مختصات ذکر شده، مهم است و آن‌را در مورد مثلا سلول‌های یک جدول تنها در زمان تهیه نهایی یک جدول می‌توان به دست آورد. البته اگر شیء ساده‌ای را روی صفحه رسم کنیم، این مورد بر اساس مختصات ابتدایی شیء واضح به نظر می‌رسد.
برای حل این مشکل در مورد جداول و سلول‌های آن خاصیتی به نام CellEvent وجود دارد که از نوع IPdfPCellEvent است. به عبارتی با ارسال یک وهله از کلاسی که اینترفیس IPdfPCellEvent را پیاده سازی می‌کند، می‌توان در زمان نمایش نهایی یک سلول به مختصات دقیق آن دسترسی پیدا کرد.
یک مثال کامل را در مورد پیاده سازی IPdfPCellEvent و استفاده از آن جهت نمایش گرادیان در Header و footer یک جدول، در ادامه مشاهده خواهید نمود:

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

namespace iTextSharpGradientTest
{
public class GradientCellEvent : IPdfPCellEvent
{
public void CellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases)
{
var cb = canvases[PdfPTable.BACKGROUNDCANVAS];
cb.SaveState();

var shading = PdfShading.SimpleAxial(
cb.PdfWriter,
position.Left, position.Top, position.Left, position.Bottom,
BaseColor.YELLOW, BaseColor.ORANGE);
var shadingPattern = new PdfShadingPattern(shading);

cb.SetShadingFill(shadingPattern);
cb.Rectangle(position.Left, position.Bottom, position.Width, position.Height);
cb.Fill();

cb.RestoreState();
}
}

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

var table1 = new PdfPTable(1);
table1.HeaderRows = 2;
table1.FooterRows = 1;

//header row
var headerCell = new PdfPCell(new Phrase("header"))
{
HorizontalAlignment = Element.ALIGN_CENTER,
Border = 0
};
headerCell.CellEvent = new GradientCellEvent();
table1.AddCell(headerCell);

//footer row
var footerCell = new PdfPCell(new Phrase("footer"))
{
HorizontalAlignment = Element.ALIGN_CENTER,
Border = 0
};
footerCell.CellEvent = new GradientCellEvent();
table1.AddCell(footerCell);

//adding some rows
for (int i = 0; i < 70; i++)
{
var rowCell = new PdfPCell(new Phrase("Row " + i)) { BorderColor = BaseColor.LIGHT_GRAY };
table1.AddCell(rowCell);
}

pdfDoc.Add(table1);
}

//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}

با خروجی:


نکته مهم این مثال نحوه مقدار دهی CellEvent است. به این ترتیب در زمان نمایش نهایی یک سلول می‌توان در متد CellLayout، به خواص فقط خواندنی آن سلول دسترسی یافت. مثلا position، مختصات نهایی مستطیل مرتبط با سلول جاری را بر می‌گرداند؛ یا از طریق canvases می‌توان برای آخرین بار فرصت یافت تا در یک سلول نقاشی کرد.


مطالب
ارتقاء به Angular 6: بررسی تغییرات Angular CLI
اولین مرحله‌ی ارتقاء به Angular 6، به روز رسانی Angular CLI 1.x به نگارش 6 آن است. این شماره نگارش نیز با شماره نگارش Angular یکی شده‌است و دیگر 1x نیست. CLI 6.0 فقط پروژه‌های Angular 5.x و 6x را پشتیبانی می‌کند و برای نصب آن نیاز به حداقل NodeJS 8.9 و NPM 5.5 را خواهید داشت. بنابراین ابتدا دستورات زیر را صادر کرده و اگر هنوز از نگارش‌های قدیمی این ابزارها استفاده می‌کنید، قبل از هر کاری باید آن‌ها را به روز رسانی کنید:
node -v
npm -v
این نگارش از CLI در پشت صحنه از Webpack 4 استفاده می‌کند که نسبت به نگارش‌های پیشین آن بسیار سریعتر است، tree-shaking بهتری را انجام می‌دهد و در نهایت سبب تولید برنامه‌هایی با حجم کمتر و با سرعت build بیشتری خواهد شد.


فایل پیشین angular-cli.json حذف و فایل جدید angular.json بجای آن معرفی شده‌است

یکی از مهم‌ترین تغییرات CLI 6.0 نسبت به نگارش‌های قبلی آن، پشتیبانی از چندین پروژه است و به همین منظور ساختار فایل تنظیمات آن‌را به طور کامل تغییر داده‌اند و اگر دستور ng new project1 را صادر کنید، دیگر از فایل پیشین angular-cli.json خبری نبوده و بجای آن فایل جدید angular.json قابل مشاهده‌است:
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "project1": {
در اینجا ساختار ابتدای فایل angular.json را مشاهده می‌کنید که در آن مفهوم projects (و یا workspace در اینجا) به همراه project1 جدیدی که ایجاد کردیم، به عنوان زیر مجموعه‌ی آن قابل مشاهده‌است.
مزیت مهم این قابلیت، امکان ایجاد libraries است که به صورت توکار از این نگارش پشتیبانی می‌شود و می‌توان اجزایی مانند components ،directives ،pipes و services اشتراکی را در یک یا چندین کتابخانه قرار داد و سپس از آن‌ها در پروژه‌ی اصلی و یا چندین پروژه‌ی متصل استفاده کرد.
نگارش 6 در پشت صحنه، پروژه‌ی موفق ng-packagr را به مجموعه‌ی CLI اضافه کرده‌است و از این پس توسط خود CLI می‌توان کتابخانه‌های استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانه‌های npm حاصل را نیز به همراه دارد؛ مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار، با بسته‌های فعلی npm مخصوص Angular وجود دارند. برای مثال بدون استفاده‌ی از این ابزار، نیاز است مستندات 13 صفحه‌ای ساخت کتابخانه‌های Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0، به صورت خودکار ایجاد و مدیریت می‌شود.

بنابراین اکنون سؤال مهمی که مطرح می‌شود این است: آیا باید فایل angular-cli.json پیشین را به صورت دستی به این فایل جدید به روز رسانی کرد و چگونه؟


به روز رسانی تمام بسته‌های سراسری سیستم

در ادامه پیش از هر کاری نیاز است تمام بسته‌های سراسری npm ایی را که هر از چندگاهی به سیستم خود اضافه کرده‌اید، به روز رسانی کنید. برای مشاهده‌ی لیست موارد تاریخ مصرف گذشته‌ی آن‌ها، دستور زیر را صادر کنید:
 npm outdated -g --depth=0
و برای به روز رسانی یکجای آن‌ها نیاز است دستور زیر اجرا شود:
 npm update -g


به روز رسانی خودکار ساختار فایل angular-cli.json

این به روز رسانی توسط CLI 6.0 به صورت خودکار پشتیبانی می‌شود و شامل این مراحل است:
ابتدا نیاز است بسته‌ی سراسری آن‌را به روز رسانی کرد:
 npm i -g @angular/cli
سپس وارد پوشه‌ی اصلی پروژه‌ی خود شده و این دستور را به صورت محلی نیز وارد کنید:
 npm install --save-dev @angular/cli@latest
با اینکار بسته‌ی CLI محلی پروژه به روز شده و اکنون می‌توانیم از قابلیت جدید آن که ng update نام دارد، استفاده کنیم. برای این منظور دستورات ذیل را به ترتیب اجرا کنید:
ng update @angular/cli
ng update @angular/core
ng update rxjs
دستور اول کار تبدیل خودکار فایل angular-cli.json قدیمی را به ساختار جدید آن انجام میدهد؛ با این لاگ:
DELETE .angular-cli.json
CREATE angular.json (4273 bytes)
UPDATE karma.conf.js (1008 bytes)
UPDATE src/tsconfig.spec.json (322 bytes)
UPDATE package.json (2076 bytes)
UPDATE tslint.json (3217 bytes)
دستور دوم بسته‌های هسته‌ی angular را به روز رسانی می‌کند و دستور سوم کار به روز رسانی کتابخانه‌ی rxjs را انجام می‌دهد.
لیست سایر بسته‌هایی را که می‌توان توسط این دستور به روز رسانی کرد، با اجرا دستور ng update می‌توانید مشاهده کنید. برای مثال اگر از Angular Material نیز استفاده می‌کنید، دستور به روز رسانی آن به صورت زیر است:
 ng update @angular/material


مشکل! دستور ng update کار نمی‌کند!

اگر پروژه‌ی شما صرفا مبتنی بر بسته‌های اصلی Angular باشد، مراحل یاد شده‌ی فوق را با موفقیت به پایان خواهید رساند. اما اگر از کتابخانه‌های ثالثی استفاده کرده باشید، منهای دستور «ng update @angular/cli» که کار تولید فایل جدید angular.json را انجام می‌دهد، مابقی با خطاهایی مانند «incompatible peer dependency» و یا «Invalid range:>=2.3.1 <3.0.0||>=4.0.0» متوقف می‌شوند.
در یک چنین حالتی نیاز است ابتدا وابستگی‌های محلی پروژه را به روز کرد و سپس دستورات ng update را تکرار نمود. برای این منظور ابتدا بسته‌ی npm-check-updates را نصب کنید:
 npm install npm-check-updates -g
کار آن به روز رسانی خودکار بسته‌های npm یک پروژه است. پس از آن دستورات زیر را صادر نمائید:
ncu -u
npm install
دستور اول تمام شماره نگارش‌های بسته‌های موجود در فایل package.json را به صورت خودکار به آخرین نگارش آن‌ها روز رسانی می‌کند و دستور دوم این بسته‌های جدید را دریافت و نصب خواهد کرد.
اکنون تمام وابستگی‌های محلی پروژه‌ی شما به صورت خودکار به آخرین نگارش آن‌ها به روز رسانی شده‌اند و اینبار اگر دستورات ذیل را اجرا کنید، با خطاهای یاد شده مواجه نخواهید شد:
ng update @angular/core
ng update rxjs

البته اگر در این حالت برنامه را کامپایل کنید، کار نخواهد کرد. علت اصلی آن به به‌روز رسانی rxjs به نگارش 6 آن مرتبط می‌شود که در مطلب بعدی پیگیری خواهد شد و این نگارش شامل حذفیات بسیاری است در جهت کاهش حجم آن، یکپارچکی و یک دست شدن syntax آن و همچنین بهبود قابل ملاحظه‌ی کارآیی آن. البته پیشنیاز الزامی آن آشنایی با pipe-able operators است. علت دیگر کامپایل نشدن برنامه هم می‌تواند عدم استفاده از HttpClient نگارش 4.3 به بعد باشد.


خاموش کردن اخطار «TypeScript version mismatch»

اگر نگارش TypeScript نصب شده‌ی در سیستم به صورت سراسری، با نگارش محلی پروژه‌ی شما یکی نباشد، اخطار «TypeScript version mismatch» را دریافت می‌کنید. روش خاموش کردن آن در CLI جدید با اجرای دستور زیر است:
 ng config cli.warnings.typescriptMismatch false
البته نگارش 6 نیاز به TypeScript 2.7.2 ذکر شده‌ی در package.json پروژه‌ی محلی را دارد؛ وگرنه اصلا شروع به کامپایل برنامه نمی‌کند:
{
  "devDependencies": {
    "typescript": "~2.7.2"
  }
}
بهترین راه برای یافتن این شماره، اجرای دستور ng new projet1 در یک پوشه‌ی خالی است و سپس مقایسه‌ی محتوای فایل package.json آن با فایل پروژه‌ی موجود.


 تغییرات ng build در نگارش 6

در نگارش 6، مفهوم پیشین environments به configurations تغییر یافته‌است و اینبار در فایل جدید angular.json تنظیم می‌شوند:
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "angular-template-driven-forms-lab": {
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            }
          }
        },
و پیشتر اگر برای تنظیم محیط build از سوئیچ env استفاده می‌شد:
 ng build --env staging
اکنون این سوئیچ به configuration تغییر یافته‌است و نام آن از مداخل configurations فایل angular.json، مانند مثال فوق که «production» نام دارد، باید دریافت و تنظیم شود:
 ng build --configuration staging
و یا برای مثال دستور «ng build --env=prod» دیگر اجرا نمی‌شود و env=prod حذف شده‌است و اکنون اجرای  ng build --prod مانند اجرای دستور ng build --configuration=prod است.


تغییرات نام‌های نهایی تولیدی

در CLI 6.0، نام‌های نهایی تولیدی دیگر به همراه bundle  و یا chunk نیستند. برای مثال دستور ng build یک چنین خروجی را تولید می‌کند:
 >ng build --watch

Date: 2018-05-05T09:10:50.158Z
Hash: a43eab94ff01539b8592
Time: 31733ms
chunk {main} main.js, main.js.map (main) 9.38 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 226 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 5.4 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 15.6 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.05 MB [initial] [rendered]

و هش‌های حالت prod به صورت زیر تولید و به نام فایل اضافه می‌شوند:
>ng build --prod --watch

Date: 2018-05-05T09:17:01.803Z
Hash: f25fd6788a4969c52b70
Time: 73279ms
chunk {0} runtime.6afe30102d8fe7337431.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered]
chunk {2} polyfills.6c08419970f9e4781b69.js (polyfills) 59.4 kB [initial] [rendered]


ساده شدن افزودن وابستگی‌های ثالث به پروژه‌های CLI

برای نصب یک کتابخانه‌ی ثالث، پیشتر می‌بایستی ابتدا بسته‌ی npm آن جداگانه نصب و سپس فایل config برنامه، جهت معرفی مداخل آن، ویرایش می‌شد. اکنون دستور جدید ng add تمام این مراحل را به صورت خودکار انجام می‌دهد:
 ng add @angular/material
برای نمونه دستور فوق نه تنها فایل‌های Angular Material را دریافت می‌کند، بلکه فایل‌های CSS و ماژول‌های مرتبط با آن‌را نیز import خواهد کرد. البته این مورد از کلید ng-add فایل package.json بسته‌ی در حال نصب دریافت و تنظیم می‌شود و کتابخانه‌های جدید باید خود را بر این اساس وفق دهند.
مطالب
الگوی نماینده (پروکسی) Proxy Pattern
همه کاربران کامپیوتر در ایران به خوبی با کلمه پروکسی آشنا هستند. پروکسی به معنی نماینده یا واسط است و پروکسی واسطی است بین ما و شیء اصلی. پروکسی در شبکه به این معنی است که سیستم شما به یک سیستم واسط متصل شده است که از طریق پروکسی محدودیت‌های دسترسی برای آن تعریف شود. در اینجا هم پروکسی در واقع به همین منظور استفاده می‌شود. در تعدادی از کامنت‌های سایت خوانده بودم دوستان در مورد اصول SOLID  و Refactoring بحث می‌کردند که آیا انجام عمل اعتبارسنجی در خود اصل متد کار درستی است یا خیر. در واقع خودم هم نمی‌دانم که این حرکت چقدر به این اصول پایبند است یا خیر، ولی می‌دانم که الگوی پروکسی کل سؤالات بالا را حذف می‌کند و با این الگو دیگر این سؤال اهمیتی ندارد. به عنوان مثال فرض کنید که شما یک برنامه ساده کار با فایل را دارید. ولی اگر بخواهید اعتبارسنجی‌هایی را برای آن تعریف کنید، بهتر است اینکار را به یک پروکسی بسپارید تا شیء گرایی بهتری را داشته باشید.

برای شروع ابتدا یک اینترفیس تعریف می‌کنیم:
   public interface IFilesService
    {
        DirectoryInfo GetDirectoryInfo(string directoryPath);
        void DeleteFile(string fileName);
        void WritePersonInFile(string fileName,string name, string lastName, byte age);
    }
این اینترفیس شامل سه متد نام نویسی، حذف فایل و دریافت اطلاعات یک دایرکتوری است. بعد از آن دو عدد کلاس را از آن مشتق می‌کنیم:
کلاس اصلی:
    class FilesServices:IFilesService
    {
        public DirectoryInfo GetDirectoryInfo(string directoryPath)
        {
            return new DirectoryInfo(directoryPath);
        }

        public void DeleteFile(string fileName)
        {
            File.Delete(fileName);
            Console.WriteLine("the file has been deleted");
        }

        public void WritePersonInFile(string fileName, string name, string lastName, byte age)
        {
            var text = $"my name is {name} {lastName} with {age} years old from dotnettips.info";
            File.WriteAllText(fileName,text);
        }    
    }
که تنها وظیفه اجرای فرامین را دارد و دیگری کلاس پروکسی است که وظیف تامین اعتبارسنجی و آماده سازی پیش شرط‌ها را دارد:
    class FilesServicesProxy:IFilesService
    {
        private readonly IFilesService _filesService;

        public FilesServicesProxy()
        {
            _filesService=new FilesServices();
        }

        public DirectoryInfo GetDirectoryInfo(string directoryPath)
        {
            var existing = Directory.Exists(directoryPath);
            if (!existing)
                Directory.CreateDirectory(directoryPath);
            return _filesService.GetDirectoryInfo(directoryPath);
        }

        public void DeleteFile(string fileName)
        {
            if(!File.Exists(fileName))
                Console.WriteLine("Please enter a valid path");
            else
                _filesService.DeleteFile(fileName);
        }

        public void WritePersonInFile(string fileName, string name, string lastName, byte age)
        {
            if (!Directory.Exists(fileName.Remove(fileName.LastIndexOf("\\"))))
            {
                Console.WriteLine("File Path is not valid");
                return;
            }
            if (name.Trim().Length == 0)
            {
                Console.WriteLine("first name must enter");
                return;
            }

            if (lastName.Trim().Length == 0)
            {
                Console.WriteLine("last name must enter");
                return;
            }

            if (age<18)
            {
                Console.WriteLine("your age is illegal");
                return;
            }


            if (name.Trim().Length < 3)
            {
                Console.WriteLine("first name must be more than 2 letters");
                return;
            }

            if (lastName.Trim().Length <5)
            {
                Console.WriteLine("last name must be more than 4 letters");
                return;
            }

            _filesService.WritePersonInFile(fileName,name,lastName,age);
            Console.WriteLine("the file has been written");
        }
    }
کلاس پروکسی، همان کلاسی است که شما باید صدا بزنید. وظیفه کلاس پروکسی هم این است که در زمان معین و صحیح، کلاس اصلی شما را بعد از اعتبارسنجی‌ها و انجام پیش شرط‌ها صدا بزند. همانطور که می‌بیند، ما در سازنده کلاس اصلی را در حافظه قرار می‌دهیم. سپس در هر متد، اعتبارسنجی‌های لازم را انجام می‌دهیم. مثلا در متد GetDirectoryInfo باید اطلاعات دایرکتوری بازگشت داده شود؛ ولی اصل عمل در واقع در کلاس اصلی است و اینجا فقط شرط گذاشته‌ایم اگر مسیر داده شده معتبر نبود، همان مسیر را ایجاد کن و سپس متد اصلی را صدا بزن. یا اگر فایل موجود است جهت حذف آن اقدام کن و ...
در نهایت در بدنه اصلی با تست چندین حالت مختلف، همه متد‌ها را داریم:
static void Main(string[] args)
        {
            IFilesService filesService=new FilesServicesProxy();
            filesService.WritePersonInFile("c:\\myfakepath\\a.txt","ali","yeganeh",26);

            var directory = filesService.GetDirectoryInfo("d:\\myrightpath\\");
            var fileName = Path.Combine(directory.FullName, "dotnettips.txt");

            filesService.WritePersonInFile(fileName, "al", "yeganeh", 26);
            filesService.WritePersonInFile(fileName, "ali", "yeganeh", 12);
            filesService.WritePersonInFile(fileName, "ali", "yeganeh", 26);

            filesService.DeleteFile("c:\\myfakefile.txt");
            filesService.DeleteFile(fileName);
        }
و نتیجه خروجی:
File Path is not valid
first name must be more than 2 letters
your age is illegal
the file has been written
Please enter a valid path
the file has been deleted
مطالب
React 16x - قسمت 12 - طراحی یک گرید - بخش 2 - فیلتر کردن اطلاعات
تا اینجا کامپوننت صفحه بندی را به همراه اعمال آن به لیست نمایش داده شده، پیاده سازی کردیم. در ادامه می‌خواهیم لیست ژانرهای سینمایی را که در فایل fakeGenreService.js تعریف شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
  { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" }
];

export function getGenres() {
  return genres.filter(g => g);
}
توسط list-group‌های بوت استرپی، در کنار صفحه نمایش داده و سپس به ازای هر گروه انتخابی توسط کاربر، فیلم‌های مرتبط با آن گروه را فیلتر کرده و نمایش دهیم.


بررسی ساختار کامپوننت ListGroup

شبیه به کامپوننت صفحه بندی که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت ListGroup نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\listGroup.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import ListGroup from "./common/listGroup";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و با اضافه کردن یک row بوت استرپی دو ستونی، قصد داریم کامپوننت لیست فیلم‌ها را در ستون اول این ردیف نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم.
برای این منظور ابتدا React.Fragment موجود را با یک div با "className="row جایگزین می‌کنیم. سپس داخل این row، دو ستون را تعریف خواهیم کرد که در اولی، المان جدید ListGroup قرار می‌گیرد و در دومی، مابقی عناصری که تاکنون اضافه کرده‌ایم؛ مانند جدول، صفحه بندی و نمایش تعداد آیتم‌ها:
    return (
      <div className="row">
        <div className="col-2">
          <ListGroup />
        </div>
        <div className="col">
          ...
        </div>
      </div>
    );
این listGroup، حداقل نیاز به لیست آیتم‌هایی را دارد که باید نمایش دهد. این لیست نیز از fakeGenreService و متد getGenres آن تامین می‌شود که به صورت یک خاصیت جدید در state به نحو زیر درج خواهد شد:
import { getGenres } from "../services/fakeGenreService";
// ...

class Movies extends Component {
  state = {
    // ...
    genres: getGenres()
  };
همانطور که در قسمت 9 این سری نیز بررسی کردیم، اگر getGenres قرار است از سمت سرور و توسط یک درخواست Ajax ای تامین شود، محل صحیح قرارگیری آن در متد lifecycle hook ویژه‌ای به نام componentDidMount است. اما در اینجا چون genres یک لیست درون حافظه‌ای است، مقدار دهی فوق، مشکلی را ایجاد نمی‌کند. هرچند می‌توان هم اکنون نیز تعریف فوق را کمی اصولی‌تر نوشت. برای اینکار متد componentDidMount را اضافه کرده و به نحو زیر تنظیم می‌کنیم:
class Movies extends Component {
  state = {
    movies: [],
    pageSize: 4,
    currentPage: 1,
    genres: []
  };

  componentDidMount() {
    this.setState({ movies: getMovies(), genres: getGenres() });
  }
ابتدا آرایه‌های مورد نیاز movies و genres را در state تعریف کرده و آن‌ها را با یک آرایه‌ی خالی، مقدار دهی اولیه می‌کنیم. از این جهت که تا رسیدن به مرحله‌ی componentDidMount که اندکی طول می‌کشد، خطاهای زمان اجرای عدم دسترسی به این آرایه‌ها در برنامه رخ ندهد. سپس زمانیکه وهله‌ای از این کامپوننت در DOM رندر شد، متد componentDidMount فراخوانی شده و دو خاصیت state را با مقادیر دریافتی، به روز رسانی می‌کند.

پس از آن می‌توان ویژگی جدید items این کامپوننت را به آرایه‌ی genres دریافتی از state، تنظیم کرد:
<ListGroup items={this.state.genres} />
در این مرحله، ورودی دیگری به نظر نمی‌رسد که مورد نیاز باشد. اکنون این سؤال مطرح می‌شود که چه رخ‌دادهایی را قرار است از این کامپوننت دریافت کنیم یا به عبارتی خروجی آن چیست؟
بهتر است هر زمانیکه کاربر، آیتمی را از این لیست انتخاب کرد، توسط بروز رخدادی مانند onItemSelect از وقوع آن مطلع شد و سپس نسبت به آن توسط متد handleGenreSelect، واکنش نشان داد؛ مانند فیلتر کردن لیست فیلم‌ها بر اساس آیتم انتخابی و نمایش آن. به همین جهت ویژگی onItemSelect را به تعریف المان ListGroup اضافه می‌کنیم:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
و سپس متد handleGenreSelect متصل به آن‌‌را به نحو زیر تعریف خواهیم کرد:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
  };
تا اینجا اینترفیس کامپوننت ListGroup را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


پیاده سازی نمایش آیتم‌ها در کامپوننت ListGroup

پیاده سازی ابتدایی کامپوننت ListGroup را در اینجا مشاهده می‌کنید:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item._id} className="list-group-item">
            {item.name}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;
کار با درج یک ul که با کلاس list-group مزین شده‌است، شروع می‌شود. سپس باید liهای آن‌را که نمایانگر آیتم‌های این لیست است، به صورت پویا با کلاس‌های list-group-item رندر کرد. برای اینکار از آرایه‌ی دریافتی this.props.items و فراخوانی متد map بر روی آن کمک می‌گیریم. در اینجا key هر ردیف با استفاده از خاصیت id هر آیتم و برچسب هر کدام از طریق خاصیت name هر شیء دریافتی، تامین می‌شود.

تا اینجا اگر برنامه را ذخیره کرده و در مرورگر نمایش دهیم، به خروجی زیر می‌رسیم:


البته به نظر عرض ستون آن نامناسب است. به همین جهت به کامپوننت movies مراجعه کرده و col-2 ستون آن‌را به col-3 تبدیل می‌کنیم.


پویا سازی انتخاب نام خواص شیء دریافتی، در کامپوننت ListGroup

در حال حاضر پیاده سازی کامپوننت ListGroup، به شیءای دقیقا با خواص id_ و name وابسته‌است و اگر شیء دیگری را که دارای خواصی معادل این نام‌ها نیست، به آن ارسال کنیم، دیگر کار نخواهد کرد. به همین جهت در محل تعریف المان این کامپوننت در کامپوننت movies، دو ویژگی دیگر نام خواص شیء مدنظر را تنظیم می‌کنیم تا بتوانیم با هر نوع شیءای در اینجا کار کنیم:
<ListGroup
  items={this.state.genres}
  textProperty="name"
  valueProperty="_id"
  onItemSelect={this.handleGenreSelect}
/>
پس از این تغییر و افزودن textProperty و valueProperty، برای پویا سازی نام‌های خواص دریافتی در کامپوننت ListGroup، از روش کار با []، جهت دسترسی پویای به خواص یک شیء، استفاده می‌کنیم تا دیگر این کامپوننت به شیء خاص genre، وابستگی نداشته باشد و قابلیت استفاده‌ی مجدد از آن افزایش یابد:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item[this.props.valueProperty]} className="list-group-item">
            {item[this.props.textProperty]}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;


تعیین مقادیر پیش‌فرضی برای خواص props

با زیاد شدن تعداد خواص props، اینترفیس کامپوننت‌ها پیچیده‌تر می‌شوند. در یک چنین حالتی می‌توان در کامپوننت‌ها defaultProps را تعریف کرد و توسط آن مقادیر پیش‌فرضی را برای خواص props درنظر گرفت. به این صورت در حین تعریف المان این کامپوننت، اگر مقادیر مدنظر با مقادیر پیش‌فرض تعیین شده یکی باشند، دیگر نیازی به ذکر این پارامترها نخواهد بود. برای مثال در انتهای کامپوننت ListGroup، خاصیت جدید defaultProps را تعریف می‌کنیم (املای آن باید دقیقا به همین شکل باشد؛ و گرنه شناخته نخواهد شد). سپس در اینجا خواصی را که می‌خواهیم مقادیر پیش‌فرضی را برای آن‌ها تعیین کنیم، ذکر خواهیم کرد:
ListGroup.defaultProps = {
  textProperty: "name",
  valueProperty: "_id"
};

export default ListGroup;
برای نمونه در اینجا دو خاصیت جدید textProperty و valueProperty را به همان مقادیر name و id_ مورد استفاده‌ی در این مثال تنظیم کرده‌ایم. پس از این تعریف، می‌توان به کامپوننت movies که از این ویژگی‌ها استفاده می‌کند مراجعه کرده و آن‌هایی را که با defaultProps تطابق دارند، از لیست ویژگی‌های ذکر شده حذف کرد؛ یعنی تعریف المان ListGroup به صورت زیر ساده می‌شود:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
بدیهی است اگر در آینده با اشیاء دیگری سر و کار داشتیم، می‌توان مجددا این خواص پیش‌فرض را بر اساس ساختار این اشیاء، مقدار دهی و تعیین کرد.


مدیریت انتخاب گروه‌های فیلم‌ها

در ادامه می‌خواهیم رخ‌داد onClick بر روی هر li این لیست را مدیریت کنیم و سبب بروز رخ‌دادی به نام onItemSelect شویم که در ابتدای بحث، آن‌را به عنوان خروجی این کامپوننت تعریف کردیم. این رخداد نیز در کامپوننت movies به متد handleGenreSelect متصل است. به همین جهت تعریف ویژگی onClick را که سبب انتقال شیء جاری رندر شده، توسط رویداد onItemSelect به خارج از آن می‌شود، به المان li کامپوننت ListGroup اضافه می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className="list-group-item"
  onClick={() => this.props.onItemSelect(item)}
  style={{ cursor: "pointer" }}
>
  {item[this.props.textProperty]}
</li>
پس از این تغییرات و ذخیره‌ی برنامه، اگر به خروجی برنامه در مرورگر مراجعه کرده و بر روی هر کدام از آیتم‌های لیست گروه‌های فیلم‌ها کلیک کنیم، شیء مرتبط با آن آیتم در کنسول توسعه دهنده‌های مرورگر، لاگ می‌شود که نشان از برقراری صحیح ارتباطات این قسمت را دارد.

پس از فعالسازی امکان کلیک بر روی هر آیتم لیست رندر شده، اکنون می‌خواهیم با انتخاب هر گروه، این گروه در این لیست، به صورت انتخاب شده، همانند شماره صفحه‌ی انتخاب شده‌ی در کامپوننت صفحه بندی، تغییر رنگ دهد و متمایز نمایش داده شود تا مشخص باشد که هم اکنون با کدام آیتم در حال کار هستیم. برای اینکار تنها کافی است کلاس active را به صورت پویا به className هر li، اضافه یا کم کنیم. البته برای این منظور این کامپوننت باید از آیتم انتخاب شده مطلع باشد؛ به همین جهت selectedItem را در لیست ویژگی‌های اینترفیس تعریف این المان اضافه می‌کنیم. برای اینکار ابتدا selectedGenre را با هربار فراخوانی handleGenreSelect که به onItemSelect کامپوننت متصل است، با فراخوانی متد setState به روز رسانی می‌کنیم:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({selectedGenre: genre});
  };
در یک چنین حالتی الزامی به تعریف selectedGenre در خاصیت state ابتدای کامپوننت نیست. چون با فراخوانی متد setState اگر یکی از خواص منتسب به شیء state به روز شده باشد، آن خاصیت نیز به روز می‌شود و یا اگر این خاصیت جدید باشد، با state موجود یکی خواهد شد؛ هرچند آن‌را به صورت زیر نیز می‌توان تعریف کرد که با یک شیء خالی مقدار دهی شده‌است:
class Movies extends Component {
  state = {
    // ...
    selectedGenre: {}
  };
سپس ویژگی selectedItem کامپوننت را به این مقدار تغییر یافته‌ی this.state.selectedGenre تنظیم می‌کنیم تا با هر بار فراخوانی setState که سبب رندر مجدد کامپوننت Movies در DOM مجازی React می‌شود، کامپوننت از selectedItem تغییر یافته مطلع شده و با افزودن کلاس active به آن آیتم، واکنش نشان دهد:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
  selectedItem={this.state.selectedGenre}
/>
اکنون به کامپوننت ListGroup مراجعه کرده و بر اساس ویژگی جدید selectedItem، تغییرات زیر را به className اعمال می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className={
    item === this.props.selectedItem
      ? "list-group-item active"
      : "list-group-item"
  }
  style={{ cursor: "pointer" }}
  onClick={() => this.props.onItemSelect(item)}
>
  {item[this.props.textProperty]}
</li>
در اینجا اگر item در حال رندر با this.props.selectedItem دریافتی یکی باشد، کلاس active به کلاس list-group-item اضافه می‌شود و برعکس.



مدیریت فیلتر کردن اطلاعات گروه فیلم انتخابی

در قسمت قبل، در ابتدای متد رندر کامپوننت movies، از متد paginate برای صفحه بندی اطلاعات استفاده کردیم. فیلتر گروه جاری انتخاب شده را باید پیش از این متد قرار دارد؛ چون تعداد صفحات و اطلاعات نمایش داده شده‌ی در هر کدام باید بر اساس لیست فیلم‌های فیلتر شده باشد.
برای انجام اینکار تغییرات زیر را اعمال خواهیم کرد:
الف) بجای متد paginate، از متد getPagedData زیر استفاده می‌کنیم:
  getPagedData() {
    const {
      pageSize,
      currentPage,
      selectedGenre,
      movies: allMovies
    } = this.state;

    const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

    const first = (currentPage - 1) * pageSize;
    const last = first + pageSize;
    const pagedMovies = filteredMovies.slice(first, last);

    return { totalCount: filteredMovies.length, data: pagedMovies };
  }
- در اینجا بجای اینکه مدام this.stat‌ها را جهت دریافت خواص آن تکرار کنیم، با استفاده از ویژگی Object Destructuring، خواصی را که نیاز داریم یکبار انتخاب کرده و سپس به دفعات از آن‌ها استفاده می‌کنیم. به همین جهت در این قطعه کد، فقط یکبار this.state را مشاهده می‌کنید که بسیار تمیزتر است و همچنین کارآیی آن نیز به علت عدم انتخاب مداوم مقدار خاصیتی از یک شیء، بالاتر از حالت قبل است.
- در حین Object Destructuring، نام خاصیت movies را نیز به allMovies تغییر داده‌ایم تا واضح‌تر باشد.
- در ادامه با استفاده از متد filter جاوااسکریپت، بر اساس id هر گروه انتخاب شده، اشیاء مرتبط با آن، از allMovies جدا شده و بازگشت داده می‌شود. البته اگر id هم انتخاب نشده باشد (اولین بار نمایش صفحه)، تمام رکوردها یعنی allMovies، مورد استفاده قرار می‌گیرد.
- پس از آن، همان کدهای صفحه بندی اطلاعات را که در قسمت قبل بررسی کردیم، مشاهده می‌کنید که اینبار بجای allMovies قسمت قبل، بر روی filteredMovies اعمال شده‌است.
- در آخر، این متد، یک شیء را با دو خاصیت که بیانگر تعداد کل رکوردهای انتخاب شده و داده‌های فیلتر شده‌ی صفحه بندی شده‌است، بازگشت می‌دهد.

ب) تغییرات متد رندر کامپوننت movies به صورت زیر است:
- ابتدا متد getPagedData فوق، فراخوانی شده و شیء دریافتی از آن با استفاده از ویژگی Object Destructuring، به دو خاصیت totalCount و movies انتساب داده می‌شود:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const { totalCount, data: movies } = this.getPagedData();
- از آرایه‌ی movies، در قسمت قبل برای رندر لیست فیلم‌ها استفاده شد. به همین جهت در اینجا تغییر نام data به movies را مشاهده می‌کنید.
- همچنین کامپوننت صفحه بندی، اینبار باید totalCount آیتم‌های فیلتر شده را نمایش دهد و نه totalCount تمام فیلم‌های موجود را:
<Pagination
    itemsCount={totalCount}
در اینجا برچسب نمایش تعداد آیتم‌های موجود نیز باید تغییر کند:
<p>Showing {totalCount} movies in the database.</p>
ج) ممکن است در اولین بار مشاهده‌ی صفحه، کاربر صفحه‌ی شماره‌ی 3 را انتخاب کند که سبب تغییر currentPage موجود در state، به عدد 3 می‌شود. اکنون اگر کاربر نمایش فیلتر شده‌ی فیلم‌های یک گروه خاص را انتخاب کند، باید این شماره، به عدد 1 مجددا تنظیم شود:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({ selectedGenre: genre, currentPage: 1 });
  };



افزودن گزینه‌ی نمایش تمام اطلاعات به لیست گروه‌های فیلم‌ها

در ادامه قصد داریم به بالای لیست گروه‌های موجود، گزینه‌ی All Genres را نیز اضافه کنیم تا با کلیک بر روی آن، مجددا بتوان لیست تمام فیلم‌های موجود را مشاهده کرد.


برای این منظور در جائیکه لیست getGenres را دریافت و نمایش می‌دهیم، یعنی متد componentDidMount، اندکی تغییر ایجاد کرده و یک آرایه‌ی جدید را ایجاد می‌کنیم؛ بطوریکه اولین عنصر آن، گزینه‌ی جدید All Genres باشد و سپس توسط spread operator، مابقی عناصر آرایه‌ی گروه‌ها را به این آرایه‌ی جدید اضافه می‌کنیم:
  componentDidMount() {
    const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
    this.setState({ movies: getMovies(), genres });
  }
همین اندازه تغییر برای فعالسازی این گزینه کفایت می‌کند؛ از این جهت که در متد getPagedData، ابتدا بررسی می‌شود که اگر آیتمی انتخاب شده بود و همچنین دارای id نیز بود، آنگاه کار فیلتر کردن صورت گیرد، درغیراینصورت، تمام رکوردها را بازگشت دهد:
const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-12.zip
مطالب
غیرفعال کردن کش مرورگر هنگام استفاده از jQuery Ajax

برای استفاده از قابلیت‌های Ajax کتابخانه jQuery ، شش متد زیر در اختیار برنامه نویس‌ها است:

$.ajax(), load(), $.get(), $.getJSON(), $.getScript(), and $.post()
که در حقیقت 5 مورد آخر ذکر شده صرفا بیان اولین متد ajax فوق به نحوی دیگر می‌باشند و محصور کننده‌ توانایی‌های آن هستند.

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

using System;
using System.Globalization;
using System.Web.Script.Services;
using System.Web.Services;

namespace TestJQueryAjax
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : WebService
{
public class TimeInfo
{
public string Date { set; get; }
public string Hr { set; get; }
public string Min { set; get; }
}

[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public TimeInfo GetTime()
{
DateTime now = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
PersianCalendar pc = new PersianCalendar();
string today = string.Format("{0}/{1}/{2}",
pc.GetYear(now),
pc.GetMonth(now).ToString("00"),
pc.GetDayOfMonth(now).ToString("00"));
TimeInfo ti = new TimeInfo
{
Date = today,
Hr = DateTime.Now.Hour.ToString("00"),
Min = DateTime.Now.Minute.ToString("00")
};
return ti;
}
}
}
سپس اگر از تابع Ajax کتابخانه jQuery جهت دریافت زمان جاری از وب سرویس استفاده نمائیم، کد صفحه ما به صورت زیر خواهد بود:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestFillCtrls.aspx.cs"
Inherits="TestJQueryAjax.TestFillCtrls" %>

<!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 type="text/javascript">
function validate() {
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/GetTime',
data: '{}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
$("#<%=txtDate.ClientID %>").val(msg.d.Date);
$("#<%=txtHr.ClientID %>").val(msg.d.Hr);
$("#<%=txtMin.ClientID %>").val(msg.d.Min);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
alert("خطایی رخ داده است");
}
});
//debugger;
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtDate" runat="server" />
<br />
<asp:TextBox ID="txtHr" runat="server" />
<br />
<asp:TextBox ID="txtMin" runat="server" />
<br />
<asp:Button ID="btnGetTime" runat="server" Text="Click here!" UseSubmitBehavior="false"
OnClientClick="validate();return false;" />
</div>
</form>
</body>
</html>
تنها نکته‌ی جدید این اسکریپت، نحوه‌ی استفاده از خروجی JSON وب متد ما است که از نوع TimeInfo تعریف شده است. خروجی نمونه این وب متد به صورت زیر می‌تواند باشد:

{"d":{"__type":"TestJQueryAjax.AjaxSrv+TimeInfo","Date":"1388/07/14","Hr":"12","Min":"59"}}
که نحوه‌ی دسترسی به اجزای آن‌را در متد validate‌ ملاحظه می‌نمائید.

باید به خاطر داشت که برای هر 6 متد Ajax ایی jQuery ، عملیات کش شدن اطلاعات در مرورگر کاربر به صورت پیش فرض فعال است. اما این نکته تنها زمانیکه dataType مورد استفاده از نوع script یا jsonp باشد، صادق نبوده و کش شدن به صورت خودکار غیرفعال می‌گردد.
روش سنتی غیرفعال کردن کش در حین عملیات اجکسی، استفاده از یک کوئری استرینگ متغیر در پایان url درخواستی است. به این صورت مرورگر درخواست صادره را جدید فرض کرده و از کش خود استفاده نمی‌نماید (همین مورد در حالت کش شدن تصاویر هم صادق است).
jQuery نیز همین عملیات را در پشت صحنه انجام داده اما تنظیم آن‌را به نحوی مطلوب‌تری ارائه می‌دهد. یا پارامتر cache را در تعریف متد ajax خود اضافه نموده و مقدار آن را مساوی false قرار دهید و یا جهت تاثیر گذاری بر روی کلیه متدهای مورد استفاده، پیش از استفاده از آن‌ها این تنظیم را مشخص سازید:

$.ajaxSetup({cache: false});

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت سوم - رهاسازی منابع سرویس‌های IDisposable
یکی از پرکاربردترین اینترفیس‌های NET.، اینترفیس IDisposable است. عموما کلاس‌هایی که ارجاعی را به منابع غیر مدیریت شده مانند فایل‌ها و سوکت‌ها داشته باشند، این اینترفیس را پیاده سازی می‌کنند. garbage collector به صورت خودکار حافظه‌ی اشیاء مدیریت شده یا دات نتی را رها می‌کند؛ اما چیزی را در مورد منابع غیر مدیریت شده نمی‌داند. به همین جهت پیاده سازی اینترفیس IDisposable روشی را جهت پاکسازی این منابع به garbage collector معرفی می‌کند.


رفتار IoC Container توکار ASP.NET Core با سرویس‌های IDisposable

ASP.NET Core به همراه یک IoC Container توکار ارائه می‌شود و اگر سرویسی با طول عمرTransient و یا Scoped به آن معرفی شود و همچنین این سرویس اینترفیس IDisposable را نیز پیاده سازی کند، کار dispose خودکار آن در پایان درخواست جاری صورت می‌گیرد و نیازی به تنظیمات اضافه‌تری ندارد. در اینجا سرویس‌هایی با طول عمر Singleton نیز در پایان کار برنامه، زمانیکه خود ServiceProvider به پایان کارش می‌رسد، dispose خواهند شد.
البته این مورد یک شرط را نیز به همراه دارد: کار وهله سازی سرویس‌های درخواستی باید توسط خود این IoC Container مدیریت شود تا در پایان کار بداند چگونه آن‌ها را Dispose کند.

یک مثال: بررسی Dispose شدن خودکار یک سرویس IDisposable
namespace CoreIocServices
{
    public interface IMyDisposableService
    {
        void Run();
    }

    public class MyDisposableService : IMyDisposableService, IDisposable
    {
        private readonly ILogger<MyDisposableService> _logger;

        public MyDisposableService(ILogger<MyDisposableService> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _logger.LogInformation("+ {0} was created", this.GetType().Name);
        }

        public void Run()
        {
            _logger.LogInformation("Running MyDisposableService!");
        }

        public void Dispose()
        {
            _logger.LogInformation("- {0} was disposed!", this.GetType().Name);
        }
    }
}
سرویس ساده‌ی فوق، اینترفیس IDisposable را پیاده سازی می‌کند و با استفاده از ILogger، پیام‌هایی را در زمان ایجاد و Dipose آن در پنجره کنسول و یا دیباگ نمایش خواهد داد.
اگر این سرویس را به یک برنامه‌ی ASP.NET Core معرفی کنیم:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IMyDisposableService, MyDisposableService>();
و سپس به نحو متداولی از آن در یک کنترلر استفاده کنیم:
namespace CoreIocSample02.Controllers
{
    public class HomeController : Controller
    {
        private readonly IMyDisposableService _myDisposableService;

        public HomeController(IMyDisposableService myDisposableService)
        {
            _myDisposableService = myDisposableService;
        }

        public IActionResult Index()
        {
            _myDisposableService.Run();
            return View();
        }
در اینجا منظور از نحو‌ه‌ی متداول، همان تزریق در سازنده‌ی کلاس و درخواست وهله‌ای از این سرویس از IoC Container است؛ بجای ایجاد مستقیم آن.
در ادامه با اجرای برنامه، اگر به لاگ‌های آن دقت کنیم، این خروجی قابل مشاهده خواهد بود:
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "Index", controller = "Home"}. Executing action CoreIocSample02.Controllers.HomeController.Index (CoreIocSample02)
info: CoreIocServices.MyDisposableService[0]
      + MyDisposableService was created
.
.
.  
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'CoreIocSample02.Controllers.HomeController.Index (CoreIocSample02)'
info: CoreIocServices.MyDisposableService[0]
      - MyDisposableService was disposed!
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 1316.4719ms 200 text/html; charset=utf-8
در ابتدای اجرای درخواست، پیام MyDisposableService was created ظاهر شده‌است (پیام صادر شده‌ی از سازنده‌ی سرویس) و جائیکه پیام Executed endpoint یا پایان درخواست جاری لاگ شده، بلافاصله پیام MyDisposableService was disposed نیز مشاهده می‌شود که از متد Dispose سرویس درخواستی صادر شده‌است.
بنابراین IoC Container، به صورت خودکار، کار Dispose این سرویس IDisposable را نیز انجام داده‌است.


Dispose خودکار وهله‌هایی که توسط IoC Container ایجاد نشده‌اند

اگر ایجاد اشیاء از نوع IDisposable را خودتان و خارج از دید IoC Container توکار ASP.NET Core انجام می‌دهید، از مزیت پاکسازی خودکار منابع توسط آن‌ها در پایان درخواست محروم خواهید شد، اما ... برای رفع این مشکل نیز متد context.Response.RegisterForDispose پیش بینی شده‌است. اگر شیءای از نوع IDisposable را توسط این متد به ASP.NET Core معرفی کنید، در پایان درخواست به صورت خودکار Dispose خواهد شد.
یک مثال: فرض کنید یک StreamWriter را داخل یک میان‌افزار ایجاد کرده‌اید، اما آن‌را Dispose نکرده‌اید:
namespace CoreIocSample02
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                var writer = File.CreateText(Path.GetTempFileName());
                context.Response.RegisterForDispose(writer);
                context.Items["filewriter"] = writer;
                await writer.WriteLineAsync("some important information");
                await writer.FlushAsync();
                await next();
            });
در این مثال، شیء writer به context.Items انتساب داده شده‌است تا در سایر قسمت‌های pipeline جاری نیز قابل دسترسی باشد. یعنی از آن می‌توان داخل یک اکشن متد نیز استفاده کرد. نکته‌ی مهم آن، معرفی این شیء به متد context.Response.RegisterForDispose است که سبب خواهد شد پس از پایان کار درخواست، به صورت خودکار writer را Dispose کند.
اکنون در ادامه، در اکشن متد WriteLog یک کنترلر دلخواه، کار ثبت وقایع با دریافت این writer از HttpContext.Items قابل انجام است؛ چون هنوز طول عمر درخواست جاری پایان نیافته و شیء writer به صورت خودکار Dispose نشده‌است:
namespace CoreIocSample02.Controllers
{
    public class HomeController : Controller
    {
        public async Task<IActionResult> WriteLog()
        {
            var writer = HttpContext.Items["filewriter"] as StreamWriter;
            if (writer != null)
            {
                await writer.WriteLineAsync("more important information");
                await writer.FlushAsync();
            }
            return View();
        }
 
روش صحیح Dispose اشیایی با طول عمر Scoped، در خارج از طول عمر یک درخواست ASP.NET Core

زمانیکه به صورت متداولی از سیستم تزریق وابستگی‌های ASP.NET Core استفاده می‌کنیم، به ازای هر درخواست HTTP رسیده، یک Scope از نوع IServiceScopeFactory ایجاد می‌شود و با پایان درخواست، این Scope نیز Dispose خواهد شد. به این ترتیب هر سرویس ایجاد شده‌ی درون این Scope نیز Dispose می‌شود؛ کاری شبیه به عملیات زیر:
using(var scope = serviceProvider.CreateScope())
{
   var provider = scope.ServiceProvider;
   var resolvedService = provider.GetRequiredService(someType);
   // Use resolvedService...
}
در این بین سرویس‌های Singleton به هیچ Scope ای منتسب نمی‌شوند و طول عمر آن‌ها توسط root container مدیریت می‌شود و زمانیکه این ServiceProvider یا root container به پایان کار خودش برسد، با dispose شدن آن، سرویس‌های Singleton آن نیز dispose خواهند شد.

مشکل! اگر از سرویس فرضی IOperationScoped با طول عمر Scoped در متدهای مختلف کلاس آغازین برنامه استفاده کنیم (مانند DbContext برنامه)، طول عمری را که دریافت خواهیم کرد singleton خواهد بود و نه Scoped؛ چون درون یک scopeFactory.CreateScope ایجاد شده‌ی به صورت خودکار توسط یک درخواست قرار نداریم. بنابراین هر درخواست وهله‌ای از سرویس IOperationScoped با طول عمر Scoped، تنها همان وهله‌ی ابتدایی آن‌را باز می‌گرداند و singleton رفتار می‌کند؛ چون scope ایی ایجاد و تخریب نشده‌است.
در یک چنین مواردی، برای اطمینان حاصل کردن از dispose شدن سرویس در پایان کار، نیاز است مراحل ایجاد scope و dispose آن‌را به صورت دستی به نحو ذیل مدیریت کنیم:
public void Configure(IApplicationBuilder app, 
                      ILoggerFactory loggerFactory, 
                      IServiceScopeFactory scopeFactory) 
{
   using (var scope = scopeFactory.CreateScope()) 
   { 
     var initializer = scope.ServiceProvider.GetService<IOperationScoped>(); 
     initializer.SeedAsync().Wait(); 
   } 
}


Dispose کردن سرویس‌های IDisposable در برنامه‌های Console

اگر همین سرویس IMyDisposableService را در مثال برنامه‌ی کنسول قسمت اول استفاده کنیم:
var myDisposableService = serviceProvider.GetService<IMyDisposableService>();
myDisposableService.Run();
در پایان کار برنامه، شاهد پیام MyDisposableService was disposed نخواهیم بود. به همین جهت در اینجا نیز می‌توانیم شبیه به کاری که در ASP.NET Core در پشت صحنه رخ می‌دهد، عمل کنیم:
در برنامه‌ی کنسول، کار ایجاد serviceProvider را خودمان انجام دادیم:
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
متد BuildServiceProvider خروجی از نوع کلاس ServiceProvider را دارد؛ با این امضاء:
namespace Microsoft.Extensions.DependencyInjection
{
    public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback
    {
        public void Dispose();
        public object GetService(Type serviceType);
    }
}
همانطور که مشاهده کنید، کلاس ServiceProvider نیز اینترفیس IDisposable را پیاده سازی می‌کند. بنابراین برای آزاد سازی صحیح منابع وابسته‌ی به آن، باید متد Dispose آن‌را نیز فراخوانی کرد:
namespace CoreIocSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
            using (var serviceProvider = serviceCollection.BuildServiceProvider())
            {
                var myDisposableService = serviceProvider.GetService<IMyDisposableService>();
                myDisposableService.Run();

                var testService = serviceProvider.GetService<ITestService>();
                testService.Run();
            }
        }
در اینجا serviceProvider را داخل یک using statement قرار داده‌ایم. اینبار اگر برنامه را اجرا کنیم، پس از پایان کار برنامه، پیام MyDisposableService was disposed نیز ظاهر می‌شود. ServiceProvider ایجاد شده یا همان root container، در زمان Dispose، تمام اشیایی را هم که توسط آن مدیریت شده‌اند و نیاز به Dispose دارند، Dispose می‌کند.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-02.zip
نظرات مطالب
EF Code First #4
- شما نباید دستی تغییری در دیتابیس ایجاد کنید. این روش Code first است. تغییرات باید شامل افزودن خاصیت به کلاس‌ها باشند.
- نباید دستور پاورشلی رو اجرا کنید اگر AutomaticMigrationsEnabled فعال است؛ چون سبب بروز تداخل می‌شود.
- روش Code first، کار به روز رسانی بانک اطلاعاتی رو تا زمان اجرای اولین کوئری به تاخیر می‌اندازد (اینطوری طراحی شده تا آغاز برنامه سریع به نظر برسد). روش دیگری هم وجود داره تا این مساله رو تغییر داد:
«وادار کردن EF Code first به ساخت بانک اطلاعاتی پیش از شروع به کار برنامه»
نظرات مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
اگر نسخه Node >= 8.10  و  npm >= 5.6 را نصب دارید. با دستور زیر میتوان پروژه ای ایجاد نمود:
npx create-react-app my-app
cd my-app
npm start
ابزار npx  یک package runner می باشد که از نسخه npm 5.2 به بعد در دسترس می‌باشد.