1) فراموش میکنیم تا اسکریپت اصلی jQuery را به درستی پیوست و مسیردهی کنیم.
2) مسیر Generic handler دیگری را ذکر میکنیم.
3) مسیرهای تصاویری را که Image slider باید نمایش دهد، کاملا بیربط ذکر میکنیم.
4) خروجی JSON نامربوطی را بازگشت میدهیم.
5) یکبار هم یک استثنای عمدی دستی را در بین کدها قرار خواهیم داد.
و ... بعد سعی میکنیم با استفاده از Firebug عیوب فوق را یافته و اصلاح کنیم؛ تا به یک برنامه قابل اجرا برسیم.
معرفی برنامهای که کار نمیکند!
یک برنامه ASP.NET Empty web application را آغاز کنید. سپس سه پوشه Scripts، Content و Images را به آن اضافه نمائید. در این پوشهها، اسکریپتهای نمایش دهنده تصاویر، Css آن و تصاویری که قرار است نمایش داده شوند، قرار میگیرند:
سپس یک فایل default.aspx و یک فایل OrbitHandler.ashx را نیز به پروژه با محتویات ذیل اضافه کنید: (در این دو فایل، 5 مورد مشکل ساز یاد شده لحاظ شدهاند)
محتویات فایل OrbitHandler.ashx.cs مطابق کدهای ذیل است:
using System.Collections.Generic; using System.IO; using System.Web; using System.Web.Script.Serialization; namespace OrbitWebformsTest { public class Picture { public string Title { set; get; } public string Path { set; get; } } public class OrbitHandler : IHttpHandler { IList<Picture> PicturesDataSource() { var results = new List<Picture>(); var path = HttpContext.Current.Server.MapPath("~/Images"); foreach (var item in Directory.GetFiles(path, "*.*")) { var name = Path.GetFileName(item); results.Add(new Picture { Path = /*"Images/" + name*/ name, Title = name }); } return results; } public void ProcessRequest(HttpContext context) { var items = PicturesDataSource(); var json = /*new JavaScriptSerializer().Serialize(items)*/ string.Empty; throw new InvalidDataException("همینطوری"); context.Response.ContentType = "text/plain"; context.Response.Write(json); } public bool IsReusable { get { return false; } } } }
همچنین کدهای صفحه ASPX ایی که قرار است (به ظاهر البته) از این Generic handler استفاده کند به نحو ذیل است:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="OrbitWebformsTest._default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <link href="Content/orbit-1.2.3.css" rel="stylesheet" type="text/css" /> <script src="Script/jquery-1.5.1.min.js" type="text/javascript"></script> <script src="Scripts/jquery.orbit-1.2.3.min.js" type="text/javascript"></script> </head> <body> <form id="form1" runat="server"> <div id="featured"> </div> </form> <script type="text/javascript"> $(function () { $.ajax({ url: "Handler.ashx", contentType: "application/json; charset=utf-8", success: function (data) { $.each(data, function (i, b) { var str = '<img src="' + b.Path + '" alt="' + b.Title + '"/>'; $("#featured").append(str); }); $('#featured').orbit(); }, dataType: "json" }); }); </script> </body> </html>
مراحل عیب یابی برنامهای که کار نمیکند!
ابتدا برنامه را در فایرفاکس باز کرده و سپس افزونه Firebug را با کلیک بر روی آیکن آن، بر روی سایت فعال میکنیم. سپس یکبار بر روی دکمه F5 کلیک کنید تا مجددا مراحل بارگذاری سایت تحت نظر افزونه Firebug فعال شده، طی شود.
اولین موردی که مشهود است، نمایش عدد 3، کنار آیکن فایرباگ میباشد. این عدد به معنای وجود خطاهای اسکریپتی در کدهای ما است.
برای مشاهده این خطاها، بر روی برگه Console آن کلیک کنید:
بله. مشخص است که مسیر دهی فایل jquery-1.5.1.min.js صحیح نبوده و همین مساله سبب بروز خطاهای اسکریپتی گردیده است. برای اصلاح آن سطر زیر را در برنامه تغییر دهید:
<script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید. اینبار در برگه Console و یا در برگه شبکه فایرباگ، خطای یافت نشدن Generic handler نمایان میشوند:
برای رفع آن به فایل default.aspx مراجعه و بجای معرفی Handler.ashx، نام OrbitHandler.ashx را وارد کنید.
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید.
اگر به برگه کنسول دقت کنیم، بروز استثناء در کدها تشخیص داده شده و همچنین در برگه Response پاسخ دریافتی از سرور، جزئیات صفحه خطای بازگشتی از آن نیز قابل بررسی و مشاهده است.
اینبار به فایل OrbitHandler.ashx.cs مراجعه کرده و سطر throw new InvalidDataException را حذف میکنیم. در ادامه برنامه را کامپایل و مجددا اجرا خواهیم کرد.
با اجرای مجدد سایت، تبادل اطلاعات صحیحی با فایل OrbitHandler.ashx برقرار شده است، اما خروجی خاصی قابل مشاهده نیست. بنابراین بازهم سایت کار نمیکند.
برای رفع این مشکل، متد ProcessRequest را به نحو ذیل تغییر خواهیم داد:
public void ProcessRequest(HttpContext context) { var items = PicturesDataSource(); var json = new JavaScriptSerializer().Serialize(items); context.Response.ContentType = "text/plain"; context.Response.Write(json); }
بله. تمام تنظیمات به نظر درست هستند، اما در برگه شبکه فایرباگ تعدادی خطای 404 و یا «یافت نشد»، مشاهده میشوند. مشکل اینجا است که مسیرهای بازگشت داده شده توسط متد Directory.GetFiles، مسیرهای مطلقی هستند؛ مانند c:\path\images\01.jpg و جهت نمایش در یک وب سایت مناسب نمیباشند. برای تبدیل آنها به مسیرهای نسبی، اینبار کدهای متد تهیه منبع داده را به نحو ذیل ویرایش میکنیم:
IList<Picture> PicturesDataSource() { var results = new List<Picture>(); var path = HttpContext.Current.Server.MapPath("~/Images"); foreach (var item in Directory.GetFiles(path, "*.*")) { var name = Path.GetFileName(item); results.Add(new Picture { Path = "Images/" + name, Title = name }); } return results; }
اینبار اگر برنامه را اجرا کنیم، بدون مشکل کار خواهد کرد.
بنابراین در اینجا مشاهده کردیم که اگر «برنامهای مبتنی بر jQuery کار نمیکند»، چگونه باید قدم به قدم با استفاده از فایرباگ و امکانات آن، به خطاهایی که گزارش میدهد و یا مسیرهایی را که یافت نشد بیان میکند، دقت کرد تا بتوان برنامه را عیب یابی نمود.
سؤال مهم: اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب میشوند؟
اگر به برگه اسکریپتها در کنسول فایرباگ مراجعه کنیم، امکان قرار دادن breakpoint بر روی سطرهای کدهای جاوا اسکریپتی نمایش داده شده نیز وجود دارد:
در اینجا همانند VS.NET میتوان برنامه را در مرورگر اجرا کرده و تگهای تصویر پویای تولید شده را پیش از اضافه شدن به صفحه، مرحله به مرحله بررسی کرد. به این ترتیب بهتر میتوان دریافت که آیا src بازگشت داده شده از سرور فرمت صحیحی دارد یا خیر و آیا به محل مناسبی اشاره میکند یا نه. همچنین در برگه HTML آن، عناصر پویای اضافه شده به صفحه نیز بهتر مشخص هستند:
کمی شوخی با IE6
اخیرا عدهای از همکاران عنوان میکنند که "لطفا" از IE6 استفاده نکنید، یا یک پیغام "لطیف" را به کاربران IE6 نمایش میدهند و امثال آن. نظر شما در مورد استفاده از روشهای زیر چیست؟ :-)
مثال زیر را در نظر بگیرید:
<html>
<head>
<title>crash-1</title>
<style type="text/css">
* {position: relative;}
</style>
</head>
<body>
<table><input></table>
</body>
</html>
<style>*{position:relative}</style><table><input></table>
<html>
<head>
<title>crash-2</title>
<script type="text/javascript">for (x in document.write) { document.write(x);}</script>
</head>
<body>
Test-2
</body>
</html>
مثال 3:
این مورد کمی خطرناک است و سبب هنگ کل سیستم عامل خواهد شد و همچنین مصرف شدید حافظه تا حد ممکن.
<html>
<head>
<title>crash-3</title>
<style type="text/css">
:link:hover {
color: #f00;
}
:link .images {
width: 300px;
display: none;
}
:link:hover .images {
position: absolute;
top: 10px;
left: 230px;
display: block;
}
</style>
</head>
<body>
<a href="#">Line 1
<div class="images">
Line 2: HOVERING OVER THIS LINE MAY CRASH THE BROWSER! <br>Line 3: What is happening
here?</div>
</a>
</body>
</html>
این مورد نیز سبب هنگ سیستم عامل میشود:
<!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" xml:lang="en-US" lang="en-US">
<head>
<style type="text/css">
a {
padding:2px;
white-space:nowrap;
position:relative;
}
</style>
</head>
<body>
<div>
<a href="#">Option 1</a>
<a href="#">Option 2</a>
</div>
</body>
</html>
شروع به کار با NBench
برای شروع به کار با NBench، ابتدا نیاز است دو بستهی نیوگت ذیل را نصب کرد:
PM> Install-Package NBench PM> Install-Package NBench.Runner
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void AddMemoryMeasurement() { const int numberOfAdds = 1000000; var dictionary = new Dictionary<int, int>(); for (var i = 0; i < numberOfAdds; i++) { dictionary.Add(i, i); } } [PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [GcMeasurement(GcMetric.TotalCollections, GcGeneration.AllGc)] public void MeasureGarbageCollections() { var dataCache = new List<int[]>(); for (var i = 0; i < 500; i++) { for (var j = 0; j < 10000; j++) { var data = new int[100]; dataCache.Add(data.ToArray()); } dataCache.Clear(); } }
سپس هر متد تست به ویژگی PerfBenchmark مزین میشود. در اینجا RunMode.Iterations به این معنا است که خودمان قصد داریم در طی یک حلقه، تعداد بار انجام را مشخص کنیم.
ویژگی MemoryMeasurement برای اندازه گیری حافظهی مصرفی یک قطعه کد و GcMeasurement برای اندازه گیری فشار بر روی Garbage collector بکار میرود.
اجرای آزمونهای NBench
پس از تهیهی دو متد فوق، به پوشهی packages\NBench.Runner.0.3.4\lib\net45 مراجعه کنید. یک فایل exe در آن موجود است که کار یافتن و اجرای آزمونهای NBench را انجام میدهد. به عنوان پارامتر آن تنها کافی است مسیر اسمبلی برنامه (فایل exe و یا dll) را به آن ارسال کنیم:
D:\Prog\NBenchSample\packages\NBench.Runner.0.3.4\lib\net45\NBench.Runner.exe "D:\Prog\NBenchSample\NBenchSample\bin\Release\NBenchSample.exe"
--------------- RESULTS: NBenchSample.Program+AddMemoryMeasurement --------------- TotalBytesAllocated: Max: 47,842,944.00 bytes, Average: 42,002,757.60 bytes, Min: 41,353,848.00 bytes, StdDev: 2,052,032.33 bytes TotalBytesAllocated: Max / s: 359,074,078.19 bytes, Average / s: 311,474,786.96 bytes, Min / s: 300,926,928.79 bytes, StdDev / s: 16,869,581.62 bytes --------------- RESULTS: NBenchSample.Program+MeasureGarbageCollections --------------- TotalCollections [Gen0]: Max: 708.00 collections, Average: 702.80 collections, Min: 697.00 collections, StdDev: 3.65 collections TotalCollections [Gen0]: Max / s: 111.55 collections, Average / s: 109.87 collections, Min / s: 107.88 collections, StdDev / s: 1.28 collections TotalCollections [Gen1]: Max: 338.00 collections, Average: 334.60 collections, Min: 330.00 collections, StdDev: 2.41 collections TotalCollections [Gen1]: Max / s: 53.61 collections, Average / s: 52.31 collections, Min / s: 51.10 collections, StdDev / s: 0.70 collections TotalCollections [Gen2]: Max: 32.00 collections, Average: 24.80 collections, Min: 18.00 collections, StdDev: 4.73 collections TotalCollections [Gen2]: Max / s: 4.91 collections, Average / s: 3.87 collections, Min / s: 2.86 collections, StdDev / s: 0.72 collections
نکتهای در مورد اندازه گیری فشار حافظه
حافظه توسط سیستم عامل، به صورت صفحات تخصیص داده میشود. برای مثال اگر شما به 12 بایت نیاز داشته باشید، سیستم عامل ممکن است 8 کیلوبایت را جهت کاهش تعداد بار تخصیصهای حافظه و بالا بردن سرعت کار، در اختیار برنامه قرار دهد. بنابراین جهت رسیدن به بهترین نتیجه، در اینجا بهتر است تعداد زیادی شیء را مورد آزمایش قرار داد. برای مثال در آزمایش فوق بجای افزودن یک آیتم به دیکشنری، افزودن میلیونها شیء، نویز استراتژی تخصیص حافظهی توسط سیستم عامل را به حداقل میرساند.
شبیه به همین استراتژی، در پیاده سازی Dictionary نیز بکارگرفته شدهاست:
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void AddMemoryMeasurement_With_initial_Size() { const int numberOfAdds = 1000000; var dictionary = new Dictionary<int, int>(numberOfAdds); for (var i = 0; i < numberOfAdds; i++) { dictionary.Add(i, i); } }
--------------- RESULTS: NBenchSample.Program+AddMemoryMeasurement_With_initial_Size --------------- TotalBytesAllocated: Max: 23,245,912.00 bytes, Average: 23,245,912.00 bytes, Min: 23,245,912.00 bytes, StdDev: 0.00 bytes TotalBytesAllocated: Max / s: 394,032,435.34 bytes, Average / s: 389,108,363.43 bytes, Min / s: 378,502,981.34 bytes, StdDev / s: 5,575,519.09 bytes
علت اینجا است که دیکشنری در پشت صحنه، از یک متد ReSize استفاده میکند که شبیه به سیستم عامل، بیشتر از مقدار مورد نیاز جهت ذخیرهی اشیاء، برای کاهش تعداد بار تخصیصهای حافظه، حافظه به خود اختصاص میدهد. به همین جهت زمانیکه اندازهی اولیه را مشخص کردهایم، کار تخصیص حافظهی بیش از اندازهی این شیء، به شدت کاهش یافتهاست.
بررسی متد MeasureGarbageCollections
در متد MeasureGarbageCollections، مقدار زیادی شیء بر روی heap ایجاد شده و GC را وادار به عکس العمل شدید میکند.
حلقهی داخلی ایجاد شده نیز تعداد زیادی شیء را در جهت پاکسازی GC تخصیص میدهد. این پاکسازی در مرحلهای به نام generation 0 صورت میگیرد.
اشیاء اضافه شدهی به لیست، طول عمر بیشتری دارند (تا پایان حلقه). بنابراین از garbage collection at generation 0 جان سالم به در خواهند برد و در garbage collection at generation 1 به عمر آنها پایان داده خواهد شد. هرچند ممکن است تعدادی از آنها پاکسازی نشده و تا پایان full garbage collection (generation 2) باقی بمانند.
در آزمایش انجام شده، با ذکر GcGeneration.AllGc، هر سه مورد Gen0 تا Gen2 اندازه گیری خواهند شد. عموما اندازه گیری Gen0 و Gen1 مهم نیستند و اینها خیلی زود به پایان خواهند رسید. اگر تعداد بار رخدادن Gen2 زیاد بود (یا اصلا وجود داشت)، میتواند سبب بروز مشکلات کارآیی شدیدی گردد.
بنابراین میتوان بجای تنظیم GcGeneration.AllGc، صرفا از GcGeneration.Gen2 استفاده کرد.
اندازهگیری Throughput یا تعداد بار اجرای یک متد در ثانیه
روش دیگر کار با فریم ورک NBench، ایجاد یک کلاس مخصوص و سپس افزودن متدهای Setup مزین به PerfSetup، متد Cleanup مزین به PerfCleanup و سپس تعدادی متد اندازه گیری کارآیی توسط ویژگی PerfBenchmark است. در اینجا برای اندازهگیری سرعت اجرای متدها، از ویژگی CounterThroughputAssertion استفاده خواهد شد که پارامتر اول آن نام یک شمارشگر است. این شمارشگر در متد Setup ایجاد میشود (با یک نام دلخواه).
public class DictionaryThroughputTests { private readonly Dictionary<int, int> _dictionary = new Dictionary<int, int>(); private const string AddCounterName = "AddCounter"; private Counter _addCounter; private int _key; private const int AverageOperationsPerSecond = 20000000; [PerfSetup] public void Setup(BenchmarkContext context) { _addCounter = context.GetCounter(AddCounterName); _key = 0; } [PerfBenchmark(RunMode = RunMode.Throughput, TestMode = TestMode.Test)] [CounterThroughputAssertion(AddCounterName, MustBe.GreaterThan, AverageOperationsPerSecond)] public void AddThroughput_ThroughputMode(BenchmarkContext context) { _dictionary.Add(_key++, _key); _addCounter.Increment(); } [PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Test)] [CounterThroughputAssertion(AddCounterName, MustBe.GreaterThan, AverageOperationsPerSecond)] public void AddThroughput_IterationsMode(BenchmarkContext context) { for (var i = 0; i < AverageOperationsPerSecond; i++) { _dictionary.Add(i, i); _addCounter.Increment(); } } [PerfCleanup] public void Cleanup(BenchmarkContext context) { _dictionary.Clear(); } }
--------------- RESULTS: NBenchSample.DictionaryThroughputTests+AddThroughput_ThroughputMode --------------- [Counter] AddCounter: Max: 575,654.00 operations, Average: 575,654.00 operations, Min: 575,654.00 operations, StdDev: 0.00 operations [Counter] AddCounter: Max / s: 7,205,997.59 operations, Average / s: 7,163,894.30 operations, Min / s: 7,075,316.79 operations, StdDev / s: 42,518.20 operations --------------- RESULTS: NBenchSample.DictionaryThroughputTests+AddThroughput_IterationsMode --------------- [Counter] AddCounter: Max: 20,000,000.00 operations, Average: 20,000,000.00 operations, Min: 20,000,000.00 operations, StdDev: 0.00 operations [Counter] AddCounter: Max / s: 7,409,380.61 operations, Average / s: 7,250,991.24 operations, Min / s: 6,880,938.73 operations, StdDev / s: 148,085.19 operations
در اینجا برای گزارش دادن، عددهای Average و Average / s باید مورد استفاده قرار گیرند.
// روش صحیح تعریف فونت var systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); FontFactory.Register(Path.Combine(systemRoot, "fonts\\tahoma.ttf"));
var systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); FontFactory.Register(Path.Combine(systemRoot, "c:\\windows\\fonts\\tahoma.ttf"));
- بله؛ قدرت پردازش CSS در XML Worker آن خیلی بهتر است از HTML Worker.
- در مورد میزان چرخش جدول، RunDirection = PdfWriter.RUN_DIRECTION_RTL را با حالت LTR هم تست کنید (PdfWriter.RUN_DIRECTION_LTR ).
- سرعت بالاتر بارگذاری و اجرای کدها
- استفاده از آخرین فناوریهای وب مانند ES 6 و وب کامپوننتها با پشتیبانی تا IE 9.
- سطح API آن با طراحی جدید آن، به شدت کاهش یافته و خلاصه شدهاست. همین امر یادگیری آنرا نیز سادهتر میکند.
برای یادگیری Angular 2 نیازی به فراگیری Angular 1 نیست. در اینجا با آشنایی با TypeScript، به این نتیجه خواهید رسید که برنامههای Angular 2 چیزی بیشتر از یک مثال عملی TypeScript نیستند. زبان TypeScript، زبان اول و توصیه شدهی کار با Angular 2 است و مزیت آن دسترسی به تمام قابلیتهای ES 6 است؛ با این تفاوت که کامپایلر TypeScript قادر است آنها را به ES 5 یا نگارش فعلی جاوا اسکریپت که توسط تمام مرورگرها پشتیبانی میشود، ترجمه و تبدیل کند. به این نحو به یک طراحی شیءگرا، مدرن و با قابلیت نگهداری بالا خواهید رسید که با تمام مرورگرهای جدید نیز سازگار است.
بنابراین پیشنیازهای اصلی کار با Angular 2، فراگیری ES 6 و TypeScript هستند که دو سری ویژه و مختص به آنها در سایت جاری تهیه شدهاند:
«مبانی ES 6»
«مبانی TypeScript»
در این قسمت قصد داریم پیشنیازهای دریافت و نصب اجزاء و وابستگیهای AngularJS 2 را به همراه TypeScript، در ویژوال استودیو 2015 بررسی کنیم. البته استفاده از ویژوال استودیو در اینجا ضروری نیست و اساسا AngularJS وابستگی به ویژوال استودیو ندارد. اگر به دنبال پشتیبانی بهتری از TypeScript هستید، VSCode رایگان، سورس باز و چند سکویی، گزینهی بسیار بهتری است نسبت به ویژوال استودیوی کامل. البته این مورد تعجبی هم ندارد؛ از این جهت که VSCode با TypeScript نوشته شدهاست.
اهمیت آشنایی با npm
اگر در طی سالهای اخیر با ویژوال استودیو کار کرده باشید، به طور قطع با NuGet نیز آشنا هستید. NuGet به عنوان یک package manager دات نتی شناخته میشود و کار آن دریافت وابستگیهای یک پروژه، از مخزنی مشخص و نصب و به روز رسانی خودکار آنها است. اما این روزها خارج از محیط توسعهی مایکروسافت، مدیرهای بستههای دیگری مانند Bower نیز برای نصب و به روز رسانی بستههای front end مانند کتابخانههای CSS ایی و جاوا اسکریپتی، بسیار رواج پیدا کردهاند. Bower یکی از ابزارهای NodeJS است که توسط NPM یا NodeJS Package Manager نصب میشود. به این معنا که استفاده از Bower به معنای استفاده از NPM است. پیش از این از NPM صرفا جهت نصب بستههای مرتبط با NodeJS استفاده میشد، اما در طی یکسال اخیر، استفاده از NPM نیز برای مدیریت بستههای front end رواج پیدا کردهاست و در صدر مدیر بستههای نصب کتابخانههای front end قرار دارد. همچنین در این حالت شما تنها نیاز به یک ابزار و یک فایل تنظیمات آن خواهید داشت، بجای استفاده از چندین ابزار و چندین فایل تنظیمات جداگانه. به علاوه این روزها انجام کارهای جدی جاوا اسکریپتی بدون دانش NPM دیگر میسر نیست. بهترین ابزارها، کامپایلرها، فشرده کنندههای front end و امثال آنها، تحت NodeJS اجرا میشوند. برای کار با AngularJS 2.0 و دریافت وابستگیهای آن نیز استفاده از npm، روش پیش فرض و توصیه شدهاست.
کار با npm جهت دریافت وابستگیهای کتابخانههای front end
برای کار با npm یا میتوان از دستورات خط فرمان آن به صورت مستقیم استفاده کرد و یا از امکانات یکپارچگی آن با ویژوال استودیو نیز بهره گرفت که ما در ادامه از این روش استفاده خواهیم کرد. البته توانمندیهای خط فرمان npm فراتر است از امکانات توکار VS.NET، اما در اینجا نیازی به آنها نیست و هدف صرفا دریافت و به روز رسانی وابستگیهای مرتبط است.
ویژوال استودیوی 2015 به همراه ابزارهای NodeJS ارائه میشود. اما مشکل اینجا است که این ابزارها هم اکنون قدیمی شدهاند و تطابقی با آخرین نگارش ابزارهای NodeJS ندارند. برای نمونه حین نصب وابستگیهای AngularJS 2.0 که یکی از آنها RxJS است، یک چنین خروجی را در پنجرهی output ویژوال استودیو مشاهده میکنید:
npm WARN engine rxjs@5.0.0-beta.6: wanted: {"npm":">=2.0.0"} (current: {"node":"v0.10.31","npm":"1.4.9"})
همانطور که در تصویر نیز ملاحظه میکنید، به مسیر Tools->Options->Projects and Solutions->External Web Tools مراجعه کرده و متغیر محیطی PATH ویندوز را به ابتدای لیست منتقل کنید. به این صورت ابزارهای به روز شدهی خط فرمان، مقدم خواهند شد بر ابزارهای قدیمی به همراه نگارش فعلی ویژوال استودیو.
البته فرض بر این است که آخرین نگارش nodejs را از آدرس https://nodejs.org/en دریافت و نصب کردهاید. با نصب آن، برنامه npm، در خط فرمان ویندوز به صورت مستقیم قابل استفاده خواهد بود؛ از این جهت که مسیر آن به PATH ویندوز اضافه شدهاست:
افزودن فایل project.json به پروژه
تا اینجا فرض بر این است که nodejs را از سایت آن دریافت و نصب کردهاید. همچنین متغیر PATH را در External Web Tools ویژوال استودیو به ابتدای لیست آن منتقل کردهاید تا همواره از آخرین نگارش npm نصب شدهی در سیستم، استفاده شود.
پس از آن همانند نیوگت که از فایل xml ایی به نام packages.config برای ثبت آخرین تغییرات خودش استفاده میکند، npm نیز از فایلی به نام package.json برای ذکر وابستگیهای مورد نیاز پروژه بهره میبرد. برای افزودن آن، از منوی Project گزینهی Add new item را انتخاب کرده و سپس npm را در آن جستجو کنید:
در اینجا نام پیش فرض package.json را پذیرفته و این فایل را به پروژه و ریشهی آن اضافه کنید.
اگر گزینهی فوق را در لیست Add new item مشاهده نمیکنید، مهم نیست. یک فایل متنی را به نام package.json به ریشهی پروژهی جاری اضافه کنید.
در ادامه محتوای این فایل را به نحو ذیل تغییر دهید:
{ "name": "asp-net-mvc5x-angular2x", "version": "1.0.0", "author": "DNT", "description": "", "scripts": {}, "license": "Apache-2.0", "dependencies": { "jquery": "2.2.3", "angular2": "2.0.0-beta.15", "systemjs": "^0.19.26", "es6-promise": "^3.1.2", "es6-shim": "^0.35.0", "reflect-metadata": "0.1.2", "rxjs": "5.0.0-beta.2", "zone.js": "^0.6.12", "bootstrap": "^3.3.6" }, "devDependencies": { "typescript": "^1.8.9", "typings": "^0.8.1" }, "repository": { } }
چند نکته:
- هر زمانیکه این فایل را ذخیره کنید، بلافاصله کار دریافت این بستهها از اینترنت آغاز خواهد شد. جزئیات آنرا میتوان در پنجرهی output ویژوال استودیو مشاهده کرد (و حتما اینکار را جهت دیباگ کار انجام دهید. بسیاری از مشکلات و خطاها، در اینجا لاگ میشوند). بنابراین اگر قصد دارید کار همگام سازی تغییرات را انجام دهید، فقط کافی است دکمههای ctrl+s یا save را بر روی این فایل اعمال کنید.
- کاری که دکمهی ctrl+s در این فایل انجام میدهد، اعمال خودکار دستور npm install بر روی پوشهای است که package.json در آن قرار دارد. بنابراین برای دریافت این بستهها، روش خط فرمان آن، ابتدا وارد شدن به ریشهی پروژهی جاری و سپس صدور دستور npm install است (که نیازی به آن نیست و ویژوال استودیو اینکار را به صورت خودکار انجام میدهد).
- بستههای دریافت شده، در پوشهای به نام node_modules در ریشهی پروژه ذخیره میشوند:
برای مشاهدهی آنها میتوان بر روی دکمهی show all files در solution explorer کلیک نمائید.
- درون فایل package.json، پشتیبانی کاملی از intellisense وجود دارد. برای مثال اگر علاقمند بودید تا آخرین نگارش AngularJS را مشاهده کنید، پس از خاصیت angular2 در تنظیمات فوق، " را تایپ کنید تا منوی تکمیل کنندهای ظاهر شود:
بدیهی است این منو جهت دریافت آخرین اطلاعات، نیاز به اتصال به اینترنت را دارد.
البته همیشه آخرین شماره نگارش AngularJS 2.0 را در این آدرس نیز میتوانید مشاهده کنید: CHANGELOG.md
- در این فایل شماره نگارش آغاز شدهای با ^ مانند "3.1.2^" به این معنا است که اجازهی نصب نگارشهای جدیدتری از 3.1.2 نیز داده شدهاست.
به روز رسانی کامپایلر TypeScript
نیاز است یکبار مطلب «مبانی TypeScript؛ تنظیمات TypeScript در ویژوال استودیو» را جهت آشنایی با نحوهی به روز رسانی اجزای مرتبط با TypeScript مطالعه کنید.
افزودن فایل tsconfig.json به پروژه
مرحلهی بعد شروع به کار با AngularJS و TypeScript، انجام تنظیمات کامپایلر TypeScript است. برای این منظور از منوی پروژه، گزینهی Add new item، عبارت typescript را جستجو کرده و در لیست ظاهر شده، گزینهی TypeScript JSON Configuration File را با نام پیش فرض آن انتخاب کنید:
اگر این گزینهی ویژه را در لیست add new items ندارید، مهم نیست. یک فایل متنی را به نام tsconfig.json به ریشهی پروژهی جاری اضافه کنید.
پس از افزودن فایل tsconfig.json به ریشهی سایت، تنظیمات آنرا به نحو ذیل تغییر دهید:
{ "compileOnSave": true, "compilerOptions": { "target": "es5", "module": "system", "moduleResolution": "node", "noImplicitAny": false, "noEmitOnError": true, "removeComments": false, "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true }, "exclude": [ "node_modules", "wwwroot", "typings/main", "typings/main.d.ts" ] }
برای نمونه خاصیت compileOnSave سبب خواهد شد تا پس از ذخیره سازی یک فایل ts، بلافاصله فایل js معادل آْن تولید شود. نوع ماژولها در اینجا به system.js تنظیم شدهاست و خروجی کدهای js آن از نوع ES 5 درنظر گرفته شدهاست. همچنین فعال سازی decorators نیز در اینجا صورت گرفتهاست. از این جهت که AngularJS2 استفادهی گستردهای را از این مفهوم، انجام میدهد.
در قسمت excludes به کامپایلر TypeScript اعلام شدهاست تا از یک سری از مسیرها مانند پوشهی node_modules که پیشتر آنرا تنظیم و دریافت کردیم، صرفنظر کند.
خلاصهی بحث
تا اینجا با نحوهی نصب پیشنیازهای AngularJS 2 و همچنین TypeScript آشنا شدیم. به صورت خلاصه:
- nmp اصلی را از سایت nodejs دریافت و نصب کنید.
- متغیر PATH را در ویژوال استودیو، به ابتدای لیست ابزارهای خارجی وب آن منتقل کنید تا از npm اصلی استفاده کند.
- فایل project.json را با محتوای ذکر شده، به ریشهی پروژه اضافه کنید. سپس یکبار آنرا save کنید تا وابستگیها را از اینترنت دریافت کند.
- اطمینان حاصل کنید که آخرین کامپایلر TypeScript را نصب کردهاید.
- فایل tsconfig.json را با محتوای ذکر شده، به ریشهی پروژه اضافه کنید.
گام سوم: افزودن یک button
... <div> <div> <input type="text" id="inputName" maxlength="15"> </div> <div> <button id="generateButton">Aye! Gimme a name!</button> </div> </div> ...
import 'dart:html'; ButtonElement genButton;
void main() { querySelector('#inputName').onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); }
... void setBadgeName(String newName) { querySelector('#badgeName').text = newName; }
... void generateBadge(Event e) { setBadgeName('Meysam Khoshbakht'); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { // To do: add some code here. } else { // To do: add some code here. } }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { genButton..disabled = false ..text = 'Aye! Gimme a name!'; } else { genButton..disabled = true ..text = 'Arrr! Write yer name!'; } }
گام چهارم: ایجاد کلاس PirateName
در این مرحله فقط کد مربوط به فایل dart را تغییر میدهیم. ابتدا کلاس PirateName را ایجاد میکنیم. با ایجاد نمونه ای از این کلاس، یک نام بصورت تصادفی انتخاب میشود و یا نامی بصورت اختیاری از طریق سازنده انتخاب میگردد.
نخست کتابخانه dart:math را به ابتدای فایل dart اضافه کنید
import 'dart:html'; import 'dart:math' show Random;
توضیحات
- با استفاده از کلمه کلیدی show، شما میتوانید فقط کلاسها، توابع و یا ویژگیهای مورد نیازتان را import کنید.
- کلاس Random یک عدد تصادفی را تولید میکند
در انتهای فایل کلاس زیر را تعریف کنید
... class PirateName { }
در داخل کلاس یک شی از کلاس Random ایجاد کنید
class PirateName { static final Random indexGen = new Random(); }
توضیحات
- با استفاده از static یک فیلد را در سطح کلاس تعریف میکنیم که بین تمامی نمونههای ایجاد شده از کلاس مشترک میباشد
- متغیرهای final فقط خواندنی میباشند و غیر قابل تغییر هستند.
- با استفاده از new میتوانیم سازنده ای را فراخوانی نموده و نمونه ای را از کلاس ایجاد کنیم
دو فیلد دیگر از نوع String و با نامهای _firstName و _appelation به کلاس اضافه میکنیم
class PirateName { static final Random indexGen = new Random(); String _firstName; String _appellation; }
متغیرهای خصوصی با (_) تعریف میشوند. Dart کلمه کلیدی private را ندارد.
دو لیست static به کلاس فوق اضافه میکنیم که شامل لیستی از name و appelation میباشد که میخواهیم آیتمی را بصورت تصادفی از آنها انتخاب کنیم.
class PirateName { ... static final List names = [ 'Anne', 'Mary', 'Jack', 'Morgan', 'Roger', 'Bill', 'Ragnar', 'Ed', 'John', 'Jane' ]; static final List appellations = [ 'Jackal', 'King', 'Red', 'Stalwart', 'Axe', 'Young', 'Brave', 'Eager', 'Wily', 'Zesty']; }
کلاس List میتواند شامل مجموعه ای از آیتمها میباشد که در Dart تعریف شده است.
سازنده ای را بصورت زیر به کلاس اضافه میکنیم
class PirateName { ... PirateName({String firstName, String appellation}) { if (firstName == null) { _firstName = names[indexGen.nextInt(names.length)]; } else { _firstName = firstName; } if (appellation == null) { _appellation = appellations[indexGen.nextInt(appellations.length)]; } else { _appellation = appellation; } } }
توضیحات
- سازنده تابعی همنام کلاس میباشد
- پارامترهایی که در {} تعریف میشوند اختیاری و Named Parameter میباشند. Named Parameterها پارمترهایی هستند که جهت مقداردهی به آنها در زمان فراخوانی، از نام آنها استفاده میشود.
- تابع nextInt() یک عدد صحیح تصادفی جدید را تولید میکند.
- جهت دسترسی به عناصر لیست از [] و شمارهی خانهی لیست استفاده میکنیم.
- ویژگی length تعداد آیتمهای موجود در لیست را بر میگرداند.
در این مرحله یک getter برای دسترسی به pirate name ایجاد میکنیم
class PirateName { ... String get pirateName => _firstName.isEmpty ? '' : '$_firstName the $_appellation'; }
توضیحات
- Getterها متدهای خاصی جهت دسترسی به یک ویژگی به منظور خواندن مقدار آنها میباشند.
- عملگر سه گانه :? دستور میانبر عبارت شرطی if-else میباشد
- $ یک کاراکتر ویژه برای رشتههای موجود در Dart میباشد و میتواند محتوای یک متغیر یا ویژگی را در رشته قرار دهد. در رشته '$_firstName the $_appellation' محتوای دو ویژگی _firstName و _appellation در رشته قرار گرفته و نمایش مییابند.
- عبارت (=> expr;) یک دستور میانبر برای { return expr; } میباشد.
تابع setBadgeName را بصورت زیر تغییر دهید تا یک پارامتر از نوع کلاس PirateName را به عنوان پارامتر ورودی دریافت نموده و با استفاده از Getter مربوط به ویژگی pirateName، مقدار آن را در badge name نمایش دهد.
void setBadgeName(PirateName newName) { querySelector('#badgeName').text = newName.pirateName; }
تابع updateBadge را بصورت زیر تغییر دهید تا یک نمونه از کلاس PirateName را با توجه به مقدار ورودی کاربر در فیلد input تولید نموده و تابع setBadgeName رافراخوانی نماید. همانطور که در کد مشاهده میکنید پارامتر ورودی اختیاری firstName در زمان فراخوانی با ذکر نام پارامتر قبل از مقدار ارسالی نوشته شده است. این همان قابلیت Named Parameter میباشد.
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(new PirateName(firstName: inputName)); ... }
تابع generateBadge را بصورت زیر تغییر دهید تا به جای نام ثابت Meysam Khoshbakht، از کلاس PirateName به منظور ایجاد نام استفاده کند. همانطور که در کد میبینید، سازندهی بدون پارامتر کلاس PirateName فراخوانی شده است.
void generateBadge(Event e) { setBadgeName(new PirateName()); }
همانند گام سوم برنامه را اجرا کنید و نتیجه را مشاهده نمایید.
روش ارائه شده در آن با ویندوزهای XP تا 7 نگارشهای 32 بیتی و 64 بیتی، بدون مشکل کار میکند. اما تاثیری بر روی ویندوز 8 و نگارشهای پس از آن نداشت.
تغییرات توابع GetDateFormatW و GetTimeFormatW در ویندوز اکسپلورر ویندوز 8
چه برنامهی ExplorerPCal و چه API Monitor را اگر با فعال سازی توابع GetDateFormatW و GetTimeFormatW اجرا کنید، هیچ خروجی خاصی را مشاهده نخواهید کرد. در ابتدا به نظر میرسد که ساختار ویندوز شاید تغییر کردهاست ... ولی اینطور نیست. فقط اینبار بجای فراخوانی این توابع از kernel32.dll، از یک dll مخفی در پوشهی System32 استفاده میشود. روش پیدا کردن آن نیز به صورت زیر است:
"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\dumpbin.exe" /imports c:\windows\explorer.exe > explorer.imports.txt
اگر خروجی این برنامه را که اکنون در فایل explorer.imports.txt ذخیره شدهاست، بررسی کنیم، به نتیجهی زیر خواهیم رسید:
api-ms-win-core-datetime-l1-1-1.dll 14015E848 Import Address Table 1401613E0 Import Name Table 0 time date stamp 0 Index of first forwarder reference 2 GetDateFormatW 1 GetDateFormatEx 4 GetTimeFormatEx
بنابراین تنها تغییری که باید در برنامهی ExplorerPCal داده شود، اضافه کردن مداخل جدید فوق است. در ویندوزهای قبل از 8، از نگارشهای Ex استفاده نمیشد. در اینجا هم از نگارشهای W و هم Ex دار استفاده شدهاست.
اگر خواستید این تغییرات را با برنامهی API Monitor بررسی کنید، فایل جدید api-ms-win-core-datetime-l1-1-1.xml ذیل را در پوشهی API\Windows آن کپی نمائید تا مداخل api-ms-win-core-datetime-l1-1-1.dll نیز به مجموعهی تعاریف آن اضافه شوند.
api-ms-win-core-datetime-l1-1-1.xml
حاصل نهایی، فایلهای اجرایی و سورس بهبود یافتهی برنامه را از اینجا میتوانید دریافت کنید:
شمسی ساز تاریخ اکسپلورر ویندوز
تاثیر آنرا نیز بر روی Explorer ویندوز 8، در تصاویر ذیل میتوانید ملاحظه نمائید:
معرفی 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>
دریافت سورس کامل این قسمت که حاوی فایلهای اصلی kendoui.professional.2014.2.1008 نیز میباشد:
KendoUI01.7z