string greeting = "Hello, C#";
که در این حالت مجموعهای از کاراکترها را ایجاد خواهد کرد:
- خانههای آن یک ضرب پر نمیشوند بلکه به ترتیب، خانه به خانه پر میشوند.
- قبل از انتساب متن باید باید از طول متن مطمئن شویم تا بتوانیم تعداد خانهها را بر اساس آن ایجاد کنیم.
- همه عملیات آرایهها از پر کردن ابتدای کار گرفته تا هر عملی، نیاز است به صورت دستی صورت بگیرد و تعداد خطوط کد برای هر کاری هم بالا میرود.
string str = "abcde"; char ch = str[1]; // ch == 'b' str[1] = 'a'; // Compilation error! ch = str[50]; // IndexOutOfRangeException
string a="Hello \"C#\""; string b="Hello \r\n C#"; //مساوی با اینتر string c="C:\\a.jpg"; //چاپ خود علامت \ -مسیردهی
string c=@"C:\a.jpg";// == "C:\\a.jpg"
string source = "Some source"; string assigned = source;
string hel = "Hel"; string hello = "Hello"; string copy = hel + "lo";
string hello = "Hello"; string same = "Hello";
برای اطلاعات بیشتر در این زمینه این لینک را مطالعه نمایید.
Console.WriteLine(word1.Equals(word2, StringComparison.CurrentCultureIgnoreCase));
string score = "sCore"; string scary = "scary"; Console.WriteLine(score.CompareTo(scary)); Console.WriteLine(scary.CompareTo(score)); Console.WriteLine(scary.CompareTo(scary)); // Console output: // 1 // -1 // 0
string alpha = "alpha"; string score1 = "sCorE"; string score2 = "score"; Console.WriteLine(string.Compare(alpha, score1, false)); Console.WriteLine(string.Compare(score1, score2, false)); Console.WriteLine(string.Compare(score1, score2, true)); Console.WriteLine(string.Compare(score1, score2, StringComparison.CurrentCultureIgnoreCase)); // Console output: // -1 // 1 // 0 // 0
نکته : برای مقایسه برابری دو رشته از متد Equals یا == استفاده کنید و فقط برای تعیین کوچک یا بزرگ بودن از compareها استفاده نمایید. دلیل آن هم این است که برای مقایسه از فرهنگ culture فعلی سیستم استفاده میشود و نظم جدول یونیکد را رعایت نمیکنند و ممکن است بعضی رشتههای نابرابر با یکدیگر برابر باشند. برای مثال در زبان آلمانی دو رشته "SS" و "ß " با یکدیگر برابر هستند.
عبارات با قاعده Regular Expression
string doc = "Smith's number: 0898880022\nFranky can be " + "found at 0888445566.\nSteven's mobile number: 0887654321"; string replacedDoc = Regex.Replace( doc, "(08)[0-9]{8}", "$1********"); Console.WriteLine(replacedDoc); // Console output: // Smith's number: 08******** // Franky can be found at 08********. // Steven' mobile number: 08********
اتصال رشتهها در Loop
DateTime dt = DateTime.Now; string s = ""; for (int index = 1; index <= 20000; index++) { s += index.ToString(); } Console.WriteLine(s); Console.WriteLine(dt); Console.WriteLine(DateTime.Now); Console.ReadKey();
- قسمتی از حافظه به طور موقت برای این دور جدید حلقه، گرفته میشود که به آن بافر میگوییم.
- رشته قبلی به بافر انتقال میابد که بسته به مقدار آن زمان بر و کند است؛ 5 کیلو یا 5 مگابایت یا 50 مگابایت و ...
- شماره تولید شده جدید به بافر چسبانده میشود.
- بافر به یک رشته تبدیل میشود وجایی برای خود در حافظه Heap میگیرد.
- حافظه رشته قدیمی و بافر دیگر بلا استفاده شدهاند و توسط GC پاکسازی میشوند که ممکن است عملیاتی زمان بر باشد.
String Builder
string declared = "Intern pool"; string built = new StringBuilder("Intern pool").ToString();
StringBuilder sb = new StringBuilder(); sb.Append("Numbers: "); DateTime dt = DateTime.Now; for (int index = 1; index <= 200000; index++) { sb.Append(index); } Console.WriteLine(sb.ToString()); Console.WriteLine(dt); Console.WriteLine(DateTime.Now); Console.ReadKey();
StringBuilder sb = new StringBuilder(15); sb.Append("Hello, C#!");
استفاده از متد ایستای string.Format
DateTime date = DateTime.Now; string name = "David Scott"; string task = "Introduction to C# book"; string location = "his office"; string formattedText = String.Format( "Today is {0:MM/dd/yyyy} and {1} is working on {2} in {3}.", date, name, task, location); Console.WriteLine(formattedText);
<Window> <Grid> <Label Content="Label" /> <Button Content="Button" /> </Grid> </Window>
Dependency Properties
خاصیتهای وابستگی همان خاصیتها یا property هایی هستند در ویندوزفرم با آنها سر و کله میزدید ولی در اینجا تفاوتهایی با پراپرتیهای قبلی وجود دارد که باعث ایجاد مزایای زیادی شده است.
اول اینکه بر خلاف پراپرتیهای ویندوز فرم که در خود فیلدهای تعیین شده همان کنترل ذخیره میشدند، در این روش کلید (نام پراپرتی) و مقدار آن داخل یک شیء دیکشنری قرار میگیرند که از شیء DependencyObject ارث بری شده است و این شیء والد یک متد با نام GetValue برای دریافت مقادیر دارد. مزیت این روش این است که بیخود و بیجهت مانند روش قبلی، ما فیلدهایی را تعریف نمیکنیم که شاید به نصف بیشتر آنها، حتی نیازی نداریم. در این حالت تنها فیلدهایی از حافظه را دریافت و ذخیره میکنیم که واقعا به آنها نیاز داریم. فیلدها یا مقادیر پیش فرض موقع ایجاد شیء در آن ذخیره میشوند.
دومین مزیت این روش خاصیت ارث بری مقادیر از عناصر بالاتر درخت منطقی است. موقعی که از طرف شما برای فرزندان این عنصر مقداری تعیین نشده باشد، سیستم به سمت گرهها یا عناصر بالا یا والد حرکت میکند و اولین عنصری را که مقدارش تنظیم شده باشد، برای فرزندان در نظر میگیرد. به این استراتژی یافتن یک مقدار، استراتژی Resolution میگویند.
سومین مزیت آن وجود یک سیستم اعلان یا گزارش آنی است. در صورتی که شما یک تابع callback را برای یک پراپرتی ست نمایید، با تغییر این پراپرتی تابع معرفی شده صدا زده خواهد شد.
جادوی پشت صحنه
مقادیر پراپرتیها در کلاسی استاتیک به اسم Dependency Property ذخیره میشوند که این ذخیره در حالت نام و مقدار است و مقدار آن شامل callback و مقدار پیش فرض است. شکل زیر نتیجهی شکل دقیقتری را نسبت به قبلی در هنگام پیمایش درخت منطقی به سمت بالا، نشان میدهد.
نحوهی تعریف یک خاصیت وابسته که باید به صورت ایستا تعریف شود به صورت زیر است و برای دریافت و درج مقدار جدید از یک پراپرتی معمولی کمک میگیریم:
// Dependency Property public static readonly DependencyProperty CurrentTimeProperty = DependencyProperty.Register( "CurrentTime", typeof(DateTime), typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now)); // .NET Property wrapper public DateTime CurrentTime { get { return (DateTime)GetValue(CurrentTimeProperty); } set { SetValue(CurrentTimeProperty, value); } }
در مورد خاصیتهای وابسته و کدنویسی آن ها در مطالب آینده بیشتر بحث خواهیم کرد.
برای شروع کار با Backload ابتدا به قسمت Nutget میرویم که در مسیر زیر است :
Backload. A Proffesional Full Featured Asp.Net FileUpload Controller *
پس از نصب دو مورد بالا ، موارد زیر را که در دو عکس پایین میبینید، به پروژهی شما اضافه خواهند شد:
*نکاتی که باید بدانید:
عکس هایی که شما آپلود میکنید در پوشهی Files موجود در ریشهی پروژهی شما قرار میگیرند این پوشه بوسیلهی خود ابزار Backload ساخته میشود.
چنانچه بخواهید در پوشهی Files پوشهای ایجاد کنید، ابتدا View آنرا باز کنید. این View در پروژه، در مسیر Views/ BackloadDemo/Index قرار دارد .
در داخل تگ form یک Hidden Field با نام objectContext اضافه میکنید. Value ایی که شما به این Hidden Field نسبت میدهید، نام پوشهی شما در پوشهی Files خواهد بود. مانند تصویر زیر: در اینجا پوشهای با نام 2014-02 در پوشهی Files وقتی که فایلی را باFile Upload آپلود میکنیم، ایجاد میشود.
چنانچه بخواهید در پوشهای که خودتان ایجاد کردید (که در این مثال 2014-02 میباشد) پوشههای متعدد دیگری هم داشته باشید Hidden Field ای با نام uploadContext ایجاد میکنید؛ مانند تصویر زیر :
اکنون اگر فایل جدیدی را آپلود کنید در مسیر
ذخیره میشود . یعنی بین نام هر پوشه از سمی کولن ; در Value استفاده میکنید.
تا اینجا ما میتوانیم بوسیلهی ابزار Backload عکسها را آپلود ، حذف و مسیر آپلود عکسها را تغییر دهیم.اگر توجه کرده باشید دفعات بعد که پروژه را اجرا میکنید عکسهای موجود در پوشه، در گرید ویو File Upload به شما نمایش داده خواهد شد. حال اگر بخوایم عکسهای موجود در پوشهی دیگری را نمایش دهیم باید چه کار کنیم؟!
گاهی اوقات نیاز داریم که محتویات پوشهای خاص را در گرید ویو File Upload مان نمایش دهیم. برای این کار شما باید کنترلر FileUploadController که در فایل ضمیمه در آموزش هست را در پوشهی Controller پروژهی خود کپی کنید .اگر شما این کنترلر را باز کنید مواردی مانند کلمه کلیدی Task و async را مشاهده خواهید کرد. این موارد در .Net Framework 4 شناسایی نمیشود. برای همین در ابتدای آموزش تاکید کردم که .Net Framework 4.5 و یا بالاتر را برای پروژهی خود انتخاب کنید .
در تابع Handler_GetFilesRequestStarted در این کنترلر باید مشخص کنید که فایلهای موجود در کدام مسیر را برای شما نمایش دهد؛ آن هم با استفاده از دستور زیر :
اکنون قبل از آنکه پروژه را اجرا کنید فایل Backload.Demo.js که در مسیر Scripts/Fileupload هست را باز کنید و url موجود در آنرا مانند عکس زیر تغیییر دهید :
حالا پروژه را اجرا کنید. خواهید دید تمامی فایلهای موجود در مسیری را که شما مشخص کردهاید، برایتان نمایش خواهد داد.
چنانچه بخواهید تعداد مثلا 5 فایل برای شما در گرید ویو مربوط به FileUpload نمایش داده شود، به تابع handler_GetFilesRequestFinished میروید. متغیر limit مشخص میکند که 5 فایل نمایش داده شود. میتوانید این عدد را به دلخواه خود تغییر دهید.
نکتهی بسیار مهم دیگری که باید به آن توجه شود مربوط به Hidden Field نام پوشهها میباشد. بار دیگر پروژه را اجرا کنید. با استفاده از ابزاری مثل FireBug کدهای html صفحهی خود را ببینید. Hidden Field ایی با نام objectContext را پیدا کنید و Value آنرا به test تغییر دهید. فایلی را آپلود کنید حالا به پوشهی Files موجود در ریشهی پروژه بروید. میبینید که پوشهای با نام testایجاد شده و فایلی هم که آپلود کردید در آن قرار دارد که این یک اشکال است. برای اینکه جلوی این گونه کارها را بگیریم به تابع handler_StoreFileRequestStartedAsync میرویم و کد زیر را مینویسیم :
دستور e.Context.PipelineControl.ExecutePipeline = false; باعث میشود که اجرای تابع متوقف شود.
فایل ضمیمه :FileUploadController-462d551688cf48c68cb55343ac5464f3.zip
برای مشاهده مثالهای دیگری دربارهی Backload به این لینک بروید.
موفق باشید.
این نوشتار اولین آموزش من در این سایت میباشد و جا دارد از دوست خوبم "محبوبه قربانی" که باعث شد من با MVC آشنا شوم تشکر کنم.
در پست قبل به بررسی انتخاب عناصر بر اساس موقعیت پرداختیم، در این پست به بحث "استفاده از انتخاب کنندههای سفارشی jQuery" خواهیم پرداخت.
4-1- استفاده از انتخاب کنندههای سفارشی jQuery
در پستهای قبلی (^ و ^ ) تعدادی از انتخاب کنندههای CSS که هر کدامشان موجب قدرت و انعطاف پذیری انتخاب اشیا موجود در صفحه میشوند را بررسی کردیم. با این وجود فیلترهای انتخاب کننده قدرتمندتری وجود دارند که توانایی ما را برای انتخاب بیشتر میکنند.
به عنوان مثال اگر بخواهید از میان تمام چک باکس ها، گزینه هایی را که تیک خورده اند انتخاب نمایید، از آنجا که تلاش برای مطابقت حالتهای اولیه کنترلهای HTML را بررسی میکنیم، jQuery انتخابگر سفارشی checked: را پیشنهاد میکند، که مجموعه از عناصر را که خاصیت checked آنها فعال باشد را برای ما برمی گرداند. براس مثال انتخاب کننده input تمامی المانهای <input> را انتخاب میکند، و انتخاب کننده input:checked تمامی inputهایی را انتخاب میکند که checked هستند. انتخاب کننده سفارشی checked:یک انتخاب کننده خصوصیت CSS عمل میکند (مانند [foo=bar]). ترکیب این انتخاب کنندهها میتواند قدرت بیشتری به ما بدهد، انتخاب کننده هایی مانند radio:checked: و checkbox:checked: .
همانطور هم که قبلا بیان شد، jQuery علاوه بر پشتیبانی از انتخاب کنندههای CSS تعدادی انتخاب کننده سفارشی را نیز شامل میشود که در جدول 3-2 شرح داده شده است.
توضیح | انتخاب کننده |
عناصری را انتخاب میکند که تحت کنترل انیمیشن میباشند. در پستهای بعدی انیمیشنها توضیح داده میشوند. | animated: |
عناصر دکمه را انتخاب میکند، عناصری مانند (input[type=submit]، input[type=reset]، input[type=button]، یا button) | button: |
عناصر Checkbox را انتخاب میکند، مانند ([input[type=checkbox). | checkbox: |
عناصر checkboxها یا دکمههای رادیویی را انتخاب میکند که در حالت انتخاب باشند. | checked: |
عناصری ر انتخاب میکند که دارای عبارت foo باشند. | contains(foo) //c: |
عناصر در حالت disabled را انتخاب میکند. | disabled: |
عناصر در حالت enabledرا انتخاب میکند. | enabled: |
عناصر فایل را انتخاب میکند، مانند ([input[type=file). | file: |
عناصر هدر مانند h1 تا h6 را انتخاب میکند. | header: |
عناصر مخفی شده را انتهاب میکند. | hidden: |
عناصر تصویر را انتخاب میکند، مانند ([input[type=image). | image: |
عناصر فرم مانند input ، select، textarea، button را انتخاب میکند. | input: |
انتخاب کنندهها را برعکس میکند. | not(filter)//c: |
عناصری که فرزندی دارند را انتخاب میکند. | parent: |
عناصر password را انتخاب میکند، مانند ([input[type=password). | password: |
عناصر radio را انتخاب میکند، مانند ([input[type=radio). | radio: |
دکمههای reset را انتخاب میکند، مانند ([input[type=reset یا [button[type=reset). | raset: |
عناصری (عناصر option) را انتخاب میکند که در وضعیت selected قراردارند. | selected: |
دکمههای submit را انتخاب میکند، مانند ([input[type=submit یا [button[type=submit). | submit: |
عناصر text را انتخاب میکند، مانند ([input[type=text). | text: |
عناصری را که در وضعیت visibleباشند انتخاب میکند. | visible: |
:checkbox:checked:enabled
این فیلترها و انتخاب کنندهها کاربردهای وسیعی در صفحات اینترنتی دارند، آیا آنها حالت معکوسی نیز دارند؟
استفاده از فیلتر not:
برای آنکه نتیجه انتخاب کنندهها را معکوس کنیم میتوانیم از این فیلتر استفاده کنیم. برای مثال دستور زیر تمام عناصری را که checkBox نیستند را انتخاب میکند:
input:not(:checkbox)
استفاده از فیلتر has:
در اینجا دیدیم که CSS انتخاب کننده قدرتمندی را ارایه کرده است که فرزندران یک عنصر را در هر سطحی که باشند (حتی اگر فرزند مستقیم هم نباشند) انتخاب میکند. برای مثال دستور زیر تمام عناصر span را که در div معرفی شده باشند را انتخاب میکند:
div span
اما اگر بخواهیم انتخابی برعکس این انتخاب داشته باشیم، باید چه کنیم؟ برای این کار باید تمام divهایی که دارای عنصر span میباشد را انتخاب کرد. برای چنین انتخابی از فیلتر has: استفاده میکنیم. به دستور زیر توجه نمایید، این دستور تمام عناصر div را که در آنها عنصر span معرفی شده است را انتخاب میکند:
div:has(span)
برای برخی انتخابهای پیچیده و مشکل، این فیلتر و مکانیزم بسیار کارا میباشد و به سادگی ما را به هدف دلخواه میرساند. فرض کنید میخواهیم آن خانه از جدول که دارای یک عنصر عکس خاص میباشد را پیدا کنیم. با توجه به این نکته که آن عکس از طریق مقدار src قابل تشخیص میباشد، با استفاده از فیلتر has: دستوری مانند زیر مینویسیم:
$('tr:has(img[src$="foo.png"])')
این دستور هر خانه از جدول را که این عکس در آن قرار گرفته باشد را انتخاب میکند.
همانگونه که دیدیم jQuery گزینههای بسیار متعددی را به منظور انتخاب عناصر موجود در صفحه برای ما مهیا کرده است که میتوانیم هر عنصری از صفحه را انتخاب و سپس تغییر دهیم که تغییر این عناصر در پستهای آینده بحث خواهد شد.
موفق و موید باشید.
new Vue({ el: '#app', template: '<div>Hello DNT</div>' });
new Vue({ el: '#app', data() { return { blogTitle: 'DNT' } }, render: function (createElement) { return createElement('h1', this.blogTitle) } });
Vue.component('child', { template: '<div>Hello DNT users</div>' });
Vue.component('child', { props: ['text'], template: `<div> {{ text }} </div>` }); new Vue({ el: '#app', data() { return { message: 'Hello DNT!' } } });
<child :text="message"></child>
Vue.component('blogPost', { props: { post: { type: Object, required: true } }, template: `<div> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> </div>` });
[Vue warn]: Invalid prop: type check failed for prop "post". Expected Object, got String. found in ---> <BlogPost> <Root>
Vue.component('blogPost', { props: { post: { type: Object, required: true, validator: obj => { const titleIsValid = typeof obj.title === 'string'; const bodyIsValid = typeof obj.body === 'string'; const isValid = titleIsValid && bodyIsValid; if (!isValid) { console.warn("prop is not valid"); return false; } return true; } } }, template: `<div> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> </div>` });
Vue.component('blogPost', { props: { post: { type: Object, validator: obj => { const titleIsValid = typeof obj.title === 'string'; const bodyIsValid = typeof obj.body === 'string'; const isValid = titleIsValid && bodyIsValid; if (!isValid) { console.warn("prop is not valid"); return false; } return true; }, default: function() { return { title: 'Vue is fun!', body: 'Vue is fun..................' } } } }, template: `<div> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> </div>` });
data: function () { return { stars: 5, hover: 5 } },
var dt = { stars: 5, hover: 5 }; Vue.component('blogPost', { data: function() { return dt; }, props: // as before..., template: `<div class="blog-post"> <h1>{{ post.title }}</h1> <p>{{ post.body }}</p> <div class="star-wrap"> <span v-for="n in 5" class="star" :class="{ full: hover >= n+1 }" @click="stars = n+1" @mouseover="hover = n+1" @mouseout="hover = stars" ></span> </div> </div>` });
برای رفع این مشکل کافی است به اینصورت دیتا را تعریف کنیم:
Vue.component('blogPost', { data: function() { return { stars: 5, hover: 5 } }, props: // as before..., template: // as before });
تغییر دیتا درون کامپوننتها
تا اینجا توانستیم از کامپوننت والد دادههایی را به کامپوننتهای فرزند ارسال کنیم. اکنون میخواهیم قابلیت تغییر دیتای تعریف شدهی درون کامپوننت والد را درون کامپوننتها نیز داشته باشیم:
Vue.component('child', { props: ['message'], methods: { changeName() { this.message = "New Name!..." } }, template: '#child-template' }); new Vue({ el: '#app', data() { return { name: 'DNT!' } } });
تمپلیت کامپوننت فوق نیز به صورت x-template درون DOM تعریف شده است:
<script type="text/x-template" id="child-template"> <div> <p>{{ message }}</p> <button @click="changeName">Change Name</button> </div> </script>
فراخوانی کامپوننت نیز به اینصورت میباشد:
<div id="app"> <child :message="name"></child> </div>
همانطور که مشاهده میکنید، دیتای name را از طریق ویژگی message توانستهایم به کامپوننت child ارسال کنیم. درون تمپلیت آن نیز یک دکمه را برای تغییر مقدار این ویژگی تعریف کردهایم. تغییر این ویژگی نیز یک assignment ساده است. اما اگر بر روی دکمهی Change Name کلیک کنید، هشدار زیر را درون کنسول مشاهده خواهید کرد:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message" found in ---> <Child> <Root>
دلیل آن نیز مشخص است؛ زیرا با تغییر این ویژگی، کامپوننت والد از وجود تغییرات مطلع نشدهاست و فقط این تغییرات، درون کامپوننت child صورت گرفتهاست. برای اطلاعرسانی کامپوننت والد میتوانیم از یک ایونت ویژه استفاده کنیم و کامپوننت والد را از وجود تغییرات مطلع کنیم:
changeName() { this.message = "New Name!...", this.$emit("change-name", this.message); }
برای تگ child نیز این ایونت را اضافه خواهیم کرد:
<child :message="name" @change-name="name = $event"></child>
در اینحالت با تغییر ویژگی message، مقدار دیتای name نیز بلافاصله تغییر پیدا خواهد کرد.
Slots
Slot یک روش عالی برای جایگزینی محتوای درون یک کامپوننت است. فرض کنید میخواهیم کامپوننتمان به صورت زیر باشد:
<modal> Hello </modal>
در اینحالت باید درون تمپلیت مکان قرارگیری Hello را تعیین کنیم. اینکار را میتوانیم با قرار دادن تگ slot انجام دهیم:
Vue.component('modal', { template: ` ... <div class="modal-body"> <slot></slot> </div> ... ` });
اکنون هر محتوایی که درون تگ modal قرار گیرد، در قسمت slot نمایش داده خواهد شد. این نوع slot به صورت پیشفرض میباشد. در واقع میتوانیم slotها را نیز نامگذاری کنیم. به عنوان مثال یک slot برای عنوان modal، یک slot برای بدنه modal و یک slot دیگر برای فوتر modal تعریف کنیم:
Vue.component('modal', { template: ` <div class="modal fade" id="detailsModal" tabindex="-1" role="dialog" aria-labelledby="detailsModalLabel" aria-hidden="false"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="detailsModalLabel"> <slot name="title"></slot> </h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> ` });
اکنون میتوانیم محتوای مورد نظر را برای قرارگیری درون slotها تعیین کنیم:
<modal> <template slot="title">Title</template> <template slot="body">Lorem ipsum dolor sit amet.</template> <template slot="footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Save changes</button> </template> </modal>
ایجاد گزارشات Crosstab در PdfReport
list.Pivot(x => new { نامستون =
در این مقاله قصد داریم با استفاده از جاوااسکریپت خالص، یک برنامهی ساده را با الگوی MVC انجام دهیم. این برنامه، عملیات CRUD را پیاده سازی میکند و تنها به سه فایل index.html , script.js , style.css نیاز دارد و از هیچ کتابخانه یا فریم ورک دیگری در آن استفاده نمیکنیم.
- M مخفف Model میباشد و کار مدیریت دادهها را بر عهده دارد.
- V مخفف View میباشد و وظیفهی نمایش دادهها به کاربر را بر عهده دارد.
- C مخفف Controller میباشد و پل ارتباطی بین Model و View میباشد و مدیریت درخواستها را بر عهده دارد.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>الگوی MVC در جاوااسکریپت</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="root"></div> <script src="script.js"></script> </body> </html>
*, *::before, *::after { box-sizing: border-box } html { color: #444; } #root { max-width: 450px; margin: 2rem auto; padding: 0 1rem; } form { display: flex; margin-bottom: 2rem; } [type="text"], button { display: inline-block; -webkit-appearance: none; padding: .5rem 1rem; border: 2px solid #ccc; border-radius: 4px; } button { cursor: pointer; background: #007bff; color: white; border: 2px solid #007bff; margin: 0 .5rem; } [type="text"] { width: 100%; } [type="text"]:active, [type="text"]:focus { outline: 0; border: 2px solid #007bff; } [type="checkbox"] { margin-right: 1rem; } h1 { color: #222; } ul { padding: 0; } li { display: flex; align-items: center; padding: 1rem; margin-bottom: 1rem; background: #f4f4f4; border-radius: 4px; } li span { display: inline-block; padding: .5rem; width: 250px; border-radius: 4px; border: 2px solid transparent; } li span:hover { background: rgba(179, 215, 255, 0.52); } li span:focus { outline: 0; border: 2px solid #007bff; background: rgba(179, 207, 255, 0.52) }
class Model { constructor() {} } class View { constructor() {} } class Controller { constructor(model, view) { this.model = model this.view = view } } const app = new Controller(new Model(), new View())
class Model { constructor() { // یک آرایه از اطلاعات پیش فرض this.todos = [{ id: 1, text: 'Run a marathon', complete: false }, { id: 2, text: 'Plant a garden', complete: false }, ] } // متدی برای افزودن آیتم جدید به آرایه addTodo(todoText) { const todo = { id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1, text: todoText, complete: false, } this.todos.push(todo) } // متدی برای بروزسانی آیتم مورد نظر editTodo(id, updatedText) { this.todos = this.todos.map(todo => todo.id === id ? { id: todo.id, text: updatedText, complete: todo.complete } : todo ) } // انجام میدهد filter با استفاده از متد id تابعی که عملیات حذف را بوسیله فیلد deleteTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id) } // متدی که در آن مشخص میکنیم کار مد نظرانجام شده یا خیر toggleTodo(id) { this.todos = this.todos.map(todo => todo.id === id ? { id: todo.id, text: todo.text, complete: !todo.complete } : todo ) } }
app.model.addTodo('Take a nap') console.log(app.model.todos)
در حال حاضر با هر بار reload شدن صفحه، فقط اطلاعات پیش فرض، درون آرایه todos قرار میگیرد؛ ولی در ادامه آن را در local storage ذخیره میکنیم.
برای ساختن قسمت View، از جاوااسکریپت استفاده میکنیم و DOM را تغییر میدهیم. البته اینکار را بدون استفاده از JSX و یا یک templating language انجام خواهیم داد. قسمتهای دیگر برنامه مانند Controller و Model نباید درگیر تغییرات DOM یا CSS یا عناصر HTML باشند و تمام این موارد توسط View هندل میشود. کد View به نحو زیر خواهد بود:
class View { constructor() {} // ایجاد یک المنت با کلاسهای استایل دلخواه createElement(tag, className) { const element = document.createElement(tag) if (className) element.classList.add(className) return element } // DOM انتخاب و گرفتن آیتمی خاص از getElement(selector) { const element = document.querySelector(selector) return element } }
سپس قسمت سازنده کلاس View را تغییر میدهیم و تمام المنتهای مورد نیاز را در آن ایجاد میکنیم:
- ارجاعی به المنتی با آیدی root
- تگ h1 برای عنوان
- یک form، input و دکمهای برای افزودن آیتمی جدید به آرایهی todos
- یک المنت ul برای نمایش آیتمهای todos
constructor() { // root ارجاعی به المنتی با آیدی this.app = this.getElement('#root') // عنوان برنامه this.title = this.createElement('h1') this.title.textContent = 'Todos' // فرم ، اینپوت ورودی و دکمه this.form = this.createElement('form') this.input = this.createElement('input') this.input.type = 'text' this.input.placeholder = 'Add todo' this.input.name = 'todo' this.submitButton = this.createElement('button') this.submitButton.textContent = 'Submit' // برای نمایش عناط آرایه یا همان لیست کارها this.todoList = this.createElement('ul', 'todo-list') // افزودن اینپوت ورودی و دکمه به فرم this.form.append(this.input, this.submitButton) // ایجاد شده است app که اینجا ارجاعی به آن بنام root اضافه کردن تمام آیتمهای بالا در المنتی با آیدی this.app.append(this.title, this.form, this.todoList) }
get _todoText() { return this.input.value } _resetInput() { this.input.value = '' }
displayTodos(todos){ //... }
متد displayTodos یک المنت ul و liهایی را به تعداد عناصر todos ایجاد میکند و آنها را نمایش میدهد. هر زمانکه تغییراتی مانند اضافه شدن، حذف و ویرایش در todos صورت گیرد، این متد دوباره فراخوانی میشود و لیست جدید را نمایش میدهد. محتوای متد dispayTodos به شکل زیر خواهد بود:
displayTodos(todos) { // حذف تمام نودها while (this.todoList.firstChild) { this.todoList.removeChild(this.todoList.firstChild) } // اگر هیچ آیتمی در آرایه نبود این پاراگراف با متن پیش فرض نمایش داده میشود if (todos.length === 0) { const p = this.createElement('p') p.textContent = 'Nothing to do! Add a task?' this.todoList.append(p) } else { // وعناصرمربوطه را ایجاد میکند liاگه درون آرایه آیتمی قرار دارد پس به ازای آن یک عنصر todos.forEach(todo => { const li = this.createElement('li') li.id = todo.id const checkbox = this.createElement('input') checkbox.type = 'checkbox' checkbox.checked = todo.complete const span = this.createElement('span') span.contentEditable = true span.classList.add('editable') if (todo.complete) { const strike = this.createElement('s') strike.textContent = todo.text span.append(strike) } else { span.textContent = todo.text } const deleteButton = this.createElement('button', 'delete') deleteButton.textContent = 'Delete' li.append(checkbox, span, deleteButton) // نود ایجاد شده به لیست اضافه میکند this.todoList.append(li) }) } // برای خطایابی و نمایش در کنسول console.log(todos) }
در نهایت قسمت Controller را که پل ارتباطی بین View و Model میباشد، کامل میکنیم. اولین تغییراتی که در کلاس Controller ایجاد میکنیم، استفاده از متد displayTodos در سازندهی این کلاس میباشد و با هر بار تغییر این متد، دوباره فراخوانی میشود:
class Controller { constructor(model, view) { this.model = model this.view = view // نمایش اطلاعات پیش فرض this.onTodoListChanged(this.model.todos) } onTodoListChanged = todos => { this.view.displayTodos(todos) } }
چهار تابعی را که در قسمت Model ایجاد نمودیم و کار ویرایش، حذف، افزودن و اتمام کار را انجام میدادند، در کلاس کنترلر آنها را هندل میکنیم و زمانیکه کاربر دکمهای را برای افزودن یا تیک حذف آیتمی، زد، تابع مربوطه توسط کنترلر در Model فراخوانی شود:
handleAddTodo = todoText => { this.model.addTodo(todoText) } handleEditTodo = (id, todoText) => { this.model.editTodo(id, todoText) } handleDeleteTodo = id => { this.model.deleteTodo(id) } handleToggleTodo = id => { this.model.toggleTodo(id) }
چون کنترلر نمیتواند بصورت مستقیم فراخوانی شود و این توابع باید درون DOM تنظیم شوند تا به ازای رخدادهایی همچون click و change، فراخوانی شوند. پس از این توابع در قسمت View استفاده میکنیم و به کلاس View، موارد زیر را اضافه میکنیم:
bindAddTodo(handler) { this.form.addEventListener('submit', event => { event.preventDefault() if (this._todoText) { handler(this._todoText) this._resetInput() } }) } bindDeleteTodo(handler) { this.todoList.addEventListener('click', event => { if (event.target.className === 'delete') { const id = parseInt(event.target.parentElement.id) handler(id) } }) } bindToggleTodo(handler) { this.todoList.addEventListener('change', event => { if (event.target.type === 'checkbox') { const id = parseInt(event.target.parentElement.id) handler(id) } }) }
برای bind کردن این متدها در کلاس Controller، کدهای زیر را اضافه میکنیم:
this.view.bindAddTodo(this.handleAddTodo) this.view.bindDeleteTodo(this.handleDeleteTodo) this.view.bindToggleTodo(this.handleToggleTodo)
برای ذخیره اطلاعات در local storage، در سازنده کلاس Model، کد زیر را اضافه میکنیم:
this.todos = JSON.parse(localStorage.getItem('todos')) || []
متد دیگری هم در کلاس Model برای بهروز رسانی مقادیر local storage قرار میدهیم:
_commit(todos) { this.onTodoListChanged(todos) localStorage.setItem('todos', JSON.stringify(todos)) }
متدی هم برای تغییراتی که هر زمان بر روی todos اتفاق میافتد، فراخوانی شود:
deleteTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id) this._commit(this.todos) }
این مقاله صرفا جهت آشنایی و نمونه کدی از پیاده سازی الگوی MVC در جاوااسکریپت میباشد.
ارتباطات بلادرنگ و SignalR
اطلاعات بیشتر: «عیب یابی و دیباگ برنامههای SignalR »
همچنین این مثالها را از اینجا نیز میتوانید دریافت کنید: SignalRSamples.zip
روش تعیین props پیشفرض توسط TypeScript
اگر بخواهیم توسط روشهای خود React، مقادیر پیشفرض props را تعیین کنیم، میتوان از defaultProps به صورت زیر با تعریف یک شیء جاوا اسکریپتی از پیش مقدار دهی شده، استفاده کرد:
Head.defaultProps = { title: "Hello", isActive: true };
type Props = { title: string; isActive: boolean; }; export const Head = ({ title = "Hello", isActive = true }: Props) => {
در این حالت انتظار داریم که در حین استفاده و تعریف المان کامپوننت Head، اگر برای مثال ویژگی isActive را ذکر نکردیم، کامپایلر TypeScript خطایی را گزارش نکند؛ که اینطور نیست:
هنوز هم در اینجا میتوان خطای عدم تعریف خاصیت isActive را مشاهده کرد. برای رفع این مشکل، به صورت زیر عمل میکنیم:
type Props = { title: string; isActive?: boolean; }; export const Head = ({ title, isActive = true }: Props) => {
اکنون با تعریف isActive?: boolean، دیگر شاهد نمایش خطایی در حین تعریف المان Head، بدون ذکر خاصیت isActive، نخواهیم بود.
تعریف انواع و اقسام نوعهای props
تا اینجا نوعهای سادهای مانند string و boolean و همچنین نحوهی تعریف اجباری و اختیاری آنها را بررسی کردیم. در ادامه یک نمونهی کاملتر را مشاهده میکنید:
type User = { name: string; }; type Props = { title: string; isActive?: boolean; count: number; options: string[]; status: "loading" | "loaded"; thing: {}; thing2: { name: string; }; user: User; func: () => void; };
- سپس نحوهی تعریف آرایهای از رشتهها را مشاهده میکنید.
- یا میتوان مقدار یک خاصیت را تنها به مقادیری خاص محدود کرد؛ مانند خاصیت status در اینجا و اگر در حین مقدار دهی این خاصیت، از مقدار دیگری استفاده شود، تایپاسکریپت، خطایی را صادر میکند.
- در ادامه سه روش تعریف اشیاء تو در تو را مشاهده میکنید؛ خاصیت thing از نوع یک شیء خالی تعریف شدهاست (بجای آن میتوان از نوع object هم استفاده کرد). خاصیت thing2 از نوع یک شیء که دارای خاصیت رشتهای name است، تعریف شده و یا بهتر است این نوع تعاریف را به یک type مستقل دیگر مانند User منتقل کرد و سپس از آن جهت تعیین نوع خاصیتی مانند user استفاده نمود.
- در اینجا حتی میتوان یک خاصیت را که از نوع یک تابع است، تعریف کرد. در این تعریف، void نوع خروجی آن است.
روش تعریف props تابعی در TypeScript
برای بررسی روش تعریف نوع توابع ارسالی از طریق props، ابتدا کامپوننت جدید src\components\Button.tsx را ایجاد میکنیم. سپس آنرا به صورت زیر تکمیل خواهیم کرد:
import React from "react"; type Props = { // onClick(): string; method returns string // onClick(): void method returns nothing; // onClick(text: string): void; method with params // onClick: () => void; function returns nothing onClick: (text: string) => void; // function with params }; export const Button = ({ onClick }: Props) => { return <button onClick={() => onClick("hi")}>Click Me</button>; };
با توجه به تعریف { onClick }، در همان لحظه، خطای any بودن نوع آن از طرف TypeScript گزارش داده میشود. بنابراین نوع جدید Props را ایجاد کرده و برای onClick، نوع متناسبی را تعریف میکنیم. در اینجا 4 روش مختلف تعریف نوع function را در TypeScript مشاهده میکنید؛ دو حالت آن با ذکر پرانتزها و درج امضای متد انجام شده و دو حالت دیگر به کمک arrow functions پیاده سازی شدهاند.
برای نمونه آخرین حالت تعریف شده از روش arrow functions استفاده میکند که متداولترین روش تعریف نوع توابع است (چون عنوان میکند که نوع onClick، یک تابع است و آنرا شبیه به یک متد معمولی نمایش نمیدهد) که در آن در ابتدا امضای پارامترهای این تابع مشخص شدهاند و در ادامه پس از <=، نوع خروجی این تابع تعریف شدهاست که void میباشد (این تابع چیزی را بر نمیگرداند).
در آخر، تعریف المان آنرا به صورت زیر به فایل src\App.tsx اضافه میکنیم که onClick آن یک مقدار را دریافت کرده و سپس آنرا در کنسول نمایش میدهد.
البته خروجی از نوع void، در اینجا بسیار معمول است؛ چون هدف از این نوع توابع بیشتر ارسال مقادیری به کامپوننت در برگیرندهی آنها است (مانند value در اینجا) و اگر برای مثال خروجی رشتهای را داشته باشند، باید در حین درج و تعریف المان آنها، برای نمونه یک "return "value1 را هم در انتهای کار قرار داد که عملا استفادهای ندارد و بیمعنا است:
import { Button } from "./components/Button"; import { Head } from "./components/Head"; // ... function App() { return ( <div className="App"> <Head title="Hello" /> <Button onClick={(value) => { console.log(value); }} /> // ...