همینطور برای ایجاد یک View گزینه (DataWindow (WPF with Catel را انتخاب نمایید. ViewModelها در Catel از کلاس پایه ViewModelBase و Viewها نیز از کلاس DataWindow مشتق میشوند.
public class MainWindowViewModel : ViewModelBase { public MainWindowViewModel() : base() { ShowPleaseWait = new Command(OnShowPleaseWaitExecute); } public override string Title { get { return "View model title"; } } public Command ShowPleaseWait { get; private set; } private void OnShowPleaseWaitExecute() { var pleaseWaitService = GetService<IPleaseWaitService>(); pleaseWaitService.Show(() => { Thread.Sleep(3000); }); } }
<Button Margin="6" Command="{Binding ShowPleaseWait}" Content="Show PleaseWait!" />
var pleaseWaitService = GetService<IPleaseWaitService>(); pleaseWaitService.Show(() => { Thread.Sleep(3000); });
var uiService = GetService<IUIVisualizerService>(); var viewModel = new AnotherWindowViewModel(); uiService.Show(viewModel);
var openFileService = GetService<IOpenFileService>(); openFileService.Filter = "ZIP files (*.zip)|*.zip"; openFileService.IsMultiSelect = false; openFileService.Title = "Open file"; if (openFileService.DetermineFile()) { // ? }
var saveFileService = GetService<ISaveFileService>(); saveFileService.Filter = "ZIP files (*.zip)|*.zip"; saveFileService.FileName = "test"; saveFileService.Title = "Save file"; if (saveFileService.DetermineFile()) { // ? }
var processService = GetSetvice<IProcessService>(); processService.StartProcess(@"C:\Windows\System32\calc.exe");
var splashScreenService = GetService<ISplashScreenService>(); splashScreenService.Enqueue(new ActionTask("Creating the shell", OnCreateShell)); splashScreenService.Enqueue(new ActionTask("Initializing modules", OnInitializeModules)); splashScreenService.Enqueue(new ActionTask("Starting application", OnStartApplication));
var messageService = GetService<IMessageService>(); if (messageService.Show("Are you sure?", "?", MessageButton.YesNo, MessageImage.Warning) == MessageResult.Yes) { // ? }
<link rel="stylesheet" href="@Url.Content("~/Content/bootstrap-rtl.css")" type="text/css" /> <script type="text/javascript" src="@Url.Content("~/scripts/jquery-2.0.2.min.js")"></script> <script type="text/javascript" src="@Url.Content("~/scripts/jquery-ui-1.10.3.min.js")"></script> <script type="text/javascript" src="@Url.Content("~/scripts/bootstrap-rtl.js")"></script> <script type="text/javascript" src="@Url.Content("~/scripts/searchboxmvc.js")"></script>
[HttpPost] public virtual ActionResult LoadData(string fieldName, string value, string stringFilterMode = "startWith") { Thread.Sleep(2000); var models = MakePersons(); if (fieldName == "Id") { models = models.Where(p => p.Id == int.Parse(value)).Take(1).ToList(); } else if (fieldName == "FirstName") { models = models.Where(p => p.FirstName.StartsWith(value)).ToList(); } return Json(new { Status = "OK", Records = models }); } private List<Person> MakePersons() { var lst = new List<Person>(); lst.Add(new Person() { Id = 1, Code = "Uytffs-098", FirstName = "احمدرضا", LastName = "عابدزاده" }); lst.Add(new Person() { Id = 2, Code = "fTuuuw-652", FirstName = "کریم", LastName = "باقری" }); lst.Add(new Person() { Id = 3, Code = "Lopapo-123", FirstName = "خداداد", LastName = "عزیزی" }); lst.Add(new Person() { Id = 4, Code = "Utppq-981", FirstName = "علی", LastName = "دایی" }); lst.Add(new Person() { Id = 5, Code = "zttsn-471", FirstName = "علی", LastName = "کریمی" }); lst.Add(new Person() { Id = 6, Code = "poiud-901", FirstName = "مهدی", LastName = "مهدوی کیا" }); lst.Add(new Person() { Id = 7, Code = "wqrPoP-391", FirstName = "علیرضا", LastName = "منصوریان" }); return lst; }
... <div id="div_SearchBoxContainer"> </div> ... @section scripts{ <script type="text/javascript"> $("#div_SearchBoxContainer").searchboxmvc({ loadUrl: '@Url.Action(actionName: "LoadData", controllerName: "Home")', defaultStringFilterMode: "startWith", loadDataOnLeave: true, displayClass: "", displayNoResultClass: "", display: function (element, record) { $(element).html(record.FirstName + " " + record.LastName); }, listItemsDisplay: function (element, record, index) { return record.LastName + " " + record.FirstName + "(" + record.Code + ")"; }, fields: [ { fieldName: "Id", fieldTitle: "شناسه", width: 100, defaultValueField: true }, { fieldName: "FirstName", fieldTitle: "نام", width: 200, defaultDisplayField: true, filter: true, isStringType: true }, { fieldName: "LastName", fieldTitle: "نام خانوادگی", filter: false, isStringType: true } ] }); </script> }
شرح پارامترهای افزونه searchboxmvc.js
sample_mvc.zip
<script src="Scripts/jquery.min.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function () { try { var result = $.ajax({ 'type': 'HEAD', 'url': '/' }).success(function () { var date1 = new Date(result.getResponseHeader('Date')); alert(date1); }); } catch (err) { //... } }); </script>
+ این رو هم باید درنظر داشت که حین پردازش تاریخ دریافتی از وب سرور باید مسایل GMT را هم لحاظ کرد تا تاریخ و زمان دریافتی با زمان ایران تطابق پیدا کند.
//برای ذخیره مقادیر از ساختار نام و مقدار استفاده میکنیم که نامها را اینجا ثبت کرده ام var Variables={ posts:"posts", postsComments:"postsComments", shares:"shares", sharesComments:"sharesComments", } //برای ذخیره زمان آخرین تغییر سایت برای هر یک از مطالب به صورت جداگانه نیاز به یک ساختار نام و مقدار است که نامها را در اینجا ذخیره کرده ام var DateContainer={ posts:"dtposts", postsComments:"dtpostsComments", shares:"dtshares", sharesComments:"dtsharesComments", interval:"interval" } //برای نمایش پیامها به کاربر var Messages={ SettingsSaved:"تنظیمات ذخیره شد", SiteUpdated:"سایت به روز شد", PostsUpdated:"مطلب ارسالی جدید به سایت اضافه شد", CommentsUpdated:"نظری جدیدی در مورد مطالب سایت ارسال شد", SharesUpdated:"اشتراک جدید به سایت ارسال شد", SharesCommentsUpdated:"نظری برای اشتراکهای سایت اضافه شد" } //لینکهای فید سایت var Links={ postUrl:"https://www.dntips.ir/feeds/posts", posts_commentsUrl:"https://www.dntips.ir/feeds/comments", sharesUrl:"https://www.dntips.ir/feed/news", shares_CommentsUrl:"https://www.dntips.ir/feed/newscomments" } //لینک صفحات سایت var WebLinks={ Home:"https://www.dntips.ir", postUrl:"https://www.dntips.ir/postsarchive", posts_commentsUrl:"https://www.dntips.ir/commentsarchive", sharesUrl:"https://www.dntips.ir/newsarchive", shares_CommentsUrl:"https://www.dntips.ir/newsarchive/comments" }
chrome.runtime.onInstalled.addListener(function(details) { var now=String(new Date()); var params={}; params[Variables.posts]=true; params[Variables.postsComments]=false; params[Variables.shares]=false; params[Variables.sharesComments]=false; params[DateContainer.interval]=1; params[DateContainer.posts]=now; params[DateContainer.postsComments]=now; params[DateContainer.shares]=now; params[DateContainer.sharesComments]=now; chrome.storage.local.set(params, function() { if(chrome.runtime.lastError) { /* error */ console.log(chrome.runtime.lastError.message); return; } }); });
chrome.storage.local.set('mykey':myvalue,....
chrome.storage.local.set(mykey:myvalue,...
"background": { "scripts": ["const.js","init.js"] }
نکته:نمی توان در تعریف بک گراند هم فایل اسکریپت معرفی کرد و هم فایل html
"background": { "page": "background.htm" }
<html> <head> <script type="text/javascript" src="const.js"></script> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript" src="init.js"></script> <script type="text/javascript" src="omnibox.js"></script> <script type="text/javascript" src="rssreader.js"></script> <script type="text/javascript" src="contextmenus.js"></script> </head> <body> </body> </html>
- کدنویسی راحتتر و خلاصهتر برای خواندن RSS
- استفاده اجباری از یک پروکسی به خاطر Content Security Policy و حتی CORS
"content_security_policy": "script-src 'self' https://*.google.com; object-src 'self'"
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
استفاده از این Api در rssreader.js
google.load("feeds", "1"); google.setOnLoadCallback(alarmManager);
function alarmManager() { chrome.storage.local.get(DateContainer.interval,function ( items) { period_time==items[DateContainer.interval]; chrome.alarms.create('RssInterval', {periodInMinutes: period_time}); }); chrome.alarms.onAlarm.addListener(function (alarm) { console.log(alarm); if (alarm.name == 'RssInterval') { var boolposts,boolpostsComments,boolshares,boolsharesComments; chrome.storage.local.get([Variables.posts,Variables.postsComments,Variables.shares,Variables.sharesComments],function ( items) { boolposts=items[Variables.posts]; boolpostsComments=items[Variables.postsComments]; boolshares=items[Variables.shares]; boolsharesComments=items[Variables.sharesComments]; chrome.storage.local.get([DateContainer.posts,DateContainer.postsComments,DateContainer.shares,DateContainer.sharesComments],function ( items) { var Vposts=new Date(items[DateContainer.posts]); var VpostsComments=new Date(items[DateContainer.postsComments]); var Vshares=new Date(items[DateContainer.shares]); var VsharesComments=new Date(items[DateContainer.sharesComments]); if(boolposts){var result=RssReader(Links.postUrl,Vposts,DateContainer.posts,Messages.PostsUpdated);} if(boolpostsComments){var result=RssReader(Links.posts_commentsUrl,VpostsComments,DateContainer.postsComments,Messages.CommentsUpdated); } if(boolshares){var result=RssReader(Links.sharesUrl,Vshares,DateContainer.shares,Messages.SharesUpdated);} if(boolsharesComments){var result=RssReader(Links.shares_CommentsUrl,VsharesComments,DateContainer.sharesComments,Messages.SharesCommentsUpdated);} }); }); } }); }
- آدرس فیدی که قرار است از روی آن بخواند
- آخرین به روزسانی که از سایت داشته متعلق به چه تاریخی است.
- نام کلید ذخیره سازی تاریخ آخرین تغییر سایت که اگر بررسی شد و مشخص شد سایت به روز شده است، تاریخ جدید را روی آن ذخیره کنیم.
- در صورتی که سایت به روز شده باشد نیاز است پیامی را برای کاربر نمایش دهیم که این پیام را در اینجا قرار میدهیم.
function RssReader(URL,lastupdate,datecontainer,Message) { var feed = new google.feeds.Feed(URL); feed.setResultFormat(google.feeds.Feed.XML_FORMAT); feed.load(function (result) { if(result!=null) { var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent; var RssUpdate=new Date(strRssUpdate); if(RssUpdate>lastupdate) { SaveDateAndShowMessage(datecontainer,strRssUpdate,Message) } } }); }
var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
function SaveDateAndShowMessage(DateField,DateValue,Message) { var params={ } params[DateField]=DateValue; chrome.storage.local.set( params,function(){ var options={ type: "basic", title: Messages.SiteUpdated, message: Message, iconUrl: "icon.png" } chrome.notifications.create("",options,function(){ chrome.notifications.onClicked.addListener(function(){ chrome.tabs.create({'url': WebLinks.Home}, function(tab) { }); }); }); }); }
"permissions": [ "storage", "tabs", "alarms", "notifications" ]
chrome.notifications.create("",options,function(){ chrome.notifications.onClicked.addListener(function(){ chrome.tabs.create({'url': WebLinks.Home}, function(tab) { });}); });
"web_accessible_resources": [ "icon.png" ]
خوب؛ کار افزونه تمام شده است ولی اجازه دهید این بار امکانات افزونه را بسط دهیم:
"options_page": "popup.html"
جایزگزینی صفحات یا Override Pages
"chrome_url_overrides": { "newtab": "newtab.htm" }
ایجاد یک تب اختصاصی در Developer Tools
"devtools_page": "devtools.htm"
<script src="devtools.js"></script>
chrome.devtools.panels.create( "Dotnettips Updater Tools", "icon.png", "devtoolsui.htm", function(panel) { } );
- One-Time Requests یا درخواستهای تک مرتبهای
- Long-Lived Connections یا اتصالات بلند مدت یا مصر
درخواستهای تک مرتبه ای
window.addEventListener("load", function() { chrome.extension.sendMessage({ type: "dom-loaded", data: { myProperty : "value" } }); }, true);
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { switch(request.type) { case "dom-loaded": alert(request.data.myProperty ); break; } return true; });
var port = chrome.runtime.connect({name: "my-channel"}); port.postMessage({myProperty: "value"}); port.onMessage.addListener(function(msg) { // do some stuff here });
chrome.runtime.onConnect.addListener(function(port) { if(port.name == "my-channel"){ port.onMessage.addListener(function(msg) { // do some stuff here }); } });
نمونه کد
آپلود نهایی کار در Google web store
برای آپلود نهایی کار به google web store که در آن تمامی برنامهها و افزونههای کروم قرار دارند بروید. سمت راست آیکن تنظیمات را بزنید و گزینه developer dashboard را انتخاب کنید تا صفحهی آپلود کار برای شما باز شود. دایرکتوری محتویات اکستنشن را zip کرده و آپلود نمایید. توجه داشته باشید که محتویات و سورس خود را باید آپلود کنید نه فایل crx را. بعد از آپلود موفقیت آمیز، صفحهای ظاهر میشود که از شما آیکن افزونه را در اندازه 128 پیکسل میخواهد بعلاوه توضیحاتی در مورد افزونه، قیمت گذاری که به طور پیش فرض به صورت رایگان تنظیم شده است، لینک وب سایت مرتبط، لینک محل پرسش و پاسخ برای افزونه، اگر لینک یوتیوبی در مورد افزونه دارید، یک شات تصویری از افزونه و همینطور چند تصویر برای اسلایدشو سازی که در همان صفحه استاندارد آنها را توضیح میدهد و در نهایت گزینهی جالبتر هم اینکه اکستنشن شما برای چه مناطقی تهیه شده است که متاسفانه ایران را ندیدم که میتوان همه موارد را انتخاب کرد. به خصوص در مورد ایران که آی پیها هم صحیح نیست، انتخاب ایران چنان تاثیری ندارد و در نهایت گزینهی publish را میزنید که متاسفانه بعد از این صفحه درخواست میکند برای اولین بار باید 5 دلار آمریکا پرداخت شود که برای بسیاری از ما این گزینه ممکن نیست.
سورس پروژه را میتوانید از اینجا ببینید و خود افزونه را از اینجا دریافت کنید.
<input type="button" onclick="startTrace('Some Text')" value="startTrace" /> <input type="button" onclick="startError()" value="test Error" /> <script type="text/javascript"> function startTrace(str) { return method1(100, 200); } function method1(arg1, arg2) { return method2(arg1 + arg2 + 100); } function method2(arg1) { var var1 = arg1 / 100; return method3(var1); } function method3(arg1) { console.trace(); var total = arg1 * 100; return total; } function testCount() { // do something console.count("testCount() Calls Count ."); } function startError() { testError(); } function testError() { var errorObj = new Error(); errorObj.message = "this is a test error"; console.exception(errorObj); } function testFunc() { var t = 0; for (var i = 0; i < 100; i++) { t += i; } } </script>
- console.log(object[,object,...])
این دستور یک پیغام در کنسول چاپ میکند .
console.log("This is a log message!");
این دستور را میتوانیم به شکلهای مختلفی فراخوانی کنیم .
مثلا :
console.log(1 , "+" , 2 , "=", (1+2));
در این دستور میتوانیم از چند حرف جایگزین هم استفاده کنیم .
مثال :
console.log("Firebug 1.0 beta was %s in December %i.","released",2006);
اگر در رشتهی مورد نظر ، یک شیء ( تابع ، آرایه ، ... ) برای جایگزین %o ارسال کنیم ، در خروجی آن شیء بصورت لینک نمایش داده میشود که با کلیک بروی آن ، فایرباگ آن شیء را در تب مناسبش Inspect میکند .
مثال :
console.log("this is a test functin : %o",testFunc);
نتیجه :
و زمانی که بروی لینک testFunc کلیک کنیم :
یک ترفند : بوسیله جایگزین %o توانستیم به تابع مورد نظر لینک بدهیم . اگر بجای جایگزین %o از %s استفاده کنیم ، میتوانیم بدنهی تابع را ببینیم :
console.log("this is a test functin : %s",testFunc);
توسط جایگزین %c هم میتوانید خروجی را فرمت کنید .
console.log("%cThis is a Style Formatted Log","color:green;text-decoration:underline;");
نتیجه :
- console.debug(object[, object, ...])
- console.info(object[, object, ...])
- console.warn(object[, object, ...])
- console.error(object[, object, ...])
مشابه با دستور log عمل میکنند با این تفاوت که خروجی را با استایل متفاوتی نمایش میدهند .
همچنین هر یک از این دستورات ، توسط دکمههای همنام در کنسول قابل فیلتر شدن هستند .
- console.assert(expression[, object, ...])
چک میکند که عبارت ارسال شده true هست یا نه . اگر true نبود ، پیغام وارد شده را چاپ و یک استثناء ایجاد میکند .
console.assert(1==1,"this is a test error"); console.assert(1!=1,"this is a test error");
نتیجه :
- console.clear()
- console.dir(object)
- console.dirxml(node)
- console.profile([title])
- console.profileEnd()
- console.trace()
با این متد میتوانید پی ببرید که از کجا و توسط چه متدهایی برنامه به قسمت trace رسیده . برای درک بهتر مجددا اسکریپت صفحهی تست این مقاله را بررسی کنید ( جایی که متد trace قرار داده شده است ) .
اکنون صفحهی تست را باز کنید و بروی دکمهی startTrace کلیک کنید . خروجی ظاهر شده در کنسول را از پایین به بالا بررسی کنید .
حتما متوجه شدید که متد method3 چگونه در کدهایمان فراخوانی شده است !؟
ابتدا با کلیک بروی دکمهی startTrace ، متد startTrace اجرا شده و به همین ترتیب متد startTrace متد method1 ، متد method1 هم متد method2 و در نهایت method2 متد method3 را فراخوانی کرده است .
دستور trace زمانی که در حال بررسی کدهای برنامه نویسان دیگر هستید ، بسیار میتواند به شما کمک کند .
- console.group(object[, object, ...])
با این دستور میتوانید لاگهای کنسول را بصورت تو در تو گروه بندی کنید .
console.group("Group1"); console.log("Log in Group1"); console.group("Group2"); console.log("Log in Group2"); console.group("Group3"); console.log("Log in Group3");
- console.groupCollapsed(object[, object, ...])
این دستور معادل دستور قبلی است با این تفاوت که هنگام ایجاد ، گروه را جمع میکند .
- console.groupEnd()
به آخرین گروه بندی ایجاد شده خاتمه میدهد .
- console.time(name)
یک تایمر با نام داده شده ایجاد میکند . زمانی که نیاز دارید زمان طی شده بین 2 نقطه را اندازه گیری کنید ، این تابع مفید خواهد بود .
- console.timeEnd(name)
تایمر همنام را متوقف و زمان طی شده را چاپ میکند .
console.time("TestTime"); var t = 1; for (var i = 0; i < 100000; i++) { t *= (i + t) } console.timeEnd("TestTime");
- console.timeStamp()
توضیحات کامل را از اینجا دریافت کنید .
- console.count([title])
تعداد دفعات فراخوانی شدن کدی که این متد در آنجا قرار دارد را چاپ میکند .
البته ظاهرا در ورژن 10.0.1 که بنده با آن کار میکنم ، این دستور بی عیب کار نمیکند . زیرا بجای آنکه در هربار فراخوانی ، در همان خط تعداد فراخوانی را نمایش بدهد ، فقط اولین لاگ را آپدیت میکند .
- console.exception(error-object[, object, ...])
یک پیغام خطا را به همراه ردیابی کامل اجرای کدها تا زمان رویداد خطا ( مانند متد trace ) چاپ میکند .
در صفحهی تست این متد را اجرا کنید :
startError();
توجه کنید که ما برای مشاهدهی عملکرد صحیح این دستور ، آن را در تابع testError قرار دادیم و بوسیله تابع startError آن فراخوانی کردیم .
- console.table(data[, columns])
بوسیله این دستور میتوانید مجموعه ای از اطلاعات را بصورت جدول بندی نمایش بدهید .
این متد از متدهای جدیدی است که در فایرباگ قرار داده شده است .
برای اطلاعات بیشتر به اینجا مراجعه کنید .
این توابع معادل توابع همنامشان در خط فرمان هستند که در قسمت قبل با عملکردشان آشنا شدیم .
دارا بودن امکانات بسیار قدرتمند و پشتیبانی از محیط فارسی و همچنین پشتیبانی آنها جهت پاسخگویی به سوالات، چه از طریق ایمیل یا چت، از نقاط قوت این ابزار به شمار میروند. در جدول مقایسات میتوانید تفاوت نسخههای موجود این گزارش ساز را مشاهده کنید. برای استفاده در MVC از نسخه وب آن استفاده میکنیم.
در این مقاله قصد داریم با نحوه راه ندازی این ابزار در وب (MVC) آشنا شویم که شامل مباحث زیر میشود:
- استفاده از EF به عنوان منبع داده و ارسال آنها به سمت گزارش ساز
- نحوه طراحی فایل MRT و بایند کردن دادههای اطلاعاتی و ایجاد جدول
- استفاده از امکانات فایل خروجی ، چاپ و پیش نمایش و...
- بررسی Direction جهت استفاده در محیطهای فارسی زبان
- نحوه ارسال اطلاعات بین دو اکشن متفاوت
طراحی فایل MRT
فایل MRT در واقع یک قالب (Template) خالی از مقادیر متغیر است که در StimulSoft Studio به طراحی آن میپردازیم و در برنامه خود، این مقادیر متغیر را با اطلاعات دلخواه خود جایگزین میکنیم. تصویر زیر یک نمونه از یک گزارش خالی است که ابتدا آن را طراحی کرده و سپس در برنامه آن را مورد استفاده قرار میدهیم:
برای اینکه فایل MRT بتواند دیتاهای لازمی را که به آن پاس میدهیم، بخواند و در جای مشخص شده قرار بدهد، باید یک BussinessObject برای آن ایجاد کنیم. بعد از اینکه یک گزارش جدید ایجاد کردید، در سمت راست به قسمت Dictionary بروید و در قسمت BussinessObject گزینه NewBussinessObject را انتخاب کنید. یک نام و نام مستعار که عموما هم یکی است، برای آن انتخاب کنید. در زیر همان پنجره شما میتوانید ستونهای اطلاعاتی خود را تعریف کنید. در اینجا من میخواهم اطلاعات یک راننده را به همراه خودروی وی، نشان دهم. برای همین، من دو موجودیت راننده و خودروی راننده را دارم. پس اسم Business Object را DriverReport میگذارم و ستونهای اطلاعاتی فقط راننده (بدون در نظر گرفتن خودروی وی) را وارد میکنم.
در همین کادر بالا شما میتوانید تصیم بگیرید که آیا میخواهید اطلاعات خودرو را به همراه دیگرستونهای اطلاعاتی راننده، ایجاد کنید یا اینکه برای خودرو یک نوع مجزا انتخاب کنید. اگر تنها یک خودرو برای راننده باشد، شاید راحتتر باشید همانند اطلاعات راننده با آن رفتار کنید. ولی اگر مثلا بخواهید خودرویهای گذشته راننده را هم جز لیست داشته باشید، بهتر است یک Business Object جدید متعلق و زیر مجموعه Business Object راننده ایجاد کنید. در اینجا چون تنها یک خودرو است، من آن اطلاعات آن را به همراه راننده، ارسال میکنم. شکل زیر ساختار درختی از گزارش بالاست:
شکل زیر هم یک ساختار دیگر از یک گزارش است که شامل Business objectهای مختلف میشود:
سپس همین فیلدها را به سمت صفحه خالی بکشانید. با دو بار کلیک روی فیلدهای قرار گرفته در صفحه، با نحوه بایند کردن مقادیر آشنا میشوید؛ هر فیلدی که قرار است دیتای آن بایند شود، باید به شکل زیر در بخش Expression پنجره باز شده، نوشته شود:
{driverReport.LastName}
در دیکشنری همچنین انواع دیگری از فیلدها نیز به چشم میخورد:
متغیرها: این نوع فیلد یک متغیر است که به طور جداگانه میتواند مقداردهی شود و از آن بیشتر برای ارسال دادههای تکی چون تصاویر، تاریخ شمسی و ... میتوان استفاده کرد.
متغیرهای سیستمی: این نوع متغیرها توسط خود گزارش ساز به طور مستقیم پر میشوند که شامل شماره صفحه، تاریخ و زمان، تعداد صفحات، مقادیر دو ارزشی (آیا صفحه آخر گزارش است؟) و ... میشود.
توابع: گزارش ساز شامل یک سری توابع آماده برای اعمال تغییرات بر روی دادهها میباشد که در دستههای مختلفی چون کار با رشتهها، زمان، ریاضیات و... قرار گرفتهاند.
بعد از تکمیل آن، فایل MRT را ذخیره و در یک دایرکتوری در ساختار پروژه قرار دهید.
راه اندازی گزارش ساز در ASP.Net MVC
اولین کاری که میکنیم، ورود سه dll اصلی به پروژه است:
Stimulate.Base
Stimulate.Report
Stimulate.Report.MVC
در مرحله بعد یک متد ساخته و یک ویوو را برای صفحه گزارش گیری ایجاد میکنیم:
public ActionResult Report(int id) { return View(); }
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions() { Localization = "~/content/reports/fa.xml", Actions = { GetReportSnapshot = "LoadReportSnapshot", ViewerEvent = "ViewerEvent", ExportReport = "ExportReport", PrintReport = "PrintReport", } }
در نسخههای دو سال اخیر، استفاده از این Helper تفاوتهایی در نحوه استفاده از خصوصیتهای آن کرده است. در این روش جدید، پراپرتیها دسته بندی شده و برای دسترسی به هر کدام باید به بخش آن مراجعه کنید؛ مثلا پراپرتیهای Action، در دسته Actions قرار گرفتهاند یا خصوصیتهای ظاهری در دسته Appearance، یا گزینههای مرتبط با خروجی گرفتنها، در دسته Export قرار گرفتهاند و الی آخر که در نسخههای پیشین، کد بالا را به شکل زیر، با پیشوند نام دسته مینوشتیم:
@Html.Stimulsoft().StiMvcViewer(new StiMvcViewerOptions() { Localization = "~/content/reports/fa.xml", ActionGetReportSnapshot = "LoadReportSnapshot", ActionViewerEvent = "ViewerEvent", ActionExportReport = "ExportReport", ActionPrintReport = "PrintReport", }
بعد از آن لازم است دیتاها را از طریق EF خوانده و به یک مدل جدید که بر اساس اطلاعات گزارش شماست و قرار است گزارش شما این پراپرتیها را بشناسد، به طور دستی یا با استفاده یک کتابخانه mapping مثل automapper انتقال دهید. یا حتی میتوانید مانند کد زیر از ساختاری ناشناس استفاده کنید. در کد زیر، من به صورت تمرینی اطلاعات یک راننده و خودروی او را انتقال میدهم:
var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 };
var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", car = new { Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 } };
var report = new StiReport(); report.Load(Server.MapPath("~/Content/Reports/driver.mrt")); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date));
var report = new StiReport(); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date)); report.Load(Server.MapPath("~/Content/Reports/driver.mrt"));
پس کد کامل ما برای ایجاد یک گزارش به شکل زیر میشود:
public ActionResult LoadReportSnapshot() { var driver = new { FirstName = "علی", LastName = "یگانه مقدم", NationalCode = "12500000000", FatherName = "حسین", Model = "نام خودرو", MotorNumber = 415244, ProductionYear = 1394, Capacity = 4 }; var report = new StiReport(); report.Load(Server.MapPath("~/Content/Reports/driver.mrt")); report.RegBusinessObject("driverReport", driver); report.Dictionary.Variables.Add("today", DateTime.Today.ToPersianString(PersianDateTimeFormat.Date)); return StiMvcViewer.GetReportSnapshotResult(HttpContext, report); }
اگر دوباره در ویو مربوطه، به سراغ helper برویم میبینیم که سه اکشن متد دیگر وجود دارند که در زیر، به ترتیب با نحوه کار آنها و کد اکشن متد آنها اشاره میکنیم:
Viewer Events : این اکشن متد که تنها یک خط ActionResult استاتیک را فراخوانی میکند، جهت مدیریت رویدادهای گزارش چون: زوم، صفحه بندی گزارش، خروجیها و چاپ میباشد و وجود آن در گزارش از الزامات است.
public virtual ActionResult ViewerEvent() { return StiMvcViewer.ViewerEventResult(); }
PrintReport: برای مدیریت و ارسال گزارشات به دستگاه چاپ میباشد. این اطلاعات از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط PrintReportResult آن را دریافت میکند.
public virtual ActionResult PrintReport() { return StiMvcViewer.PrintReportResult(this.HttpContext); }
ExportReport: گزارش ساز استیمول به شما اجاز میدهد در فرمتهای گوناگونی چون xlsx,docx,pptx,pdf,rtf و ... از گزارش خود خروجی بگیرید. اطلاعات گزارش از طریق شی HttpContext به سمت اکشن متد ارسال شده و توسط ExportReportResult دریافت میشود.
public virtual ActionResult ExportReport() { return StiMvcViewer.ExportReportResult(this.HttpContext); }
البته خوشبختانه این مشکل در حالت پیش نمایش و چاپ و خروجیها دیده نمیشود و فقط مختص نمایش روی فرم Html است. برای حل این مشکل ممکن است از گزینه یا پراپرتی RightToLeft، در بخش Appearance موجود در helper استفاده کنید که البته استفاده از آن مانند تصویر بالا، فقط محدود به container گزارش و نوار ابزار آن میشود. برای حل این مشکل کافی است کد css زیر را به صفحه گزارش اضافه کنید تا مشکل حل شود:
.stiMvcViewerReportPanel table{ direction:ltr !important; }
حال حتما پیش خود میگویید که این روش برای اطلاعات ایستا و تمرینی مناسب است و من چگونه باید پارامترهای ارسالی به اکشن متد Report را به اکشن متد LoadReportSnapshot ارسال کنم. برای این منظور استفاده از SessionStateها زیاد توصیه شدهاست:
public virtual ActionResult Report(int id) { TempData["id"]=id; return View(); } public virtual ActionResult LoadReportSnapshot() { var driverId = (int)TempData ["id"]; //..... }
public virtual ActionResult Report(int id) { return View(); } public virtual ActionResult LoadReportSnapshot(int id) { //..... }
نکته بسیار مهم: گزارش ساز استیمول متاسفانه شامل تنظیم پیش فرض نامناسبی است که عملیات کش را بر روی گزارشها اعمال میکند. به عنوان مثال تصور کنید من صفحه گزارش شخصی به نام «وحید نصیری» را باز میکنم و در تب دیگر گزارش شخص دیگری با نام «علی یگانه مقدم» را باز میکنم. حال اگر کاربر به سراغ تب آقای نصیری برود و بخواهد چاپ یا خروجی درخواست کند، اشتباها با گزارش علی یگانه مقدم روبرو خواهد شد که این اتفاق به دلیل کش شدن رخ میدهد. برای غیر فعال کردن این قابلیت پیش فرض، کد زیر را در Helper اضافه کنید:
Server = { GlobalReportCache = false }
- «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid »
- «استفاده از Kendo UI templates»
صورت مساله
میخواهیم به یک چنین تصویری برسیم؛ که دارای گروه بندی اطلاعات است، فرمت شرطی روی ستون قیمت آن اعمال شده و تاریخ نمایش داده شده در آن نیز شمسی است. همچنین برای مثال ستون قیمت آن دارای ته جمع صفحه بوده و به علاوه یک دکمهی سفارشی به نوار ابزار آن اضافه شدهاست.
مباحث قسمت سمت سرور این مثال با مطلب «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid» دقیقا یکی است. فقط یک خاصیت AddDate نیز در اینجا اضافه شدهاست.
تغییر نحوهی نمایش pager
اگر به قسمت pager تصویر فوق دقت کنید، یک دکمهی refresh، تعداد موارد هر صفحه و امکان وارد کردن دستی شماره صفحه، در آن پیش بینی شدهاست. این موارد را با تنظیمات ذیل میتوان فعال کرد:
$("#report-grid").kendoGrid({ // ... pageable: { previousNext: true, // default true numeric: true, // default true buttonCount: 5, // default 10 refresh: true, // default false input: true, // default false pageSizes: true // default false },
بومی سازی پیغامهای گرید
پیغامهای فارسی را که در تصویر فوق مشاهده میکنید، حاصل پیوست فایل kendo.fa-IR.js هستند:
<!--https://github.com/loudenvier/kendo-global/blob/master/lang/kendo.fa-IR.js--> <script src="js/messages/kendo.fa-IR.js" type="text/javascript"></script>
گروه بندی اطلاعات
برای گروه بندی اطلاعات در Kendo UI Grid دو قسمت باید تغییر کنند.
ابتدا باید فیلد پیش فرض گروه بندی در قسمت data source گرید تعریف شود:
var productsDataSource = new kendo.data.DataSource({ // ... group: { field: "IsAvailable" }, // ... });
$("#report-grid").kendoGrid({ // ... groupable: true, // allows the user to alter what field the grid is grouped by // ...
اضافه کردن ته جمعهای ستونها
این ته جمعها که aggregate نام دارند باید در دو قسمت فعال شوند:
var productsDataSource = new kendo.data.DataSource({ //... aggregate: [ { field: "Name", aggregate: "count" }, { field: "Price", aggregate: "sum" } ] //... });
سپس این متدها را میتوان مطابق فرمت hash syntax قالبهای Kendo UI در قسمت footerTemplate هر ستون تعریف کرد:
$("#report-grid").kendoGrid({ // ... columns: [ { field: "Name", title: "نام محصول", footerTemplate: "تعداد: #=count#" }, { field: "Price", title: "قیمت", footerTemplate: "جمع: #=kendo.toString(sum,'c0')#" } ] // ... });
فرمت شرطی اطلاعات
در ستون قیمت، میخواهیم اگر قیمتی بیش از 2490 بود، با پس زمینهی قهوهای و رنگ زرد نمایش داده شود. برای این منظور میتوان یک قالب Kendo UI سفارشی را طراحی کرد:
<script type="text/x-kendo-template" id="priceTemplate"> #if( Price > 2490 ) {# <span style="background:brown; color:yellow;">#=kendo.toString(Price,'c0')#</span> #} else {# #= kendo.toString(Price,'c0')# #}# </script>
$("#report-grid").kendoGrid({ //... columns: [ { field: "Price", title: "قیمت", template: kendo.template($("#priceTemplate").html()), footerTemplate: "جمع: #=kendo.toString(sum,'c0')#" } ] //... });
فرمت تاریخ میلادی به شمسی در حین نمایش
برای تبدیل سمت کلاینت تاریخ میلادی به شمسی از کتابخانهی moment-jalaali.js کمک گرفته شدهاست:
<!--https://github.com/moment/moment/--> <script src="js/cultures/moment.min.js" type="text/javascript"></script> <!--https://github.com/jalaali/moment-jalaali--> <script src="js/cultures/moment-jalaali.js" type="text/javascript"></script>
$("#report-grid").kendoGrid({ //... columns: [ { field: "AddDate", title: "تاریخ ثبت", template: "#=moment(AddDate).format('jYYYY/jMM/jDD')#" } ] //... });
اضافه کردن یک دکمه به نوار ابزار گرید
نوار ابزار Kendo UI Grid را نیز میتوان توسط یک قالب سفارشی آن مقدار دهی کرد:
$("#report-grid").kendoGrid({ // ... toolbar: [ { template: kendo.template($("#toolbarTemplate").html()) } ] // ... });
<script> // این اطلاعات برای تهیه خروجی سمت سرور مناسب هستند function getCurrentGridFilters() { var dataSource = $("#report-grid").data("kendoGrid").dataSource; var gridState = { page: dataSource.page(), pageSize: dataSource.pageSize(), sort: dataSource.sort(), group: dataSource.group(), filter: dataSource.filter() }; return kendo.stringify(gridState); } </script> <script id="toolbarTemplate" type="text/x-kendo-template"> <a class="k-button" href="\#" onclick="alert('gridState: ' + getCurrentGridFilters());">نوار ابزار سفارشی</a> </script>
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
KendoUI05.zip
تغییرات مورد نیاز جهت فعال سازی ویرایش، حذف و افزودن رکوردهای jqGrid
میخواهیم در بدو نمایش گرید، یک ستون خاص دارای دکمههای ویرایش و حذف ظاهر شوند:
برای اینکار تنها کافی است در انتهای ستونهای تعریف شده، یک ستون خاص را با formatter مساوی actions ایجاد کنیم:
colModel: [ { // سایر ستونها name: 'myac', width: 80, fixed: true, sortable: false, resize: false, formatter: 'actions', formatoptions: { keys: true } } ],
نیاز است تعاریف سایر ستونهایی را که باید قابلیت ویرایش داشته باشند، به نحو ذیل تغییر دهیم:
colModel: [ { name: 'Id', index: 'Id', align: 'right', width: 70, editable: false }, { name: 'Name', index: 'Name', align: 'right', width: 100, editable: true, edittype: 'text', editoptions: { maxlength: 40 }, editrules: { required: true } }, { name: 'Supplier.Id', index: 'Supplier.Id', align: 'right', width: 110, editable: true, edittype: 'select', editoptions: { dataUrl: '@Url.Action("SuppliersSelect","Home")' }, editrules: { required: true } }, { name: 'Category.Id', index: 'Category.Id', align: 'right', width: 110, editable: true, edittype: 'select', editoptions: { dataUrl: '@Url.Action("CategoriesSelect","Home")' }, editrules: { required: true } }, { name: 'Price', index: 'Price', align: 'center', width: 100, formatter: 'currency', formatoptions: { decimalSeparator: '.', thousandsSeparator: ',', decimalPlaces: 2, prefix: '$' }, editable: true, edittype: 'text', editrules: { required: true, number: true, minValue: 0 } }, { name: 'myac', width: 80, fixed: true, sortable: false, resize: false, formatter: 'actions', formatoptions: { keys: true } } ],
- edittype آن بیانگر کنترلی است که باید حین ویرایش آن سلول خاص ظاهر شود. برای مثال اگر text باشد، یک text box و اگر مانند حالت Supplier.Id مساوی select تعریف شود، یک drop down را ظاهر خواهد کرد. برای مقدار دهی این drop down میتوان editoptions و سپس dataUrl آنرا مقدار دهی نمود.
public ActionResult SuppliersSelect() { var list = ProductDataSource.LatestProducts; var suppliers = list.Select(x => new SelectListItem { Text = x.Supplier.CompanyName, Value = x.Supplier.Id.ToString(CultureInfo.InvariantCulture) }).ToList(); return PartialView("_SelectPartial", suppliers); }
@model IList<SelectListItem> @Html.DropDownList("srch", Model)
- خاصیت editrules، برای مباحث اعتبارسنجی اطلاعات ورودی توسط کاربر پیش بینی شدهاست. برای مثال اگر required: true در آن تنظیم شود، کاربر مجبور به تکمیل این سلول خاص خواهد بود. در اینجا خواصی مانند number و integer از نوع bool، خاصیتهای minValue و maxValue از نوع عددی، email, url, date, time از نوع bool و custom قابل تنظیم است (مثالهای حالت custom را در منابع انتهای بحث میتوانید مطالعه کنید).
- پس از اینکه مشخص شدند کدامیک از ستونها باید قابلیت ویرایش داشته باشند، مسیری که باید اطلاعات نهایی را به سرور ارسال کند، توسط خاصیت editurl مشخص میشود:
$('#list').jqGrid({ caption: "آزمایش چهارم", //url from wich data should be requested url: '@Url.Action("GetProducts","Home")', //url for edit operation editurl: '@Url.Action("EditProduct","Home")',
[HttpPost] public ActionResult EditProduct(Product postData) { //todo: Edit product based on postData return Json(true); }
$('#list').navGrid( '#pager', //enabling buttons { add: true, del: true, edit: false, search: false }, //edit options {}, //add options { width: 'auto', url: '@Url.Action("AddProduct","Home")' }, //delete options { url: '@Url.Action("DeleteProduct","Home")' } );
[HttpPost] public ActionResult DeleteProduct(string id) { //todo: Delete product return Json(true); } [HttpPost] public ActionResult AddProduct(Product postData) { //todo: Add product to repository return Json(true); }
var lastSel; function inlineEdit() { $('input[name=rdEditApproach]').attr('disabled', true); $('#list').navGrid( '#pager', //enabling buttons { add: true, del: true, edit: false, search: false }, //edit options {}, //add options { width: 'auto', url: '@Url.Action("AddProduct","Home")' }, //delete options { url: '@Url.Action("DeleteProduct","Home")' } ); //add onSelectRow event to support inline edit $('#list').setGridParam({ onSelectRow: function (id) { if (id && id != lastSel) { //save changes in row $('#list').saveRow(lastSel, false); lastSel = id; } //trigger inline edit for row $('#list').editRow(id, true); } }); };
- برای فعال سازی خودکار فرمهای افزودن رکوردها و یا ویرایش ردیفهای موجود میتوان از فراخوانی متد formEdit ذیل کمک گرفت:
function formEdit() { $('input[name=rdEditApproach]').attr('disabled', true); $('#list').navGrid( '#pager', //enabling buttons { add: true, del: true, edit: true, search: false }, //edit option { width: 'auto', checkOnUpdate: true, checkOnSubmit: true, beforeShowForm: function (form) { centerDialog(form, $('#list')); } }, //add options { width: 'auto', url: '@Url.Action("AddProduct","Home")', reloadAfterSubmit: false, checkOnUpdate: true, checkOnSubmit: true, beforeShowForm: function (form) { centerDialog(form, $('#list')); } }, //delete options { url: '@Url.Action("DeleteProduct","Home")', reloadAfterSubmit: false }) .jqGrid('navButtonAdd', "#pager", { caption: "حذف ردیفهای انتخابی", title: "Delete Toolbar", buttonicon: 'ui-icon ui-icon-trash', onClickButton: function () { var idsList = jQuery("#list").jqGrid('getGridParam', 'selarrrow'); alert(idsList); //jQuery("#list").jqGrid('delGridRow',idsList,{reloadAfterSubmit:false}); } }); }; function centerDialog(form, grid) { var dlgDiv = $("#editmod" + grid[0].id); var parentDiv = dlgDiv.parent(); // div#gbox_list var dlgWidth = dlgDiv.width(); var parentWidth = parentDiv.width(); var dlgHeight = dlgDiv.height(); var parentHeight = parentDiv.height(); var parentTop = parentDiv.offset().top; var parentLeft = parentDiv.offset().left; dlgDiv[0].style.top = Math.round( parentTop + (parentHeight-dlgHeight)/2 ) + "px"; dlgDiv[0].style.left = Math.round( parentLeft + (parentWidth-dlgWidth )/2 ) + "px"; }
با کلیک بر روی دکمهی افزودن ردیف جدید، صفحهی ذیل به صورت خودکار تولید میشود:
و با کلیک بر روی دکمهی ویرایش ردیفی انتخاب شده، صفحهی ویرایش آن ردیف به همراه مقادیر سلولهای آن ظاهر خواهند شد:
تنظیمات قسمتهای Add و Delete ویرایش توسط فرمها، با حالت ویرایش داخل ردیفی آنچنان تفاوتی ندارد. فقط در اینجا پیش از نمایش فرم، از متد centerDialog برای نمایش صفحات افزودن و ویرایش رکوردها در وسط صفحه، استفاده شدهاست. توسط checkOnUpdate: true, checkOnSubmit: true سبب خواهیم شد تا اگر کاربر مقادیر موجود فرمی را تغییر دادهاست و سعی در بستن فرم، بدون ذخیره سازی اطلاعات کند، پیغام هشدار دهندهای به او نمایش داده شود که آیا میخواهید تغییرات را ذخیره کنید یا خیر؟
- در انتهای متد formEdit، به کمک متد jqGrid و پارامتر navButtonAdd یک دکمهی سفارشی را نیز اضافه کردهایم. اگر به ستون پس از شمارههای خودکار ردیفها، در سمت راست گرید دقت کنید، یک سری chekbox قابل مشاهده هستند. برای فعال سازی خودکار آنها کافی است خاصیت multiselect گرید به true تنظیم شود. اکنون برای دسترسی به این ستونهای انتخاب شده، میتوان از متد jqGrid به همراه پارامترهای getGridParam و selarrrow استفاده کرد. خروجی آن، لیست idهای ستونها است.
برای مطالعه بیشتر
Common Editing Properties
Inline Editing
Form Editing
Cell Editing
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid04.zip
قابلیت چند زبانه و Localization در AngularJs- بخش چهارم و نهایی: Best Practiceهای angular-translate
ex7_load_static_files
<script src="Scripts/angular.js"></script> <script src="Scripts/angular-cookies.js"></script> <script src="Scripts/angular-translate.js"></script> <script src="Scripts/angular-translate-storage-cookie.js"></script> <!-- for override loader methods in angular translate --> <script src="/src/service/loader-static-files.js"></script>
// Register a loader for the static files // So, the module will search missing translation tables under the specified urls. // Those urls are [prefix][langKey][suffix]. $translateProvider.useStaticFilesLoader({ prefix: '/l10n/', suffix: '.json' });
حال ببینیم که این فرآیند در loader-static-files چگونه پیاده سازی شده است. در این فایل یک متد load نوشته شده است که فایلهای static را طبق یک الگوی مشتمل بر prefix و suffix از سرور میخواند. لزومی ندارد که شما فایلها را حتما به صورت JSON و با این پسوند ذخیره کنید. اما چیزی که قطعی است این است که فایلها حتما باید به صورت key value ذخیره شده باشند.
تکه کد زیر اطلاعات فایل loader-static-files را نمایش میدهد.
angular.module('pascalprecht.translate') .factory('$translateStaticFilesLoader', $translateStaticFilesLoader); function $translateStaticFilesLoader($q, $http) { 'use strict'; return function (options) { if (!options || (!angular.isArray(options.files) && (!angular.isString(options.prefix) || !angular.isString(options.suffix)))) { throw new Error('Couldn\'t load static files, no files and prefix or suffix specified!'); } if (!options.files) { options.files = [{ prefix: options.prefix, suffix: options.suffix }]; } var load = function (file) { if (!file || (!angular.isString(file.prefix) || !angular.isString(file.suffix))) { throw new Error('Couldn\'t load static file, no prefix or suffix specified!'); } var deferred = $q.defer(); $http(angular.extend({ url: [ file.prefix, options.key, file.suffix ].join(''), method: 'GET', params: '' }, options.$http)).success(function (data) { deferred.resolve(data); }).error(function () { deferred.reject(options.key); }); return deferred.promise; }; var deferred = $q.defer(), promises = [], length = options.files.length; for (var i = 0; i < length; i++) { promises.push(load({ prefix: options.files[i].prefix, key: options.key, suffix: options.files[i].suffix })); } $q.all(promises).then(function (data) { var length = data.length, mergedData = {}; for (var i = 0; i < length; i++) { for (var key in data[i]) { mergedData[key] = data[i][key]; } } deferred.resolve(mergedData); }, function (data) { deferred.reject(data); }); return deferred.promise; }; } $translateStaticFilesLoader.displayName = '$translateStaticFilesLoader';
همانطور که ملاحظه میکنید، کد فوق یک سرویس با نام $translateStaticFilesLoader را تعریف نموده است. در صورتیکه ما در کنترلر فایل ex7، اصلا نامی از آن نبردیم و تنها از $translateProvider.useStaticFilesLoader استفاده نمودیم! جواب در نحوهی نگارش کد angular-translate نهفته است. در خط 866 فایل angular-translate تکه کد زیر مربوط به تعریف translateStaticFileLoader میباشد. همانطور که ملاحظه میکنید سرویس translateStaticFilesLoader درون فضای نام سرویس translateTable قرار گرفته است. بنابراین ما تنها با تعریف سرویس translateStaticFilesLoader، در حقیقت آن را override نمودهایم. در کد نمونهای که در بخشهای قبلی قرار دادهام یک فایل translate.js نیز قرار دارد که در فولدر src/services قرار گرفته است. این فایل نیز برخی از امکانات و سرویسهای built-in درون angular-translate را سفارشی نموده است.
/** * @ngdoc function * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader * @methodOf pascalprecht.translate.$translateProvider * * @description * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader. * * @param {Object=} options Optional configuration object */ this.useStaticFilesLoader = function (options) { return this.useLoader('$translateStaticFilesLoader', options); };
در این 4 مجموعه سعی کردم تمامی آنچه را که برای ایجاد قابلیت چند زبانه و localization نیاز است و حیاتی بود، تشریح کنم. بنابراین تا کنون دانش خوبی دربارهی این کتابخانه کسب نمودهاید. باقی تمرینها را میتوانید بر حسب نیاز با استفاده از مستندات موجود در angular-translate مطالعه و استفاده نمایید.