We are thrilled to announce the highly anticipated .NET Conf 2024, a free, three-day virtual developer event celebrating the release of .NET 9. Co-organized by the .NET community and Microsoft, this annual tradition continues to grow, and we’re more excited than ever to bring you the latest innovations in .NET. Mark your calendars for November 12th to 14th, 2024, and prepare to be inspired by a wealth of knowledge, creativity, and community engagement.
برای تهیه یک RadioButtonList نیز میتوان از همان نکتهی CheckBoxList استفاده کرد: نام عناصر radio button اضافه شده به صفحه را یکسان وارد میکنیم. به این ترتیب یک گروه تشکیل خواهد شد و زمانیکه اطلاعات این عناصر به سرور ارسال میشود، اینبار بجای یک آرایه، تنها مقدار کنترل انتخاب شده، ارسال میگردد. یک مثال:
یک پروژه جدید و خالی ASP.NET MVC را آغاز کنید. سپس کنترلر Home و View خالی Index را نیز ایجاد نمائید. محتویات این دو را به نحو زیر تغییر دهید:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm1 (Normal)</legend>
@using (Html.BeginForm(actionName: "HandleForm1", controllerName: "Home"))
{
@:your favorite tech: <br />
@Html.RadioButton(name: "tech", value: ".NET", isChecked: true) @:DOTNET <br />
@Html.RadioButton(name: "tech", value: "JAVA", isChecked: false) @:JAVA <br />
@Html.RadioButton(name: "tech", value: "PHP", isChecked: false) @:PHP <br />
<input type="submit" value="Submit" />
}
</fieldset>
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult HandleForm1(string tech)
{
return RedirectToAction("Index");
}
}
}
در اینجا سه RadioButton با نامی یکسان در صفحه اضافه شدهاند. سپس داخل متد HandleForm1 یک breakpoint قرار دهید. اکنون برنامه را اجرا کنید و فرم را به سرور ارسال نمائید. پارامتر tech با value عنصر انتخابی مقدار دهی خواهد شد.
تهیه یک RadioButtonList عمومی
اطلاعات فوق را میتوان تبدیل به یک HtmlHelper با قابلیت استفاده مجدد نیز نمود:
@helper RadioButtonList(string groupName, IEnumerable<System.Web.Mvc.SelectListItem> items)
{
<div class="RadioButtonList">
@foreach (var item in items)
{
@item.Text
<input type="radio" name="@groupName"
value="@item.Value"
@if (item.Selected) { <text>checked="checked"</text> }
/>
<br />
}
</div>
}
برای مثال یک فایل را در مسیر app_code\Helpers.cshtml ایجاد کرده و اطلاعات فوق را به آن اضافه نمائید.
اینبار برای استفاده از آن خواهیم داشت:
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Tags = new[]
{
new SelectListItem { Text = ".NET", Value = "Val1", Selected = true },
new SelectListItem { Text = "JAVA", Value = "Val2", Selected = false },
new SelectListItem { Text = "PHP", Value = "Val3", Selected = false }
};
return View();
}
[HttpPost]
public ActionResult HandleForm2(string preferredTechnology)
{
return RedirectToAction("Index");
}
}
}
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm2 (Helper)</legend>
@using (Html.BeginForm(actionName: "HandleForm2", controllerName: "Home"))
{
@:your favorite tech: <br />
@Helpers.RadioButtonList("preferredTechnology", (SelectListItem[])ViewBag.Tags)
<input type="submit" value="Submit" />
}
</fieldset>
متد سفارشی تهیه شده، یک آرایه از SelectListItem ها را دریافت کرده و به صورت خودکار تبدیل به RadioButtonList میکند. بر اساس نام آن میتوان به مقدار انتخاب شده ارسالی به سرور در کنترلر مرتبط، دسترسی یافت.
تهیه یک Templated helper سفارشی
در عمل زمانیکه با مدلها کار میکنیم و اطلاعات برنامه قرار است Strongly typed باشند، مرسوم است لیستی از انتخابها را به صورت یک enum تعریف کنند. برای مثال مدل زیر را به برنامه اضافه کنید:
using System.ComponentModel.DataAnnotations;
namespace MvcApplication23.Models
{
public enum Gender
{
[Display(Name = "مرد")]
Male,
[Display(Name = "زن")]
Female,
}
public class User
{
[ScaffoldColumn(false)]
public int Id { set; get; }
[Display(Name = "نام")]
public string Name { set; get; }
[Display(Name = "جنسیت")]
[UIHint("EnumRadioButtonList")]
public Gender Gender { set; get; }
}
}
قصد داریم یک Templated helper سفارشی را به نام EnumRadioButtonList، ایجاد کنیم تا در زمان فراخوانی متد Html.EditorForModel، به صورت خودکار enum تعریف شده را به صورت یک RadioButtonList نمایش دهد.
برای این منظور فایل جدید Views\Shared\EditorTemplates\EnumRadioButtonList.cshtml را به برنامه اضافه کنید. محتوای آنرا به نحو زیر تغییر دهید:
@using System.ComponentModel.DataAnnotations
@using System.Globalization
@model Enum
@{
Func<Enum, string> getDescription = enumItem =>
{
var type = enumItem.GetType();
var memInfo = type.GetMember(enumItem.ToString());
if (memInfo != null && memInfo.Any())
{
var attrs = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
if (attrs != null && attrs.Any())
return ((DisplayAttribute)attrs[0]).GetName();
}
return enumItem.ToString();
};
var listItems = Enum.GetValues(Model.GetType())
.OfType<Enum>()
.Select(enumItem =>
new SelectListItem()
{
Text = getDescription(enumItem),
Value = enumItem.ToString(),
Selected = enumItem.Equals(Model)
});
string prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
int index = 0;
foreach (var li in listItems)
{
string fieldName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", prefix, index++);
<div class="editor-radio">
@Html.RadioButton(prefix, li.Value, li.Selected, new { @id = fieldName })
@Html.Label(fieldName, li.Text)
</div>
}
ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}
در اینجا به کمک Reflection به اطلاعات enum دریافتی دسترسی خواهیم داشت. بر این اساس میتوان نام عناصر آنرا یافت و تبدیل به یک RadioButtonList کرد. البته کار به همینجا ختم نمیشود. در این بین باید دقت داشت که ممکن است از ویژگی Display (مانند مدل نمونه فوق) بر روی تک تک عناصر یک enum نیز استفاده شود. به همین جهت این مورد نیز باید پردازش گردد.
نهایتا برای استفاده از این Templated helper سفارشی، کنترلر و View برنامه را به نحو زیر میتوان تغییر داد:
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication23.Models;
namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var user = new User { Id = 1, Name = "name 1", Gender = Gender.Male };
return View(user);
}
[HttpPost]
public ActionResult HandleForm3(User user)
{
return RedirectToAction("Index");
}
}
}
@model MvcApplication23.Models.User
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm3 (EditorForModel)</legend>
@using (Html.BeginForm(actionName: "HandleForm3", controllerName: "Home"))
{
@Html.EditorForModel()
<input type="submit" value="Submit" />
}
</fieldset>
برای استفاده از یک templated helper سفارشی چندین روش وجود دارد:
الف) همانند مثال فوق از ویژگی UIHint استفاده شود.
ب) نام فایل را به enum.cshtml تغییر دهیم. به این ترتیب از این پس کلیه enumها در صورت استفاده از متد Html.EditorForModel، به صورت خودکار تبدیل به یک RadioButtonList میشوند.
ج) متد زیر نیز همین کار را انجام میدهد:
@Html.EditorFor(model => model.EnumProperty, "EnumRadioButtonList")
مدل برنامه
در اینجا قصد داریم لیستی را با ساختار کلاس Product در اختیار Kendo UI گرید قرار دهیم:
namespace KendoUI03.Models { public class Product { public int Id { set; get; } public string Name { set; get; } public decimal Price { set; get; } public bool IsAvailable { set; get; } } }
پیشنیاز تامین داده مخصوص Kendo UI Grid
برای ارائه اطلاعات مخصوص Kendo UI Grid، ابتدا باید درنظر داشت که این گرید، درخواستهای صفحه بندی خود را با فرمت ذیل ارسال میکند. همانطور که مشاهده میکنید، صرفا یک کوئری استرینگ با فرمت JSON را دریافت خواهیم کرد:
/api/products?{"take":10,"skip":0,"page":1,"pageSize":10,"sort":[{"field":"Id","dir":"desc"}]}
{ "Data": [ {"Id":1500,"Name":"نام 1500","Price":2499.0,"IsAvailable":false}, {"Id":1499,"Name":"نام 1499","Price":2498.0,"IsAvailable":true} ], "Total":1500, "Aggregates":null }
میتوان برای تمام اینها، کلاس و Parser تهیه کرد و یا ... پروژهی سورس بازی به نام Kendo.DynamicLinq نیز چنین کاری را میسر میسازد که در ادامه از آن استفاده خواهیم کرد. برای نصب آن تنها کافی است دستور ذیل را صادر کنید:
PM> Install-Package Kendo.DynamicLinq
تامین کنندهی داده سمت سرور
همانند مطلب کار با Kendo UI DataSource ، یک ASP.NET Web API Controller جدید را به پروژه اضافه کنید و همچنین مسیریابیهای مخصوص آنرا به فایل global.asax.cs نیز اضافه نمائید.
using System.Linq; using System.Net.Http; using System.Web.Http; using Kendo.DynamicLinq; using KendoUI03.Models; using Newtonsoft.Json; namespace KendoUI03.Controllers { public class ProductsController : ApiController { public DataSourceResult Get(HttpRequestMessage requestMessage) { var request = JsonConvert.DeserializeObject<DataSourceRequest>( requestMessage.RequestUri.ParseQueryString().GetKey(0) ); var list = ProductDataSource.LatestProducts; return list.AsQueryable() .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter); } } }
ProductDataSource.LatestProducts صرفا یک لیست جنریک تهیه شده از کلاس Product است. در نهایت با استفاده از متد الحاقی جدید ToDataSourceResult، به صورت خودکار مباحث صفحه بندی سمت سرور به همراه مرتب سازی اطلاعات، صورت گرفته و اطلاعات نهایی با فرمت DataSourceResult بازگشت داده میشود. DataSourceResult نیز در Kendo.DynamicLinq تعریف شده و سه فیلد یاد شدهی Data، Total و Aggregates را تولید میکند.
تا اینجا کارهای سمت سرور این مثال به پایان میرسد.
تهیه View نمایش اطلاعات ارسالی از سمت سرور
اعمال مباحث بومی سازی
<head> <meta charset="utf-8" /> <meta http-equiv="Content-Language" content="fa" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Kendo UI: Implemeting the Grid</title> <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <!--شیوه نامهی مخصوص راست به چپ سازی--> <link href="styles/kendo.rtl.min.css" rel="stylesheet" /> <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.all.min.js" type="text/javascript"></script> <!--محل سفارشی سازی پیامها و مسایل بومی--> <script src="js/cultures/kendo.culture.fa-IR.js" type="text/javascript"></script> <script src="js/cultures/kendo.culture.fa.js" type="text/javascript"></script> <script src="js/messages/kendo.messages.en-US.js" type="text/javascript"></script> <style type="text/css"> body { font-family: tahoma; font-size: 9pt; } </style> <script type="text/javascript"> // جهت استفاده از فایل: kendo.culture.fa-IR.js kendo.culture("fa-IR"); </script> </head>
- سپس سه فایل kendo.culture.fa-IR.js، kendo.culture.fa.js و kendo.messages.en-US.js نیز اضافه شدهاند. فایلهای fa و fa-Ir آن هر چند به ظاهر برای ایران طراحی شدهاند، اما نام ماههای موجود در آن عربی است که نیاز به ویرایش دارد. به همین جهت به سورس این فایلها، جهت ویرایش نهایی نیاز خواهد بود که در پوشهی src\js\cultures مجموعهی اصلی Kendo UI موجود هستند (ر.ک. فایل پیوست).
- فایل kendo.messages.en-US.js حاوی تمام پیامهای مرتبط با Kendo UI است. برای مثال«رکوردهای 10 تا 15 از 1000 ردیف» را در اینجا میتوانید به فارسی ترجمه کنید.
- متد kendo.culture کار مشخص سازی فرهنگ بومی برنامه را به عهده دارد. برای مثال در اینجا به fa-IR تنظیم شدهاست. این مورد سبب خواهد شد تا از فایل kendo.culture.fa-IR.js استفاده گردد. اگر مقدار آنرا به fa تنظیم کنید، از فایل kendo.culture.fa.js کمک گرفته خواهد شد.
راست به چپ سازی گرید
تنها کاری که برای راست به چپ سازی Kendo UI Grid باید صورت گیرد، محصور سازی div آن در یک div با کلاس مساوی k-rtl است:
<div class="k-rtl"> <div id="report-grid"></div> </div>
تامین داده و نمایش گرید
در ادامه کدهای کامل DataSource و Kendo UI Grid را ملاحظه میکنید:
<script type="text/javascript"> $(function () { var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' }, parameterMap: function (options) { return kendo.stringify(options); } }, schema: { data: "Data", total: "Total", model: { fields: { "Id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است "Name": { type: "string" }, "IsAvailable": { type: "boolean" }, "Price": { type: "number" } } } }, error: function (e) { alert(e.errorThrown); }, pageSize: 10, sort: { field: "Id", dir: "desc" }, serverPaging: true, serverFiltering: true, serverSorting: true }); $("#report-grid").kendoGrid({ dataSource: productsDataSource, autoBind: true, scrollable: false, pageable: true, sortable: true, filterable: true, reorderable: true, columnMenu: true, columns: [ { field: "Id", title: "شماره", width: "130px" }, { field: "Name", title: "نام محصول" }, { field: "IsAvailable", title: "موجود است", template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>' }, { field: "Price", title: "قیمت", format: "{0:c}" } ] }); }); </script>
- در اینجا ذکر contentType الزامی است. زیرا ASP.NET Web API بر این اساس است که تصمیم میگیرد، خروجی را به صورت JSON ارائه دهد یا XML.
- با استفاده از parameterMap، سبب خواهیم شد تا پارامترهای ارسالی به سرور، با فرمت صحیحی تبدیل به JSON شده و بدون مشکل به سرور ارسال گردند.
- در قسمت schema باید نام فیلدهای موجود در DataSourceResult دقیقا مشخص شوند تا گرید بداند که data را باید از چه فیلدی استخراج کند و تعداد کل ردیفها در کدام فیلد قرار گرفتهاست.
- نحوهی تعریف model را نیز در اینجا ملاحظه میکنید. ذکر نوع فیلدها در اینجا بسیار مهم است و اگر قید نشوند، در حین جستجوی پویا به مشکل برخواهیم خورد. زیرا پیش فرض نوع تمام فیلدها string است و در این حالت نمیتوان عدد 1 رشتهای را با یک فیلد از نوع int در سمت سرور مقایسه کرد.
- در اینجا serverPaging، serverFiltering و serverSorting نیز به true تنظیم شدهاند. اگر این مقدار دهیها صورت نگیرد، این اعمال در سمت کلاینت انجام خواهند شد.
پس از تعریف DataSource، تنها کافی است آنرا به خاصیت dataSource یک kendoGrid نسبت دهیم.
- autoBind: true سبب میشود تا اطلاعات DataSource بدون نیاز به فراخوانی متد read آن به صورت خودکار دریافت شوند.
- با تنظیم scrollable: false، اعلام میکنیم که قرار است تمام رکوردها در معرض دید قرارگیرند و اسکرول پیدا نکنند.
- pageable: true صفحه بندی را فعال میکند. این مورد نیاز به تنظیم pageSize: 10 در قسمت DataSource نیز دارد.
- با sortable: true مرتب سازی ستونها با کلیک بر روی سرستونها فعال میگردد.
- filterable: true به معنای فعال شدن جستجوی خودکار بر روی فیلدها است. کتابخانهی Kendo.DynamicLinq حاصل آنرا در سمت سرور مدیریت میکند.
- reorderable: true سبب میشود تا کاربر بتواند محل قرارگیری ستونها را تغییر دهد.
- ذکر columnMenu: true اختیاری است. اگر ذکر شود، امکان مخفی سازی انتخابی ستونها نیز مسیر خواهد شد.
- در آخر ستونهای گرید مشخص شدهاند. با تعیین "{format: "{0:c سبب نمایش فیلدهای قیمت با سه رقم جدا کننده خواهیم شد. مقدار ریال آن از فایل فرهنگ جاری تنظیم شده دریافت میگردد. با استفاده از template تعریف شده نیز سبب نمایش فیلد bool به صورت یک checkbox خواهیم شد.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
KendoUI03.zip
- استفاده گزارشات به صورت کلاسهای #C در برنامه شما (با استفاده از Designer و Save as آن به صورت کلاس #C). در این صورت گزارش همزمان با کامپایل برنامه شما، کامپایل شده و در زمان فراخوانی، دیگر نیاز به کامپایل مجدد آن نیست.
اشکال: در صورتیکه قالب گزارش شما نیاز به تغییر داشته باشد، برنامهی شما باید با کلاسهای جدید گزارش مجددا کامپایل شود. - میتوان گزارش را از طریق برنامه Designer به صورت اسمبلی ( dll ) ذخیره کرد.
اشکال: این روش برای مدیریت گزارشها در طول ارتقاء آنها مفید نمیباشد. - از گزارش کامپایل شده استفاده نکنید و به جای آن از تفسیر عبارات در گزارش استفاده کنید. برای فعالسازی این حالت، خصوصیت Calculation Mode گزارش را در پنجره خصوصیتها در برنامه Designer بر روی Interpretation قرار دهید.
اشکال: این روش به این معنی است که تمامی اصطلاحات زبان C# / VB.NET تفسیر نمیشوند. همچنین استفاده از رخدادهای مربوط به اجزاء گزارش غیرممکن میباشد. - کامپایل و رندر گزارش، در دامنهی برنامهی دیگری انجام شود. در این مورد میتوان پس از رندر گزارش، اسمبلی را در دامنهی برنامه جاری بارگذاری کرد و فایل قفل شدهی آن، بعدا حذف خواهد شد. با استفاده از متد ()CreateReportInNewAppDomain از کلاس StiReport میتوان گزارش را ایجاد کرد و با استفاده از متد ()UnloadReportAppDomain آن را بارگذاری کرد.
اشکال در این روش متدهای RegData و RegBusinessObject با سرعت خیلی کمی اجرا میشوند؛ چرا که هدایت دادهها به دامنهی دیگری استفاده شده است. ایجاد و بارگذاری در دامنهی برنامه نیازمند زمان میباشد. - کارآمدترین روش: کامپایل گزارش تنها در زمان اولین فراخوانی انجام شود و هر زمان که درخواست فراخوانی گزارش انجام میشود، گزارش را از اسمبلی کامپایل شده، در حافظه بارگذاری میکنیم. در این مورد تنها یک کپی از گزارش در حافظه ذخیره میشود و تنها یک اسمبلی در درایو وجود دارد. پوشه مربوط به گزارشهای کامپایل شده میتواند قبل از استفاده از گزارشات حذف شود، چرا که در اولین درخواست گزارش، دوباره ایجاد خواهد شد.
مثال:
var report = new StiReport(); report.Load( "Report.mrt" ); var folder = Environment.GetFolderPath( Environment.SpecialFolder.LocalApplicationData ); folder = Path.Combine( folder, "Stimulsoft\\CompiledReports" ); folder = Path.Combine( folder, System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion() ); var compiledReportFile = Path.Combine( folder, report.GetReportAssemblyCacheName() ); if ( File.Exists( compiledReportFile ) ) report = StiReport.GetReportFromAssembly( compiledReportFile, true ); else { if ( !Directory.Exists( folder ) ) Directory.CreateDirectory( folder ); report.Compile( compiledReportFile ); } report.RegBusinessObject( "TestData", new { Title = "Vahid " + DateTime.Now.Millisecond } ); report.Render( false );
دریافت افزونهی jsTree
برای دریافت افزونهی jsTree میتوان به مخزن کد آن در Github مراجعه کرد و همچنین مستندات آنرا در سایت jstree.com قابل مطالعه هستند.
تنظیمات مقدماتی jsTree
در این مطلب فرض شدهاست که فایل jstree.min.js، در پوشهی Scripts و فایلهای CSS آن در پوشهی Content\themes\default کپی شدهاند.
به این ترتیب layout برنامه چنین شکلی را خواهد یافت:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <link href="~/Content/themes/default/style.min.css" rel="stylesheet" /> <script src="~/Scripts/jquery.min.js"></script> <script src="~/Scripts/jstree.min.js"></script> </head> <body dir="rtl"> @RenderBody() @RenderSection("scripts", required: false) </body> </html>
نمایش راست به چپ اطلاعات
در کدهای این افزونه به تگ body و ویژگی dir آن برای تشخیص راست به چپ بودن محیط دقت میشود. به همین جهت این تعریف را در layout فوق ملاحظه میکنید. برای مثال اگر به فایل jstree.contextmenu.js (موجود در مجموعه سورسهای این افزونه) مراجعه کنید، یک چنین تعریفی قابل مشاهده است:
right_to_left = $("body").css("direction") === "rtl";
تهیه ساختاری جهت ارائهی خروجی JSON
با توجه به اینکه قصد داریم به صورت پویا با این افزونه کار کنیم، نیاز است بتوانیم ساختار سلسله مراتبی مدنظر را با فرمت JSON ارائه دهیم. در ادامه کلاسهایی که معادل فرمت JSON قابل قبول توسط این افزونه را تولید میکنند، ملاحظه میکنید:
using System.Collections.Generic; namespace MvcJSTree.Models { public class JsTreeNode { public string id { set; get; } // نام این خواص باید با مستندات هماهنگ باشد public string text { set; get; } public string icon { set; get; } public JsTreeNodeState state { set; get; } public List<JsTreeNode> children { set; get; } public JsTreeNodeLiAttributes li_attr { set; get; } public JsTreeNodeAAttributes a_attr { set; get; } public JsTreeNode() { state = new JsTreeNodeState(); children = new List<JsTreeNode>(); li_attr = new JsTreeNodeLiAttributes(); a_attr = new JsTreeNodeAAttributes(); } } public class JsTreeNodeAAttributes { // به هر تعداد و نام اختیاری میتوان خاصیت تعریف کرد public string href { set; get; } } public class JsTreeNodeLiAttributes { // به هر تعداد و نام اختیاری میتوان خاصیت تعریف کرد public string data { set; get; } } public class JsTreeNodeState { public bool opened { set; get; } public bool disabled { set; get; } public bool selected { set; get; } public JsTreeNodeState() { opened = true; } } }
- هر چند اسامی مانند a_attr، مطابق اصول نامگذاری دات نت نیستند، ولی این نامها را تغییر ندهید. زیرا این افزونه دقیقا به همین نامها و با همین املاء نیاز دارد.
- id، میتواند دقیقا معادل id یک رکورد در بانک اطلاعاتی باشد. Text عنوان گرهای (node) است که نمایش داده میشود. icon در اینجا مسیر یک فایل png است جهت نمایش در کنار عنوان هر گره. توسط state میتوان مشخص کرد که زیر شاخهی جاری به صورت باز نمایش داده شود یا بسته. به کمک خاصیت children میتوان زیر شاخهها را تا هر سطح و تعدادی که نیاز است تعریف نمود.
- خاصیتهای li_attr و a_attr کاملا دلخواه هستند. برای مثال در اینجا دو خاصیت href و data را در کلاسهای مرتبط با آنها مشاهده میکنید. میتوانید در اینجا به هر تعداد ویژگی سفارشی دیگری که جهت تعریف یک گره نیاز است، خاصیت اضافه کنید.
سادهترین مثالی که از ساختار فوق میتواند استفاده کند، اکشن متد زیر است:
[HttpPost] public ActionResult GetTreeJson() { var nodesList = new List<JsTreeNode>(); var rootNode = new JsTreeNode { id = "dir", text = "Root 1", icon = Url.Content("~/Content/images/tree_icon.png"), a_attr = { href = "http://www.bing.com" } }; nodesList.Add(rootNode); nodesList.Add(new JsTreeNode { id = "test1", text = "Root 2", icon = Url.Content("~/Content/images/tree_icon.png"), a_attr = { href = "http://www.bing.com" } }); return Json(nodesList, JsonRequestBehavior.AllowGet); }
بنابراین ساختارهای خود ارجاع دهنده را به خوبی میتوان با این افزونه وفق داد.
فعال سازی اولیه سمت کلاینت افزونه jsTree
برای استفادهی پویای از این افزونه در سمت کلاینت، فقط نیاز به یک DIV خالی است:
<div id="jstree"> </div>
$('#jstree').jstree({ "core": { "multiple": false, "check_callback": true, 'data': { 'url': '@getTreeJsonUrl', "type": "POST", "dataType": "json", "contentType": "application/json; charset=utf8", 'data': function (node) { return { 'id': node.id }; } }, 'themes': { 'variant': 'small', 'stripes': true } }, "types": { "default": { "icon": '@Url.Content("~/Content/images/bookmark_book_open.png")' }, }, "plugins": ["contextmenu", "dnd", "state", "types", "wholerow", "sort", "unique"], "contextmenu": { "items": function (o, cb) { var items = $.jstree.defaults.contextmenu.items(); items["create"].label = "ایجاد زیر شاخه"; items["rename"].label = "تغییر نام"; items["remove"].label = "حذف"; var cpp = items["ccp"]; cpp.label = "ویرایش"; var subMenu = cpp["submenu"]; subMenu["copy"].label = "کپی"; subMenu["paste"].label = "پیست"; subMenu["cut"].label = "برش"; return items; } } });
- multiple : false به این معنا است که نمیخواهیم کاربر بتواند چندین گره را با نگه داشتن دکمهی کنترل انتخاب کند.
- check_callback : true کدهای مرتبط با منوی کلیک سمت راست ماوس را فعال میکند.
- در قسمت data کار تبادل اطلاعات با سرور جهت دریافت فرمت JSON ایی که به آن اشاره شد، انجام میشود. متغیر getTreeJsonUrl یک چنین شکلی را میتواند داشته باشد:
@{ ViewBag.Title = "Demo"; var getTreeJsonUrl = Url.Action(actionName: "GetTreeJson", controllerName: "Home"); }
- در قسمت types که مرتبط است با افزونهای به همین نام، آیکن پیش فرض یک نود جدید ایجاد شده را مشخص کردهایم.
- گزینهی plugins، لیست افزونههای اختیاری این افزونه را مشخص میکند. برای مثال contextmenu منوی کلیک سمت راست ماوس را فعال میکند، dnd همان کشیدن و رها کردن گرهها است در زیر شاخههای مختلف. افزونهی state، انتخاب جاری کاربر را در سمت کلاینت ذخیره و در مراجعهی بعدی او بازیابی میکند. با ذکر افزونهی wholerow سبب میشویم که انتخاب یک گره، معادل انتخاب یک ردیف کامل از صفحه باشد. افزونهی sort کار مرتب سازی خودکار اعضای یک زیر شاخه را انجام میدهد. افزونهی unique سبب میشود تا در یک زیر شاخه نتوان دو عنوان یکسان را تعریف کرد.
- در قسمت contextmenu نحوهی بومی سازی گزینههای منوی کلیک راست ماوس را مشاهده میکنید. در حالت پیش فرض، عناوینی مانند create، rename و امثال آن نمایش داده میشوند که به نحو فوق میتوان آنرا تغییر داد.
با همین حد تنظیم، این افزونه کار نمایش سلسله مراتبی اطلاعات JSON ایی دریافت شده از سرور را انجام میدهد.
ذخیره سازی گرههای جدید و تغییرات سلسله مراتب پویای تعریف شده در سمت سرور
همانطور که عنوان شد، اگر افزونهی اختیاری contextmenu را فعال کنیم، امکان افزودن، ویرایش و حذف گرهها و زیر شاخهها را خواهیم یافت. برای انتقال این تغییرات به سمت سرور، باید به نحو ذیل عمل کرد:
$('#jstree').jstree({ // تمام تنظیمات مانند قبل }).on('delete_node.jstree', function (e, data) { }) .on('create_node.jstree', function (e, data) { }) .on('rename_node.jstree', function (e, data) { }) .on('move_node.jstree', function (e, data) { }) .on('copy_node.jstree', function (e, data) { }) .on('changed.jstree', function (e, data) { }) .on('dblclick.jstree', function (e) { }) .on('select_node.jstree', function (e, data) { });
در تمام این حالات، جایی که data در اختیار ما است، میتوان یک چنین ساختار جاوا اسکریپتی را برای ارسال به سرور طراحی کرد:
function postJsTreeOperation(operation, data, onDone, onFail) { $.post('@doJsTreeOperationUrl', { 'operation': operation, 'id': data.node.id, 'parentId': data.node.parent, 'position': data.position, 'text': data.node.text, 'originalId': data.original ? data.original.id : data.node.original.id, 'href': data.node.a_attr.href }) .done(function (result) { onDone(result); }) .fail(function (result) { alert('failed.....'); onFail(result); }); }
.on('create_node.jstree', function (e, data) { postJsTreeOperation('CreateNode', data, function (result) { data.instance.set_id(data.node, result.id); }, function (result) { data.instance.refresh(); }); })
و معادل سمت سرور دریافت کنندهی این اطلاعات، اکشن متد ذیل میتواند باشد:
[HttpPost] public ActionResult DoJsTreeOperation(JsTreeOperationData data) { switch (data.Operation) { case JsTreeOperation.CopyNode: case JsTreeOperation.CreateNode: //todo: save data var rnd = new Random(); // آی دی رکورد پس از ثبت در بانک اطلاعاتی دریافت و بازگشت داده شود return Json(new { id = rnd.Next() }, JsonRequestBehavior.AllowGet); case JsTreeOperation.DeleteNode: //todo: save data return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet); case JsTreeOperation.MoveNode: //todo: save data return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet); case JsTreeOperation.RenameNode: //todo: save data return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet); default: throw new InvalidOperationException(string.Format("{0} is not supported.", data.Operation)); } }
namespace MvcJSTree.Models { public enum JsTreeOperation { DeleteNode, CreateNode, RenameNode, MoveNode, CopyNode } public class JsTreeOperationData { public JsTreeOperation Operation { set; get; } public string Id { set; get; } public string ParentId { set; get; } public string OriginalId { set; get; } public string Text { set; get; } public string Position { set; get; } public string Href { set; get; } } }
در اینجا Href را نیز مشاهده میکنید. همانطور که عنوان شد، اعضای JsTreeNodeAAttributes اختیاری هستند. بنابراین اگر این اعضاء را تغییر دادید، باید خواص JsTreeOperationData و همچنین اعضای شیء تعریف شده در postJsTreeOperation را نیز تغییر دهید تا با هم تطابق پیدا کنند.
چند نکتهی تکمیلی
اگر میخواهید که با دوبار کلیک بر روی یک گره، کاربر به href آن هدایت شود، میتوان از کد ذیل استفاده کرد:
var selectedData; // ... .on('dblclick.jstree', function (e) { var href = selectedData.node.a_attr.href; alert('selected node: ' + selectedData.node.text + ', href:' + href); // auto redirect if (href) { window.location = href; } // activate edit mode //var inst = $.jstree.reference(selectedData.node); //inst.edit(selectedData.node); }) .on('select_node.jstree', function (e, data) { //alert('selected node: ' + data.node.text); selectedData = data; });
حتی اگر خواستید که با دوبار کلیک بر روی یک گره، گزینهی ویرایش آن فعال شود، کدهای آن را به صورت کامنت مشاهده میکنید.
مثال کامل این بحث را از اینجا میتوانید دریافت کنید:
MvcJSTree.zip
پشتیبانی از Generic Attributes در C# 11
/// <summary> /// Instantiates a new <see cref="ServiceFilterAttribute"/> instance. /// </summary> /// <param name="type">The <see cref="Type"/> of filter to find.</param> public ServiceFilterAttribute(Type type) { ServiceType = type ?? throw new ArgumentNullException(nameof(type)); }
public class TypeSafeServiceFilterAttribute<T> : ServiceFilterAttribute where T: IActionFilter { public TypeSafeServiceFilterAttribute():base(typeof(T)) { } }
[Route("api/[controller]")] [ApiController] public class CoursesController : ControllerBase { [HttpGet] [TypeSafeServiceFilterAttribute<ExampleFilter>()] public IActionResult Get() { return Ok(); } }
public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, CancellationToken cancellationToken, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TSource, CancellationToken, ValueTask> body)
یک مثال: در اینجا میخواهیم به صورت موازی، مشخصات کاربرانی از Github را توسط HttpClient دریافت کنیم. هر بار هم فقط میخواهیم سه وظیفه اجرا شوند و نه بیشتر
using System.Net.Http.Headers; using System.Net.Http.Json; var userHandlers = new [] { "users/VahidN", "users/shanselman", "users/jaredpar", "users/davidfowl" }; using HttpClient client = new() { BaseAddress = new Uri("https://api.github.com"), }; client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DotNet", "6")); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 3 }; await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) => { var user = await client.GetFromJsonAsync<GitHubUser>(uri, token); Console.WriteLine($"Name: {user.Name}\nBio: {user.Bio}\n"); }); public class GitHubUser { public string Name { get; set; } public string Bio { get; set; } }
متد Parallel.ForEachAsync، آرایهای را که باید بر روی آن کار کند، دریافت میکند. سپس تنظیمات اجرای موازی آنها را هم مشخص میکنیم. در ادامه آنها را در دستههای مشخصی، به صورت موازی بر اساس منطقی که مشخص میکنیم، اجرا خواهد کرد.
وضعیت امکان اجرای موازی متدهای async همزمان، تا پیش از دات نت 6
<List<T به همراه متد الحاقی ForEach است که میتواند یک <Action<T را بر روی المانهای این لیست، اجرا کند و ... عموما زمانیکه به وظایف async میرسیم، به اشتباه مورد استفاده قرار میگیرد:
customers.ForEach(c => SendEmailAsync(c));
foreach(var c in customers) { SendEmailAsync(c); // the return task is ignored }
customers.ForEach(async c => await SendEmailAsync(c));
تنها راه حل پذیرفتهی شدهی چنین عمل async ای، فراخوانی آنها به صورت متداول زیر و بدون استفاده از متد ForEach است:
foreach(var c in customers) { await SendEmailAsync(c); }
foreach(var o in orders) { await ProcessOrderAsync(o); }
var tasks = orders.Select(o => ProcessOrderAsync(o)).ToList(); await Task.WhenAll(tasks);
دات نت 6، هم کنترل MaxDegreeOfParallelism را میسر کردهاست و هم اینکه اینبار نگارش async واقعی Parallel.ForEachAsync را ارائه دادهاست تا دیگر همانند حالت قبلی Parallel.ForEach، به async voidها و مشکلات مرتبط با آنها نرسیم.
نحوه ذخیره شدن متن در فایلهای PDF
حتما نیاز است پیشنیاز فوق را یکبار مطالعه کنید تا علت خروجیهای متفاوتی را که در ادامه ملاحظه خواهید نمود، بهتر مشخص شوند. همچنین فایل PDF ایی که مورد بررسی قرار خواهد گرفت، همان فایلی است که توسط متد writePdf ذکر شده در پیشنیاز تهیه شده است.
دو کلاس متفاوت برای استخراج متن از فایلهای PDF در iTextSharp وجود دارند:
الف) SimpleTextExtractionStrategy
using System.Diagnostics; using System.IO; using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.pdf.parser; namespace TestReaders { class Program { private static void readPdf1() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new SimpleTextExtractionStrategy()); File.WriteAllText("page-" + i + "-text.txt", text); } reader.Close(); } static void Main(string[] args) { readPdf1(); } } }
Test ld Wor llo He Hello People
ب) LocationTextExtractionStrategy
همان مثال قبل را درنظر بگیرید، اینبار به شکل زیر:
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); File.WriteAllText("page-" + i + "-text.txt", text); } reader.Close(); }
Test Hello World Hello People
استخراج متون فارسی از فایلهای PDF توسط iTextSharp
روشهای فوق با PDFهای فارسی هم کار میکنند اما خروجی حاصل آن مفهوم نیست و نیاز به پردازش ثانوی دارد. ابتدا مثال زیر را درنظر بگیرید:
static void writePdf2() { using (var document = new Document(PageSize.A4)) { var writer = PdfWriter.GetInstance(document, new FileStream("test.pdf", FileMode.Create)); document.Open(); FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf"); var tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H); ColumnText.ShowTextAligned( canvas: writer.DirectContent, alignment: Element.ALIGN_CENTER, phrase: new Phrase("تست میشود", tahoma), x: 100, y: 100, rotation: 0, runDirection: PdfWriter.RUN_DIRECTION_RTL, arabicOptions: 0); } Process.Start("test.pdf"); }
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text)); File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8); } reader.Close(); }
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Security; namespace TestReaders { [SuppressUnmanagedCodeSecurity] class GdiMethods { [DllImport("GDI32.dll")] public static extern bool DeleteObject(IntPtr hgdiobj); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint GetCharacterPlacement(IntPtr hdc, string lpString, int nCount, int nMaxExtent, [In, Out] ref GcpResults lpResults, uint dwFlags); [DllImport("GDI32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); } [StructLayout(LayoutKind.Sequential)] struct GcpResults { public uint lStructSize; [MarshalAs(UnmanagedType.LPTStr)] public string lpOutString; public IntPtr lpOrder; public IntPtr lpDx; public IntPtr lpCaretPos; public IntPtr lpClass; public IntPtr lpGlyphs; public uint nGlyphs; public int nMaxFit; } public class UnicodeCharacterPlacement { const int GcpReorder = 0x0002; GCHandle _caretPosHandle; GCHandle _classHandle; GCHandle _dxHandle; GCHandle _glyphsHandle; GCHandle _orderHandle; public Font Font { set; get; } public string Apply(string lines) { if (string.IsNullOrWhiteSpace(lines)) return string.Empty; return Apply(lines.Split('\n')).Aggregate((s1, s2) => s1 + s2); } public IEnumerable<string> Apply(IEnumerable<string> lines) { if (Font == null) throw new ArgumentNullException("Font is null."); if (!hasUnicodeText(lines)) return lines; var graphics = Graphics.FromHwnd(IntPtr.Zero); var hdc = graphics.GetHdc(); try { var font = (Font)Font.Clone(); var hFont = font.ToHfont(); var fontObject = GdiMethods.SelectObject(hdc, hFont); try { var results = new List<string>(); foreach (var line in lines) results.Add(modifyCharactersPlacement(line, hdc)); return results; } finally { GdiMethods.DeleteObject(fontObject); GdiMethods.DeleteObject(hFont); font.Dispose(); } } finally { graphics.ReleaseHdc(hdc); graphics.Dispose(); } } void freeResources() { _orderHandle.Free(); _dxHandle.Free(); _caretPosHandle.Free(); _classHandle.Free(); _glyphsHandle.Free(); } static bool hasUnicodeText(IEnumerable<string> lines) { return lines.Any(line => line.Any(chr => chr >= '\u00FF')); } void initializeResources(int textLength) { _orderHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _dxHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _caretPosHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _classHandle = GCHandle.Alloc(new byte[textLength], GCHandleType.Pinned); _glyphsHandle = GCHandle.Alloc(new short[textLength], GCHandleType.Pinned); } string modifyCharactersPlacement(string text, IntPtr hdc) { var textLength = text.Length; initializeResources(textLength); try { var gcpResult = new GcpResults { lStructSize = (uint)Marshal.SizeOf(typeof(GcpResults)), lpOutString = new String('\0', textLength), lpOrder = _orderHandle.AddrOfPinnedObject(), lpDx = _dxHandle.AddrOfPinnedObject(), lpCaretPos = _caretPosHandle.AddrOfPinnedObject(), lpClass = _classHandle.AddrOfPinnedObject(), lpGlyphs = _glyphsHandle.AddrOfPinnedObject(), nGlyphs = (uint)textLength, nMaxFit = 0 }; var result = GdiMethods.GetCharacterPlacement(hdc, text, textLength, 0, ref gcpResult, GcpReorder); return result != 0 ? gcpResult.lpOutString : text; } finally { freeResources(); } } } }
در اینجا برای اصلاح متد readPdf2 خواهیم داشت:
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text)); text = new UnicodeCharacterPlacement { Font = new System.Drawing.Font("Tahoma", 12) }.Apply(text); File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8); } reader.Close(); }
سؤال: آیا این روش با تمام PDFهای فارسی کار میکند؟
پاسخ: خیر! همانطور که در پیشنیاز مطلب جاری عنوان شد، در یک حالت خاص، PDF writer میتواند شماره Glyphها را کاملا عوض کرده و در فایل PDF نهایی ثبت کند. خروجی حاصل در برنامه Adobe reader خوانا است، چون نمایش را بر اساس اطلاعات هندسی Glyphها انجام میدهد؛ اما خروجی متنی آن به نوعی obfuscated است چون مثلا حرف A آن به کاراکتر مرسوم دیگری نگاشت شده است.
نام پروژهی من AMD و فایل index.html بدان اضافه شده است. فرض کنید یک پوشهی جدید را به نام modules به آن اضافه میکنیم و در آن دو فایل typescript ی را به نامهای module1.ts و module2.ts، اضافه میکنیم.
محتویات module1 را اینگونه مینویسیم:
export module module1 { export abstract class firstCls { static f1(str: string) { console.log(str); } } }
و همچنین module2 به شکل زیر خواهد بود:
import * as Amd from 'module1'; module module2 { export class secondCls { f2(str: string) { Amd.module1.firstCls.f1(str); } } } new module2.secondCls().f2(`amd work's`);
(دقت کنید بعد از کامپایل شدن لفظ import تبدیل به define میشود)
از طریق add - new item فایل tsconfig.json را به مسیر اصلی پروژه اضافه کنید. در صورتی که آن را پیدا نکردید، به صورت دستی فایل آن را ساخته و محتویات زیر را در آن کپی نمایید:
{ "compilerOptions": { "noImplicitAny": false, "noEmitOnError": true, "removeComments": false, "sourceMap": false, "target": "es6", "module": "amd" }, "exclude": [ "node_modules" ] }
در ادامه به root پروژه رفته و دستور npm init را ارسال کرده تا فایل package.json تولید شود. همچنین برای requirejs نیز دستور زیر را ارسال مینماییم:
npm install requirejs --save-dev
حال requirejs به پروژهی شما اضافه شده است.
برای مدیریت کردن فراخوانی initial module در پوشهی modules که قبلا ساخته بودیم فایل main.js راساخته و کدهای زیر را بدان اضافه مینماییم.
(لازم به ذکر است این فایل را میتوانیم با استفاده از typescript نوشته و requirejs definitely typed را به پروژه اضافه کرده و از مزایای intellisense بودن بهره ببریم)
کدهای زیر را درون main.js مینویسیم:
require(['modules/module2.js'], modules_module2()); function modules_module2() { //additionals config goes here }
از آنجاییکه ممکن است تعداد وابستگی فایلها زیاد باشد و ترتیب load شدن آنها نیز اهمیت داشته باشد، در این قسمت میتوان configهای بیشتری را همچون sequence در load کردن فایلها، تعریف کرد که میتوانید در وبسایت رسمی requirejs آن را مطالعه بفرمایید.
حال فایل index.html را باز کرده و config برای فراخوانی requirejs, main.js را مینویسیم؛ به صورت زیر:
<h1>Hello requirejs and amd modules</h1> <!--src means require js address--> <!--data-main means initial require config--> <script src="node_modules/requirejs/require.js" data-main="modules/main.js"></script> <script></script>
پر واضح است که src آدرس فایل require.js و همچنین data-main آدرس initial require config پروژه را مشخص میکند.
اکنون پروژه را run کرده و میبینید که فایلهای مورد نیاز به صورت async برای ما load میشوند. اگر از مرورگر کروم استفاده مینمایید، بدین صورت میتوانید network و همچنین console را مشاهد نمایید:
مشاهد میکنید که فایلهای مورد نیاز load شدهاند و همچنین amd work's در console نمایش داده شده است.
requirejs بدین صورت عمل میکند: بعد از یافتن هر فایل، با استفاده از regex کل فایل را بررسی کرده و به دنبال وابستگیهای آن فایل میگردد (منظور همان importها میباشد و آن فایل به صورت async لود میشود) و در فایلهای بعدی نیز همین روال ادامه خواهد یافت. هر چند راهکارهایی برای بهبود کارآیی در آن اندیشیده شده است؛ بدین صورت که اساس کارش با استفاده از singleton میباشد و بعد از require کردن فایلی، هر دفعه که فراخوانی میشود، نیاز به پردازش مجدد ندارد. با این وجود ممکن است در بعضی مواقع و مخصوصا با اشتباهات سهوی برنامه نویسان از کارآیی نرم افزار مطبوع شما بکاهد.
کدهای این برنامه را میتوانید از اینجا دریافت نمایید (ضمن اینکه وابستگیهای اضافهتری مانند پوشهی node_modules حذف شدهاند؛ بنابراین npm install فراموش نشود)
دانلود AMD.zip
در قسمت بعد به امکانات توکار کامپایلر typescript برای معماری ماژولها میپردازیم