CoffeeScript #4
Syntax
Loops
for name in ["Vahid", "Hamid", "Saeid"] alert "Hi #{name}"
var i, len, name, ref; ref = ["Vahid", "Hamid", "Saeid"]; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; alert("Hi " + name); }
for name, i in ["Vahid", "Hamid", "Saeid"] alert "#{i} - Hi #{name}"
alert name for name in ["Vahid", "Hamid", "Saeid"]
names = ["Vahid", "Hamid", "Saeid"] alert name for name in names when name[0] is "V"
var i, len, name, names; names = ["Vahid", "Hamid", "Saeid"]; for (i = 0, len = names.length; i < len; i++) { name = names[i]; if (name[0] === "V") { alert(name); } }
names = "Vahid": "Mohammad Taheri", "Ali": "Ahmadi" alert("#{first} #{last}") for first, last of names
var first, last, names; names = { "Vahid": "Mohammad Taheri", "Ali": "Ahmadi" }; for (first in names) { last = names[first]; alert(first + " " + last); }
num = 6 minstrel = while num -= 1 num + " Hi"
var minstrel, num; num = 6; minstrel = (function() { var _results; _results = []; while (num -= 1) { _results.push(num + " Hi"); } return _results; })();
Arrays
CoffeeScript با الهام گرفتن از Ruby، به وسیله تعیین محدوده، آرایه را ایجاد میکند. محدوده آرایه به وسیله دو عدد تعیین میشوند که با .. یا ... از هم جدا میشوند.range = [1..5]
var range; range = [1, 2, 3, 4, 5];
firstTwo = ["one", "two", "three"][0..1]
var firstTwo; firstTwo = ["one", "two", "three"].slice(0, 2);
numbers = [0..9] numbers[3..5] = [-3, -4, -5]
my = "my string"[0..2]
words = ["Vahid", "Hamid", "Saeid", "Ali"] alert "Stop" if "Hamid" in words
- در صورت تعریف محدوده آرایه به صورت [..3]numbers (که آرایه numbers از قبل تعریف شده باشد)، خروجی، آرایهای از مقادیر موجود در numbers را از خانه شماره 4 تا انتهای آن برمی گرداند.
- در صورت تعریف محدوده آرایه به صورت [..3-]numbers (که آرایه numbers از قبل تعریف شده باشد)، خروجی، آرایهای از مقادیر موجود در numbers را از خانه انتهایی به میزان 3 خانه به سمت ابتدای آرایه برمیگرداند.
- در صورت عدم تعریف محدوده آرایه و فقط استفاده از [..] یا [...] (یک شکل عمل میکنند)، کل مقادیر آرایه اصلی (که از قبل تعریف شده باشد)، برگردانده میشود.
- تفاوت .. و ... در حالتی که دو عدد برای محدوده تعریف شود، در این است که ... آرایه به صورت عدد انتهایی - 1 تعریف میشود. مثلا [3...0] یعنی خانههای آرایه از 0 تا 2 را به عنوان خروجی برگردان.
Aliases
CoffeeScript شامل یک سری نامهای مستعار است که برای خلاصه نویسی بیشتر بسیار مفید هستند. یکی از آن نام ها، @ است که به جای نوشتن this به کار میرود.@name = "Vahid"
this.name = "Vahid";
User::first = -> @records[0]
User.prototype.first = function() { return this.records[0]; };
alert "OK" if name?
if (typeof name !== "undefined" && name !== null) { alert("OK"); }
name = myName ? "-"
var name; name = typeof myName !== "undefined" && myName !== null ? myName : "-";
user.getAddress()?.getStreetName()
var ref; if ((ref = user.getAddress()) != null) { ref.getStreetName(); }
user.getAddress().getStreetName?()
var base; if (typeof (base = user.getAddress()).getStreetName === "function") { base.getStreetName(); }
کامنت گذاری مرتبط با پست های سایت
خواستم خواهش کنم اگر برای شما مقدور هست در بین کدها با کامنت گذاری به مطالب مرتبط با سایت ارجاع دهید (طبق گفته آقای نصیری برای فهمیدن این پروژه نیاز هست که روی مطالب سایت مسلط بود).
با این کار سطح آموزشی این پروژه بسیار غنیتر میشود.
با تشکر
دکمه F5 را بزنید و برنامه را اجرا کنید. یک دکمه میبینید که Text آن عبارت + است.
AngularJS #4
۱- مفهوم این عبارت رو لطفا توضیح بدید:
«در متد addComment وقتی که دیدگاه مورد نظر اضافه شد، به آرایهی کامنتها یک کامنت جدید میافزاییم و چون انقیاد داده دو طرفه است، بالافاصله دیدگاه جدید نیز در view به نمایش در میآید.»
این بلافاصله نشون میده از چی استفاده میکنه؟
۲- من تابع Remvoe رو نوشتم ولی کار نمیکنه. یه تغییراتی دادم که آیدی رو درست بفرست ولی به تابع اصلا وارد نمیشه.
<button type="button" style="float:right;cursor:pointer;" ng-click="remove({{comment.id}},$Index);">X</button>
"Error: [$parse:syntax] Syntax Error: Token '{' invalid key at column 9 of the expression [remove({{comment.id}},$Index);] starting at [{comment.id}},$Index);].
StringBuilder
بهترین روش برای تولید و دستکاری یک رشته (string) طولانی و یا دستکاری متناوب و تکراری یک رشته استفاده از کلاس StringBuilder است. این کلاس در فضای نام System.Text قرار داره. شی String در داتنتفریمورک تغییرناپذیره (immutable)، بدین معنی که پس از ایجاد نمیتوان محتوای اونو تغییر داد. برای مثال اگر شما بخواین محتوای یک رشته رو با اتصال به رشتهای دیگه تغییر بدین، اجازه اینکار را به شما داده نمیشه. درعوض بهصورت خودکار رشتهای جدید در حافظه ایجاد میشه و محتوای دو رشته موجود پس از اتصال به هم درون اون قرار میگیره. این کار درصورتیکه تعداد عملیات مشابه زیاد باشه میتونه تاثیر منفی بر کارایی و حافظه خالی در دسترس برنامه بگذاره.
کلاس StringBuilder با استفاده از آرایهای از کاراکترها، راهحل مناسب و بهینهای رو برای این مشکل فراهم کرده. این کلاس در زمان اجرا به شما اجازه میده تا بدون ایجاد نمونههای جدید از کلاس String، محتوای یک رشته رو تغییر بدین. شما میتونید نمونهای از این کلاس رو بهصورت خالی و یا با یک رشته اولیه ایجاد کنید، سپس با استفاده از متدهای متنوع موجود، محتوای رشته رو با استفاده از انواع داده مختلف و بهصورت دلخواه دستکاری کنید. همچنین با استفاده از متد معروف ()ToString این کلاس میتونید در هر لحظه دلخواه رشته تولیدی رو بدست بیارین. دو پراپرتی مهم کلاس StringBuilder رفتارش رو درهنگام افزودن دادههای جدید کنترل میکنن:
Capacity , Length
پراپرتی Capacity اندازه بافر کلاس StringBuilder را تعیین میکنه و Length طول رشته جاری موجود در این بافر رو نمایش میده. اگر پس از افزودن داده جدید، طول رشته از اندازه بافر موجود بیشتر بشه، StringBuilder باید یه بافر جدید با اندازهای مناسب ایجاد کنه تا رشته جدید رو بتونه تو خودش نگه داره. اندازه این بافر جدید بهصورت پیشفرض دو برابر اندازه بافر قبلی درنظر گرفته میشه. بعد تمام رشته قبلی رو تو این بافر جدید کپی میکنه.
از برنامه ساده زیر میتونین برای بررسی این مسئله استفاده کنین:
using System.IO; using System.Text; class Program { static void Main() { using (var writer = new StreamWriter("data.txt")) { var builder = new StringBuilder(); for (var i = 0; i <= 256; i++) { writer.Write(builder.Capacity); writer.Write(","); writer.Write(builder.Length); writer.WriteLine(); builder.Append('1'); // <-- Add one character } } } }
دقت کنین که برای افزودن یک کاراکتر استفاده از دستور Append با نوع داده char (همونطور که در بالا استفاده شده) بازدهی بهتری نسبت به استفاده از نوع داده string (با یک کاراکتر) داره. خروجی کد فوق به صورت زیره:
16, 0 16, 1 16, 2 ... 16,14 16,15 16,16 32,17 ...
استفاده نامناسب و بیدقت از این کلاس میتونه منجر به بازسازیهای متناوب این بافر شده که درنهایت فواید کلاس StringBuilder رو تحت تاثیر قرار میده. درهنگام کار با کلاس StringBuilder اگر از طول رشته موردنظر و یا حد بالایی برای Capacity آن آگاهی حتی نسبی دارین، میتونید با مقداردهی مناسب این پراپرتی از این مشکل پرهیز کنید.
نکته: مقدار پیشفرض پراپرتی Capacity برابر 16 است.
هنگام مقداردهی پراپرتیهای Capacity یا Length به موارد زیر توجه داشته باشید:
- مقداردهی Capacity به میزانی کمتر از طول رشته جاری (پراپرتی Length)، منجر به خطای زیر میشه:
System.ArgumentOutOfRangeException
خطای مشابهی هنگام مقداردهی پراپرتی Capacityبه بیش از مقدار پراپرتی MaxCapacity رخ میدهه.البته این مورد تنها درصورتیکه بخواین اونو به بیش از حدود 2 گیگابایت (Int32.MaxValue) مقداردهی کنید پیش میاد!
- اگر پراپرتی Length را به مقداری کمتر از طول رشته جاری تنظیم کنید، رشته به اندازه طول تنظیمی کوتاه (truncate) میشه.
- اگر مقدار پراپرتی Length را به میزانی بیشتر از طول رشته جاری تنظیم کنید، فضای خالی موجود در بافر با space پر میشه.
- تنظیم مقدار Length بیشتر از Capacity، منجر به مقداردهی خودکار پراپرتی Capacity به مقدار جدید تنظیم شده برای Length میشه.
در ادامه به یک مثال برای مقایسه کارایی تولید یک رشته طولانی با استفاده از این کلاس میپردازیم. تو این مثال از دو روش برای تولید رشتههای طولانی استفاده میشه. روش اول که همون روش اتصال رشتهها (Concat) به هم هستش و روش دوم هم که استفاده از کلاس StringBuilder است. در قطعه کد زیر کلاس مربوط به عملیات تست رو مشاهده میکنین:
namespace StringBuilderTest { internal class SbTest1 { internal Action<string> WriteLog; internal int Iterations { get; set; } internal string TestString { get; set; } internal SbTest1(int iterations, string testString, Action<string> writeLog) { Iterations = iterations; TestString = testString; WriteLog = writeLog; } internal void StartTest() { var watch = new Stopwatch(); //StringBuilder watch.Start(); var sbTestResult = SbTest(); watch.Stop(); WriteLog(string.Format("StringBuilder time: {0}", watch.ElapsedMilliseconds)); //Concat watch.Start(); var concatTestResult = ConcatTest(); watch.Stop(); WriteLog(string.Format("ConcatTest time: {0}", watch.ElapsedMilliseconds)); WriteLog(string.Format("Results are{0} the same", sbTestResult == concatTestResult ? string.Empty : " NOT")); } private string SbTest() { var sb = new StringBuilder(TestString); for (var x = 0; x < Iterations; x++) { sb.Append(TestString); } return sb.ToString(); } private string ConcatTest() { string concat = TestString; for (var x = 0; x < Iterations; x++) { concat += TestString; } return concat; } } }
دو روش بحثشده در کلاس مورد استفاده قرار گرفته و مدت زمان اجرای هر کدوم از عملیاتها به خروجی فرستاده میشه. برای استفاده از این کلاس هم میشه از کد زیر در یک برنامه کنسول استفاده کرد:
do { Console.Write("Iteration: "); var iterations = Convert.ToInt32(Console.ReadLine()); Console.Write("Test String: "); var testString = Console.ReadLine(); var test1 = new SbTest1(iterations, testString, Console.WriteLine); test1.StartTest(); Console.WriteLine("----------------------------------------------------------------"); } while (Console.ReadKey(true).Key == ConsoleKey.C); // C = continue
برای نمونه خروجی زیر در لپتاپ من (Corei7 2630QM) بدست اومد:
تنظیم خاصیت Capacity به یک مقدار مناسب میتونه تو کارایی تاثیرات زیادی بگذاره. مثلا در مورد مثال فوق میشه یه متد دیگه برای آزمایش تاثیر این مقداردهی به صورت زیر به کلاس برناممون اضافه کنیم:
private string SbCapacityTest() { var sb = new StringBuilder(TestString) { Capacity = TestString.Length * Iterations }; for (var x = 0; x < Iterations; x++) { sb.Append(TestString); } return sb.ToString(); }
تو این متد قبل از ورود به حلقه مقدار خاصیت Capacity به میزان موردنظر تنظیم شده و نتیجه بدست اومده:
مشاهده میشه که روش concat خیلی کنده (دقت کنین که طول رشته اولیه هم بیشتر شده) و برای ادامه کار مقایسه اون رو کامنت کردم و نتایج زیر بدست اومد:
میبینین که استفاده مناسب از مقداردهی به خاصیت Capacity میتونه تا حدود 300 درصد سرعت برنامه ما رو افزایش بده. البته همیشه اینطوری نخواهد بود. ما در این مثال مقدار دقیق طول رشته نهایی رو میدونستیم که باعث میشه عملیات افزایش بافر کلاس StringBuilder هیچوقت اتفاق نیفته. این امر در واقعیت کمتر پیش میاد.
مقاله موجود در سایت dotnetperls شکل زیر رو به عنوان نتیجه تست بازدهی ارائه میده:
- در مواقعی که عملیاتی همچون مثال بالا طولانی و حجیم ندارین بهتره که از این کلاس استفاده نکنین چون عملیاتهای داخلی این کلاس در عملیات کوچک و سبک (مثل ابتدای نمودار فوق) موجب کندی عملیات میشه. همچنین استفاده از اون نیاز به کدنویسی بیشتری داره.
- این کلاس فشار کمتری به حافظه سیستم وارد میکنه. درمقابل استفاده از روش concat موجب اشغال بیش از حد حافظه میشه که خودش باعث اجرای بیشتر و متناوبتر GC میشه که در نهایت کارایی سیستم رو کاهش میده.
- استفاده از این کلاس برای عملیات Replace (و یا عملیات مشابه) در حلقهها جهت کار با رشتههای طولانی و یا تعداد زیادی رشته میتونه بسیار سریعتر و بهتر عمل کنه چون این کلاس برخلاف کلاس string اشیای جدید تولید نمیکنه.
- یه اشتباه بزرگ در استفاده از این کلاس استفاده از "+" برای اتصال رشتههای درون StringBuilder هست. هرگز از این کارها نکنین. (فکر کنم واضحه که چرا)
روش متداول کار با کتابخانهی iTextSharp ، ایجاد شیء Document ، سپس ایجاد PdfWriter برای نوشتن در آن، گشودن سند و ... افزودن اشیایی مانند Paragraph ، PdfPTable ، PdfPCell و غیره به آن است و در نهایت بستن سند. راه میانبری هم برای کار با این کتابخانه وجود دارد و آن هم استفاده از امکانات فضای نام iTextSharp.text.html.simpleparser آن میباشد. به این ترتیب میتوان به صورت خودکار، یک محتوای HTML را تبدیل به فایل PDF کرد.
مثال : نمایش یک متن HTML ساده انگلیسی
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;
namespace HeadersAndFooters
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var html = @"<span style='color:blue'><b>Testing</b></span>
<i>iTextSharp's</i> <u>HTML to PDF capabilities</u>";
var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), null);
foreach (var htmlElement in parsedHtmlElements)
{
pdfDoc.Add(htmlElement);
}
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
نکتهی جدید کد فوق، استفاده از متد HTMLWorker.ParseToList است. به این ترتیب parser کتابخانهی iTextSharp وارد عمل شده و html تعریف شده را به معادل المانهای بومی خودش تبدیل میکند؛ مثلا تبدیل به chunk یا pdfptable و امثال آن. در نهایت در طی یک حلقه، این عناصر به صفحه اضافه میشوند.
البته باید دقت داشت که HTMLWorker امکان تبدیل عناصر پیچیده، تودرتو و چندلایه HTML را ندارد؛ اما بهتر از هیچی است!
همهی اینها خوب! اما به درد ما فارسی زبانها نمیخورد. همین متغیر html فوق را با یک متن فارسی جایگزین کنید، چیزی نمایش داده نخواهد شد. البته این هم نکته دارد که در ادامه ذکر خواهد شد.
جهت نمایش متون فارسی نیاز است تا نکات ذکر شده در مطلب «فارسی نویسی و iTextSharp» رعایت شوند که شامل:
- تعیین صریح قلم
- تعیین encoding
- استفاده از عناصر دربرگیرندهای است که خاصیت RunDirection را پشتیبانی میکنند؛ مانند PdfPCell و غیره
به این ترتیب خواهیم داشت:
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;
using iTextSharp.text.html;
namespace HeadersAndFooters
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
//روش صحیح تعریف فونت
FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
StyleSheet styles = new StyleSheet();
styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.FONTFAMILY, "tahoma");
styles.LoadTagStyle(HtmlTags.BODY, HtmlTags.ENCODING, "Identity-H");
var html = @"<span style='color:blue'><b>آزمایش</b></span>
کتابخانه <i>iTextSharp</i> <u>جهت بررسی فارسی نویسی</u>";
var parsedHtmlElements = HTMLWorker.ParseToList(new StringReader(html), styles);
PdfPCell pdfCell = new PdfPCell { Border = 0 };
pdfCell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
foreach (var htmlElement in parsedHtmlElements)
{
pdfCell.AddElement(htmlElement);
}
var table1 = new PdfPTable(1);
table1.AddCell(pdfCell);
pdfDoc.Add(table1);
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}
همانطور که ملاحظه میکنید ابتدا قلمی در cache قلمهای این کتابخانه ثبت میشود (FontFactory.Register). سپس نوع قلم و encoding آن توسط یک StyleSheet تعریف شده و به HTMLWorker.ParseToList ارسال میگردد و در نهایت به کمک یک المان دارای RunDirection، در صفحه نمایش داده میشود.
نکته:
ممکن است که به متغیر html ، یک table ساده html را نسبت دهید. در این حالت پس از تنظیم style یاد شده، در هر سلول این html table ، متون فارسی به صورت معکوس نمایش داده خواهند شد که این هم یک نکتهی کوچک دیگر دارد:
foreach (var htmlElement in parsedHtmlElements)
{
if (htmlElement is PdfPTable)
{
var table = (PdfPTable)htmlElement;
table.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
foreach (var row in table.Rows)
{
foreach (var cell in row.GetCells())
{
cell.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
}
}
}
pdfCell.AddElement(htmlElement);
}
در قسمتی که قرار است المانهای معادل به pdfCell اضافه شوند، آنها را بررسی کرده و RunDirection آنها را RTL خواهیم کرد.
کاربردها:
بدیهی است این حالت برای تهیه گزارشات پیشرفتهتر برای مثال تهیه قالبهایی که در حین تهیه PDF ، قسمتهایی از آنها توسط برنامه نویس Replace میشوند، بسیار مناسب است.
همچنین مطلب «بارگذاری یک یوزرکنترل با استفاده از جیکوئری» و متد RenderUserControl مطرح شده در آن که در نهایت یک قطعه کد HTML را به صورت رشته به ما تحویل میدهد، میتواند جهت تهیه گزارشهای پویایی که برای مثال قسمتی از آن یک GridView بایند شده حاصل از یک یوزر کنترل است، مورد استفاده قرار گیرد.
Cookie - قسمت اول
مقدمه
GET /index.html HTTP/1.1 Host: www.dotnettips.info |
GET https://www.dntips.ir/ HTTP/1.1 Host: www.dotnettips.info Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: BlogPost-1175=NLOpR%2fgHcUGqPL8dZYv3BDDqgd4xOtiiNxHIp1rD%2bAQ%3d; BlogComment-5002=WlS1iaIsiBnQN1UDD4p%2fHFvuoxC3b8ckbw78mAWXZOSWMpxPlLo65%2bA40%2flFVR54; ReaderEmail=DP%2bx4TEtMT2LyhNQ5QqsArka%2fWALP5LYX8Y
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: cookieName=cookieValue Set-Cookie: cookieName2=cookieValue2; Expires=Thr, 10-Jun-2021 10:18:14 GMT ... |
HTTP/1.1 200 OK Date: Wed, 30 Jan 2013 20:25:15 GMT Server: Microsoft-IIS/6.0 X-Powered-By: ASP.NET X-Compressed-By: HttpCompress Set-Cookie: .ASPXROLES=NzZ9qIRpCWHofryYglbsQFv_SSGPn7ivo0zKFoS94gcODVdIKQAe_IBwuc-TQ-03jGeIkZabTuxA0A3k2-nChy7iAWw9rPMYXSkqzMkizRFkDC0k3gQTkdLqLmmeIfnL9UjfMNWO8iVkYQrSv24ecbpFDSQCH827V2kEj8k2oCm_5sKRSmFpifh4N7kinEi0vomG1vW4Rbg9JWMhCgcvndvsFsXxpj-NiEikC1RqHpiLArIyalEMEN-cIuVtRe7uoo938u9l-7OXb8yzXucVl4bdqPy2DXM3ddWzb3OH1jSFM6gxwJ8qRZDlSGmEEbhji7rA-efI4aYGTKx6heWfUsY6E2k73jJLbuZ3RB4oNwRYmz8FRB0-vm1pO7rhF1JIoi1YB17ez-Ox5chNEFkPVREanHVU9DxboJ5dKgN-2B5udUFPunnshbN8EBhixbFQOpqRiiOK4uWWaWy3rVEJYpCCDBRctKCfEyYD1URFYeajB0AXmiMUTcGeuUtwb-XFjbQZnbylmMF3EJgG16bcc1IEkTAUv1JfKjaql0XGWJI1; path=/; HttpOnly Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 106727 <!DOCTYPE html> <html> ...
GET /index2.html HTTP/1.1 Host: www.example.org Cookie: cookieName=cookieValue; cookieName2=cookieValue2 Accept: */* |
رشتهها در ES 6
در بیشتر زبانهای برنامهنویسی قابلیتی تحت عنوان String Interpolation وجود دارد. منظور، فرآیند جایگزین کردن مقادیر، با یکسری placeholder درون یک رشته است. در نسخههای قبلی جاوا اسکریپت محدودیتهایی در استفاده از رشتهها وجود داشت و امکان انجام این کار به صورت توکار مهیا نبود. یعنی برای پیادهسازی این قابلیت میتوانستیم با تغییر prototype شیء String و یا روشهای دیگری اینحالت را پیادهسازی کنیم (+):
// First, checks if it isn't implemented yet. if (!String.prototype.format) { String.prototype.format = function() { var args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != 'undefined' ? args[number] : match ; }); }; } "Hello, {0}, I'm a simple {1}, Today is: {2}".format("World", "String", new Date()); // Output "Hello, World, I'm a simple String, Today is: Tue Dec 29 2015 10:21:10 GMT+0330 (Iran Standard Time)"
اما در ES 6 با کمک قابلیتی تحت عنوان template string این محدودیتها به طور قابل ملاحظهایی کاهش پیدا کرده است. در واقع یک template string، یک رشتهی جاوا اسکریپتی است که به جای (" ") و یا (' ') درون دو کاراکتر (` `) یا به اصطلاح back-tick character محصور خواهد شد. این ویژگی در سناریوهای مختلفی کاریرد دارد. از این ویژگی میتوانیم جهت الحاق رشتهها استفاده کنیم. به عنوان مثال میتوانیم کد زیر را:
let category = "music"; let id = 2112; let url = "http://apiserver/" + category + "/" + id;
با کمک template string به اینصورت بازنویسی کنیم:
let category = "music"; let id = 2112; let url = `http://apiserver/${category}/${id}`;
و یا میتوانیم مثال ابتدای مطلب را به اینصورت بازنویسی کنیم:
console.log(`Hello, ${"World"}, I'm a simple ${"String"}, Today is: ${new Date()}`);
همانطور که عنوان شد برای استفاده از این قابلیت باید رشتهی موردنظر را درون دو کاراکتر (` `) قرار دهیم. سپس درون این کاراکترها میتوانیم literal text و همچنین یکسری placeholder جهت جایگزین کردن با مقادیر و عبارات موردنظر داشته باشیم. این placeholderها نیز با استفاده از سینتکس { }$ قابل تعریف هستند. لازم به ذکر است که عبارت موردنظرمان را باید درون دو علامت { } بنویسیم. مقادیر درون این دو علامت میتوانند هر عبارت معتبر جاوا اسکریپتی باشند:
let a = 5; let b = 10; console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`); // "Fifteen is 15 and // not 20."
در کد فوق متغیرهای a و b درون placeholderهای مربوطه جایگزین خواهند شد. همانطور که مشاهده میکنید، این سینتکس نسبت به سینتکس + که برای الحاق رشتهها قبلاً مورد استفاده قرار میگرفت خیلی بهتر و خواناتر است.
به صورت خلاصه:
- کد درون placeholder میتواند هر عبارت جاوا اسکریپتی باشد.
- اگر مقدار درون placeholder یک رشته نباشد٬ توسط متد toString به رشته تبدیل خواهد شد.
- اگر بخواهید درون template string از یک کاراکتر backtick استفاده کنید٬ میتوانید به این صورت عمل کنید:
`\`` // یا "`"
Multiline Strings
console.log(`string text line 1 string text line 2`); // "string text line 1 // string text line 2"
همانطور که مشاهده میکنید، template string از متنهای چندخطی نیز به خوبی پشتیبانی میکند. به عنوان مثال اگر رشتهی فوق را درون گیومه مینوشتیم میبایستی از سینتکس + برای الحاق دو خط فوق استفاده میکردیم:
console.log("string text line 1\n"+ "string text line 2"); // "string text line 1 // string text line 2"
محدودیتهای template strings
- به صورت خودکار کارکترهای خاص را برای شما escape نمیکند (جهت جلوگیری از آسیبپذیریهای XSS).
- به صورت کامل از کتابخانههایی جهت اعمال internationalization پشتیبانی نمیکند.
- جایگزینی برای کتابخانههایی مانند Mustache و Nunjucks نیست.
var x = 1; var y = 3; var result = upper `${x} + ${y} is ${x+y}`; console.log(result); // Output // 1 + 3 IS 4
let upper = function(strings, ...values){ let result = ""; for(var i = 0; i < strings.length; i++){ result += strings[i]; if(i < values.length){ result += values[i]; } } return result.toUpperCase(); };
strings = ["", " + ", " is ", ""]; values = [1, 3, 4];
یک مثال عملی
میخواهیم یک tag template ایجاد کنیم که به انتهای اعداد درون یک تملپت، مقدار "تومان" را اضافه کرده و خود عدد را نیز به صورت سه رقم سه رقم جدا کند. میخواهیم رشتهی زیر همراه با مقادیر آن:
var name = "سیروان عفیفی"; var price = 150000; var text = withToman `${name} با تشکر از خرید شما, مبلغ قابل پرداخت: ${price}`; alert(text);
function withToman(strings, ...values) { return strings.reduce( function (s, v, idx) { if(idx > 0) { if(typeof values[idx - 1] == "number") { s += `${values[idx - 1].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")} تومان` } else { s += values[idx -1]; } } return s + v; }, ""); }
در مورد بررسی ارتباط با دادهها در WPF باید سه مورد را بشناسیم:
- DataContext: این شیء اتصالش را به منبع دادهها برقرار کرده و هر موقع دادهای را نیاز داریم، از طریق این شیء تامین میشود.
- DataBinding: یک واسطه بین DataContext و هر آن چیزی است که قرار است از دادهها تغذیه کند. در تعریفی رسمیتر میگوییم: روشی ساده و قدرتمند بوده و واسطی است بین مدل تجاری و رابط کاربری. هر زمانی که دادهای تغییر کند، ما را آگاه میسازد که میتواند یک ارتباط یک طرفه یا دو طرفه باشد.
- DataTemplate: نحوهی فرمت بندی و نمایش دادهها را تعیین میکند.
ابتدا قبل از هر چیزی کلاس فرم قبلی را پیاده سازی میکنیم. در این پیاده سازی از یک enum برای انتخاب زمینههای کاری هم کمک گرفته ایم و هچنین با یک متد ایستا، منبع دادهی تک رکوردی را جهت تست برنامه آماده کردهایم:
public enum FieldOfWork { Actor=0, Director=1, Producer=2 } public class Person { public string Name { get; set; } public bool Gender { get; set; } public string ImageName { get; set; } public string Country { get; set; } public DateTime Date { get; set; } public IList<FieldOfWork> FieldOfWork { get; set; } public static Person GetPerson() { return new Person() { Name = "Leo", Gender = true, ImageName ="man.jpg", Country = "Italy", Date = DateTime.Now }; } }
حالا لازم است که این منبع داده را در اختیار DataContext بگذاریم. وارد بخش کد نویسی شده و در سازندهی پنجره کد زیر را مینویسیم:
DataContext = Person.GetPerson();
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = Person.GetPerson(); } }
همانطور که میدانید متن کنترل TextBox توسط خصوصیت Text پر میشود و برای همین در این خصوصیت مینویسیم:
Text="{Binding Name}"
Source="{Binding ImageName}"
اطلاع از به روزرسانی در منبع دادهها:
حال این نکته پیش میآید که اگر همین اطلاعات دریافت شده در مدل منبع داده تغییر کند، چگونه میتوانیم از این موضوع مطلع شده و همین اطلاعات به روز شده را که نمایش دادهایم، تغییر دهیم. بنابراین جهت اطلاع از این مورد، کد را به شکل زیر تغییر میدهیم.
کار را از یک کلاس آغاز میکنیم. از اینترفیس INotifyPropertyChanged ارث بری کرده و در آن یک رویداد و یک متد را تعریف میکنیم و کمی در هم در تعریف Propertyها دست میبریم. فعلا اینکار را فقط برای پراپرتی Name انجام میدهیم:
private string _name; public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } }
در بخش Setter آن خصوصیت هم باید این متد را صدا زده و نام خصوصیت را به آن پاس بدهیم تا موقعی که مدل تغییر پیدا کرد، بگوید که خصوصیت Name بوده است که تغییر کرده است.
برای اینکه بدانیم کد واقعا کار میکند و تستی بر آن زده باشیم، فعلا دکمهی Save را به Change تغییر میدهیم و کد داخل پنجره را بدین صورت تغییر میدهیم:
public partial class MainWindow : Window { private Person person; public MainWindow() { InitializeComponent(); person = Person.GetPerson(); DataContext = person; } private void Button_Click(object sender, RoutedEventArgs e) { person.Name = "Leonardo Decaperio"; } }
این کد واقعا کدی مفید جهت به روزرسانی است ولی مشکلی دارد که نام پراپرتی باید به صورت String به آن پاس شود که در یک برنامه بزرگ این مورد یک مشکل خواهد شد و اگر نام خصوصیت تغییر کند باید نام داخل آن هم تغییر کند؛ پس کد را به شکل دیگری بازنویسی میکنیم:
private string _name; public string Name { get { return _name; } set { _name = value; OnPropertyChanged(); } } private void OnPropertyChanged([CallerMemberName] string property="") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } }
OnPropertyChanged();
کد این قسمت
در قسمتهای آینده به بررسی تبدیل مقادیر و framework element و کنترلها میپردازیم.