در مطلب «امکان ساخت قالب برای پروژههای NET Core.» با مقدمات تبدیل یک پروژهی سفارشی سازی شده، به یک قالب ایجاد پروژههای جدید NET Core. آشنا شدیم. اگر علاقمند باشید میتوانید قالبهای خود را به صورت بستههای نیوگت نیز با دیگران به اشتراک بگذارید. برای نمونه تمام قالبهایی را که توسط دستور dotnet new قابل نصب هستند، میتوانید در مسیر ذیل، در سیستم خود پیدا کنید:
و یا قالبی را که در قسمت قبل، به سیستم dotnet new اضافه کردیم، مدخل تعریف آن، در فایل templatecache.json ذیل، ثبت شدهاست (short name آنرا در این فایل جستجو کنید):
برای حذف قالب تعریف شده از سیستم dotnet new، تنها دستور ذیل وجود دارد که سبب حذف تعریف تمام قالبهای سفارشی جدید میشود:
ساخت بستهی نیوگت از قالب سفارشی
- برای ساخت بستهی نیوگت، ابتدا یک پوشهی مجزا را خارج از پروژهی خود ایجاد کنید (تصویر فوق).
- سپس آخرین نگارش فایل nuget.exe را از آدرس https://dist.nuget.org/index.html دریافت کنید و به داخل این پوشه کپی نمائید.
- فایل pack.bat دارای این یک سطر است:
کار آن پردازش فایل Templates.nuspec و تولید بستهی نیوگت متناظر با آن است.
- در ادامه داخل پوشهی nuget، مطابق تصویر فوق، پوشهای به نام Content و فایل خالی Templates.nuspec را ایجاد کنید.
پوشهی Content در برگیرندهی قسمتی از پروژهاست که قرار است درون بستهی نیوگت قرارگیرد (یعنی تمام فایلهای پروژه به همراه پوشهی مخصوص template.config. باید به اینجا کپی شوند). برای مثال پوشههای Bin و Obj و یا اسکریپتهای جانبی را میشود در اینجا لحاظ نکرد.
- محتوای فایل Templates.nuspec یک چنین ساختاری را دارد:
که در آن، نام، شماره و توضیحاتی در مورد پروژه ذکر میشوند. همچنین نوع این بسته به Template تنظیم خواهد شد.
- اکنون فایل pack.bat را اجرا کنید. پس از آن فایلی مانند DNT.Identity.1.0.0.nupkg تولید خواهد شد که آنرا میتوان در سایت nuget.org مانند سایر بستههای نیوگت آپلود کرد و به اشتراک گذاشت.
یک نکته: میشد فایل nuget.exe و pack.bat را در کنار پوشهی Content و فایل Templates.nuspec هم قرار داد. در این حالت پس از اجرای دستور nuget pack، فایلهای exe و bat نیز داخل فایل نهایی تولیدی قرار میگرفتند. بنابراین بهتر است اینها را درون یک پوشه قرار نداد.
نحوهی نصب یک قالب جدید پروژههای NET Core. از طریق نیوگت
پس از آپلود فایل nupkg حاصل در سایت nuget.org، اکنون نحوهی نصب آن در سیستم به صورت ذیل است:
در حالت عمومی:
و یا در اینجا:
*:: به معنای نصب آخرین نگارش موجود است.
همانطور که در تصویر فوق نیز ملاحظه میکنید، این قالب جدید در کنار سایر قالبهای پیشفرض SDK مربوط به NET Core. قرار گرفتهاست.
اکنون کار با این قالب نصب شده، همانند قسمت «نحوهی ایجاد یک پروژهی جدید بر اساس قالب نصب شده» مطلب پیشین است:
%userprofile%\.templateengine\dotnetcli
و یا قالبی را که در قسمت قبل، به سیستم dotnet new اضافه کردیم، مدخل تعریف آن، در فایل templatecache.json ذیل، ثبت شدهاست (short name آنرا در این فایل جستجو کنید):
%userprofile%\.templateengine\dotnetcli\v2.0.0-preview2-006497\templatecache.json
برای حذف قالب تعریف شده از سیستم dotnet new، تنها دستور ذیل وجود دارد که سبب حذف تعریف تمام قالبهای سفارشی جدید میشود:
dotnet new --debug:reinit
ساخت بستهی نیوگت از قالب سفارشی
- برای ساخت بستهی نیوگت، ابتدا یک پوشهی مجزا را خارج از پروژهی خود ایجاد کنید (تصویر فوق).
- سپس آخرین نگارش فایل nuget.exe را از آدرس https://dist.nuget.org/index.html دریافت کنید و به داخل این پوشه کپی نمائید.
- فایل pack.bat دارای این یک سطر است:
nuget pack .\nuget\Templates.nuspec
- در ادامه داخل پوشهی nuget، مطابق تصویر فوق، پوشهای به نام Content و فایل خالی Templates.nuspec را ایجاد کنید.
پوشهی Content در برگیرندهی قسمتی از پروژهاست که قرار است درون بستهی نیوگت قرارگیرد (یعنی تمام فایلهای پروژه به همراه پوشهی مخصوص template.config. باید به اینجا کپی شوند). برای مثال پوشههای Bin و Obj و یا اسکریپتهای جانبی را میشود در اینجا لحاظ نکرد.
- محتوای فایل Templates.nuspec یک چنین ساختاری را دارد:
<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>DNT.Identity</id> <version>1.0.0</version> <description>Empty DNT.Identity project</description> <authors>VahidN (https://www.dntips.ir/)</authors> <language>en-US</language> <projectUrl>https://github.com/VahidN/DNTIdentity</projectUrl> <licenseUrl>https://github.com/VahidN/DNTIdentity/blob/master/LICENSE.md</licenseUrl> <packageTypes> <packageType name="Template" /> </packageTypes> </metadata> </package>
- اکنون فایل pack.bat را اجرا کنید. پس از آن فایلی مانند DNT.Identity.1.0.0.nupkg تولید خواهد شد که آنرا میتوان در سایت nuget.org مانند سایر بستههای نیوگت آپلود کرد و به اشتراک گذاشت.
یک نکته: میشد فایل nuget.exe و pack.bat را در کنار پوشهی Content و فایل Templates.nuspec هم قرار داد. در این حالت پس از اجرای دستور nuget pack، فایلهای exe و bat نیز داخل فایل نهایی تولیدی قرار میگرفتند. بنابراین بهتر است اینها را درون یک پوشه قرار نداد.
نحوهی نصب یک قالب جدید پروژههای NET Core. از طریق نیوگت
پس از آپلود فایل nupkg حاصل در سایت nuget.org، اکنون نحوهی نصب آن در سیستم به صورت ذیل است:
در حالت عمومی:
dotnet new --install [name]::[version]
dotnet new --install DNT.Identity::*
همانطور که در تصویر فوق نیز ملاحظه میکنید، این قالب جدید در کنار سایر قالبهای پیشفرض SDK مربوط به NET Core. قرار گرفتهاست.
اکنون کار با این قالب نصب شده، همانند قسمت «نحوهی ایجاد یک پروژهی جدید بر اساس قالب نصب شده» مطلب پیشین است:
dotnet new dntidentity -n MyNewProj
Angular Material به همراه یک کامپوننت Date-Picker بسیار شکیل و حرفهای است اما ... از تقویم شمسی پشتیبانی نمیکند. در این مطلب میخواهیم با تدارک یک DateAdapter سفارشی، این مشکل را برطرف کنیم تا در نهایت به یک چنین Date-Picker شمسی برسیم:
تاریخچهی تغییرات کامپوننت Date-Picker
نصب پیشنیاز تبدیل تاریخ میلادی به شمسی و بر عکس
DateAdapter شمسی تهیه شده از کتابخانهی jalali-moment برای تبدیل تاریخها استفاده میکند. بنابراین ابتدا نیاز است این وابستگی را نصب کرد:
افزودن DateAdapter شمسی به پروژه
برای افزودن DateAdapter شمسی تهیه شده، فایل جدید app\shared\material.persian-date.adapter.ts را به برنامه اضافه کرده و به صورت زیر تکمیل کنید:
معرفی وفق دهندهی شمسی به پروژه
پس از تعریف MaterialPersianDateAdapter و همچنین PERSIAN_DATE_FORMATS، برای معرفی آنها به برنامه، فایل app\shared\material.module.ts را گشوده و به صورت زیر تغییر دهید:
کار این تعاریف، تعویض DateAdapter اصلی میلادی، با نمونهی شمسی است. همچنین فرمت نمایشی برچسبها را نیز جایگزین میکند.
پس از آن اگر mat-datepicker را به نحو متداولی به صفحه اضافه کنیم:
چند مثال تکمیلی از کاربردهای کامپوننت mat-datepicker
1) استفاده از تاریخ میلادی رسیدهی از سمت سرور و نمایش آن
با این کدها:
در اینجا jsonDate همان رشتهی تاریخی است که از سمت سرور دریافت شده و میلادی است. با انتساب آن به ngModel، به صورت خودکار شمسی نمایش داده خواهد شد:
2) تعیین تاریخ آغاز تقویم و نمایش آن در حالت انتخاب سال
با این کدها:
در این مثال خاصیت startAt را به یک تاریخ میلادی متصل کردهایم و همچنین خاصیت startView به year تنظیم شدهاست که یک چنین خروجی را در بار اول نمایش تقویم ایجاد میکند:
3) تعیین بازهی تاریخی قابل انتخاب توسط کاربر
با این کدها:
همانطور که ملاحظه میکنید کتابخانهی jalali-moment میتواند تاریخ شمسی و یا میلادی را توسط متد from آن دریافت کند و هر دو حالت در اینجا پس از انتساب به خواص min و max تقویم، به خوبی کار کرده و سبب محدود ساختن بازهی قابل انتخاب توسط کاربر میشوند.
در این تصویر روزهای خاکستری، قابل انتخاب نیستند و غیرفعال شدهاند (چون min به 10 مهر و max به 29 مهر تنظیم شدهاست).
4) غیرفعال کردن روزهای قابل انتخاب بر اساس یک منطق سفارشی
با این کدها:
در اینجا روزهای پنجشنبه و جمعه در تقویم نمایش داده شده، بر اساس تعریف matDatepickerFilter سفارشی، دیگر قابل انتخاب نیستند:
5) کار با رخدادهای تقویم
با این کدها:
در اینجا نحوهی واکنش نشان دادن به رخدادهای dateInput و dateChange کامپوننت mat-datepicker را ملاحظه میکنید:
در اینجا، onInput، با ورود دستی اطلاعات به textbox کامپوننت، فعال میشود و onChange، در صورت انتخاب یک تاریخ از تقویم.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
تاریخچهی تغییرات کامپوننت Date-Picker
اخیرا تیم Angular Material، امکان تعریف تقویمهای دیگری را بجز تقویم میلادی، با تدارک کلاس پایه DateAdapter فراهم کردهاست. در این بین توسعه دهندگان ایرانی پیگیر نیز یک DateAdapter شمسی را بر این مبنا تهیه کردهاند که در ادامه نحوهی افزودن و استفادهی از آنرا بررسی خواهیم کرد.
DateAdapter شمسی تهیه شده از کتابخانهی jalali-moment برای تبدیل تاریخها استفاده میکند. بنابراین ابتدا نیاز است این وابستگی را نصب کرد:
npm install jalali-moment --save
افزودن DateAdapter شمسی به پروژه
برای افزودن DateAdapter شمسی تهیه شده، فایل جدید app\shared\material.persian-date.adapter.ts را به برنامه اضافه کرده و به صورت زیر تکمیل کنید:
import { DateAdapter } from "@angular/material"; import * as jalaliMoment from "jalali-moment"; export const PERSIAN_DATE_FORMATS = { parse: { dateInput: "jYYYY/jMM/jDD" }, display: { dateInput: "jYYYY/jMM/jDD", monthYearLabel: "jYYYY jMMMM", dateA11yLabel: "jYYYY/jMM/jDD", monthYearA11yLabel: "jYYYY jMMMM" } }; export class MaterialPersianDateAdapter extends DateAdapter<jalaliMoment.Moment> { constructor() { super(); super.setLocale("fa"); } getYear(date: jalaliMoment.Moment): number { return this.clone(date).jYear(); } getMonth(date: jalaliMoment.Moment): number { return this.clone(date).jMonth(); } getDate(date: jalaliMoment.Moment): number { return this.clone(date).jDate(); } getDayOfWeek(date: jalaliMoment.Moment): number { return this.clone(date).day(); } getMonthNames(style: "long" | "short" | "narrow"): string[] { switch (style) { case "long": case "short": return jalaliMoment.localeData("fa").jMonths().slice(0); case "narrow": return jalaliMoment.localeData("fa").jMonthsShort().slice(0); } } getDateNames(): string[] { const valuesArray = Array(31); for (let i = 0; i < 31; i++) { valuesArray[i] = String(i + 1); } return valuesArray; } getDayOfWeekNames(style: "long" | "short" | "narrow"): string[] { switch (style) { case "long": return jalaliMoment.localeData("fa").weekdays().slice(0); case "short": return jalaliMoment.localeData("fa").weekdaysShort().slice(0); case "narrow": return ["ی", "د", "س", "چ", "پ", "ج", "ش"]; } } getYearName(date: jalaliMoment.Moment): string { return this.clone(date).jYear().toString(); } getFirstDayOfWeek(): number { return jalaliMoment.localeData("fa").firstDayOfWeek(); } getNumDaysInMonth(date: jalaliMoment.Moment): number { return this.clone(date).jDaysInMonth(); } clone(date: jalaliMoment.Moment): jalaliMoment.Moment { return date.clone().locale("fa"); } createDate(year: number, month: number, date: number): jalaliMoment.Moment { if (month < 0 || month > 11) { throw Error( `Invalid month index "${month}". Month index has to be between 0 and 11.` ); } if (date < 1) { throw Error(`Invalid date "${date}". Date has to be greater than 0.`); } const result = jalaliMoment() .jYear(year).jMonth(month).jDate(date) .hours(0).minutes(0).seconds(0).milliseconds(0) .locale("fa"); if (this.getMonth(result) !== month) { throw Error(`Invalid date ${date} for month with index ${month}.`); } if (!result.isValid()) { throw Error(`Invalid date "${date}" for month with index "${month}".`); } return result; } today(): jalaliMoment.Moment { return jalaliMoment().locale("fa"); } parse(value: any, parseFormat: string | string[]): jalaliMoment.Moment | null { if (value && typeof value === "string") { return jalaliMoment(value, parseFormat, "fa"); } return value ? jalaliMoment(value).locale("fa") : null; } format(date: jalaliMoment.Moment, displayFormat: string): string { date = this.clone(date); if (!this.isValid(date)) { throw Error("JalaliMomentDateAdapter: Cannot format invalid date."); } return date.format(displayFormat); } addCalendarYears(date: jalaliMoment.Moment, years: number): jalaliMoment.Moment { return this.clone(date).add(years, "jYear"); } addCalendarMonths(date: jalaliMoment.Moment, months: number): jalaliMoment.Moment { return this.clone(date).add(months, "jmonth"); } addCalendarDays(date: jalaliMoment.Moment, days: number): jalaliMoment.Moment { return this.clone(date).add(days, "jDay"); } toIso8601(date: jalaliMoment.Moment): string { return this.clone(date).format(); } isDateInstance(obj: any): boolean { return jalaliMoment.isMoment(obj); } isValid(date: jalaliMoment.Moment): boolean { return this.clone(date).isValid(); } invalid(): jalaliMoment.Moment { return jalaliMoment.invalid(); } deserialize(value: any): jalaliMoment.Moment | null { let date; if (value instanceof Date) { date = jalaliMoment(value); } if (typeof value === "string") { if (!value) { return null; } date = jalaliMoment(value).locale("fa"); } if (date && this.isValid(date)) { return date; } return super.deserialize(value); } }
کار این Adapter و یا «وفق دهنده» این است که مشخص میکند، هفتهی ایرانی از چه روزی شروع میشود. نام روزهای هفتهی ایرانی چیست؟ برچسبهای نام ماههای ایرانی چگونه باید تامین شوند و در کل جهت وفق دادن تقویم میلادی اصلی با تقویم شمسی، چه اجزایی باید به سیستم معرفی شوند تا این تقویم توکار بدون مشکل مانند قبل کار کند.
پس از تعریف MaterialPersianDateAdapter و همچنین PERSIAN_DATE_FORMATS، برای معرفی آنها به برنامه، فایل app\shared\material.module.ts را گشوده و به صورت زیر تغییر دهید:
import { NgModule } from "@angular/core"; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from "@angular/material"; import { MaterialPersianDateAdapter, PERSIAN_DATE_FORMATS } from "./material.persian-date.adapter"; @NgModule({ providers: [ { provide: DateAdapter, useClass: MaterialPersianDateAdapter, deps: [MAT_DATE_LOCALE] }, { provide: MAT_DATE_FORMATS, useValue: PERSIAN_DATE_FORMATS } ] }) export class MaterialModule { }
پس از آن اگر mat-datepicker را به نحو متداولی به صفحه اضافه کنیم:
<mat-form-field> <input matInput [matDatepicker]="picker6" placeholder="json gregorian input" [(ngModel)]="dateControl"> <mat-datepicker-toggle matSuffix [for]="picker6"></mat-datepicker-toggle> <mat-datepicker #picker6></mat-datepicker> </mat-form-field>
یک چنین خروجی حاصل خواهد شد:
چند مثال تکمیلی از کاربردهای کامپوننت mat-datepicker
1) استفاده از تاریخ میلادی رسیدهی از سمت سرور و نمایش آن
<mat-form-field> <input matInput [matDatepicker]="picker6" placeholder="json gregorian input" [(ngModel)]="dateControl"> <mat-datepicker-toggle matSuffix [for]="picker6"></mat-datepicker-toggle> <mat-datepicker #picker6></mat-datepicker> </mat-form-field>
@Component() export class PersianDatepickerComponent { jsonDate = "2018-01-08T20:21:29.4674496"; dateControl = this.jsonDate; }
2) تعیین تاریخ آغاز تقویم و نمایش آن در حالت انتخاب سال
<mat-form-field> <input matInput [matDatepicker]="picker2" placeholder="startAt 2017-01-01 and startView=year"> <mat-datepicker-toggle matSuffix [for]="picker2"></mat-datepicker-toggle> <mat-datepicker #picker2 startView="year" [startAt]="startDate"></mat-datepicker> </mat-form-field>
import * as moment from "jalali-moment"; @Component() export class PersianDatepickerComponent { startDate = moment("2017-01-01", "YYYY-MM-DD"); // = moment.from("2017-01-01", "en"); }
3) تعیین بازهی تاریخی قابل انتخاب توسط کاربر
<mat-form-field> <input matInput [matDatepicker]="picker3" [min]="minDate" [max]="maxDate" placeholder="min: 2017-10-02 and max: 1396-07-29"> <mat-datepicker-toggle matSuffix [for]="picker3"></mat-datepicker-toggle> <mat-datepicker #picker3></mat-datepicker> </mat-form-field>
import * as moment from "jalali-moment"; @Component() export class PersianDatepickerComponent { minDate = moment.from("2017-10-02", "en"); // = moment('2017-10-02', 'YYYY-MM-DD'); maxDate = moment.from("1396-07-29", "fa"); // = moment('1396-07-29', 'jYYYY-jMM-jDD'); }
در این تصویر روزهای خاکستری، قابل انتخاب نیستند و غیرفعال شدهاند (چون min به 10 مهر و max به 29 مهر تنظیم شدهاست).
4) غیرفعال کردن روزهای قابل انتخاب بر اساس یک منطق سفارشی
<mat-form-field> <input matInput [matDatepicker]="picker4" [matDatepickerFilter]="myFilter" placeholder="Date validation - Datepicker Filter"> <mat-datepicker-toggle matSuffix [for]="picker4"></mat-datepicker-toggle> <mat-datepicker #picker4></mat-datepicker> </mat-form-field>
import * as moment from "jalali-moment"; @Component() export class PersianDatepickerComponent { myFilter = (d: moment.Moment): boolean => { const day: number = d.day(); // Prevent Thursday and Friday from being selected. return day !== 5 && day !== 4; } }
5) کار با رخدادهای تقویم
<mat-form-field> <input matInput [matDatepicker]="picker5" (dateInput)="onInput($event)" (dateChange)="onChange($event)" placeholder="dateInput and dateChange events"> <mat-datepicker-toggle matSuffix [for]="picker5"></mat-datepicker-toggle> <mat-datepicker #picker5></mat-datepicker> </mat-form-field>
import { MatDatepickerInputEvent } from "@angular/material"; import * as moment from "jalali-moment"; @Component() export class PersianDatepickerComponent { onInput(event: MatDatepickerInputEvent<moment.Moment>) { console.log("OnInput: ", event.value); } onChange(event: MatDatepickerInputEvent<moment.Moment>) { const x = moment(event.value).format("jYYYY/jMM/jDD"); console.log("OnChange: ", x); } }
در اینجا، onInput، با ورود دستی اطلاعات به textbox کامپوننت، فعال میشود و onChange، در صورت انتخاب یک تاریخ از تقویم.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
C# 9.0 به همراه دو بهبود جزئی دربارهی کار با Lambdas است:
- امکان عدم ذکر بعضی از پارامترهای Lambdas
- امکان تعریف متدهای static anonymous
امکان عدم ذکر بعضی از پارامترهای Lambdas در C# 9.0
مثال زیر را در نظر بگیرید:
در عبارت lambda تعریف شده، از پارامترهای obj و args استفاده نشدهاست. به همین جهت برای کاهش اخطارهای نمایش داده شدهی توسط کامپایلر در C# 9.0 میتوان آنها را به صورت discard parameters توسط _ معرفی کرد؛ به یکی از دو روش زیر:
که در یکی، نوعها به همراه discard parameters ذکر شدهاند و در دومی فقط discard parameters را داریم.
نمونهی دیگر آن در حین تعریف رخدادگردانها است:
که اینبار پارامترهای استفاده نشده به صورت زیر قابل بیان هستند:
امکان تعریف Static anonymous functions در C# 9.0
برای کاهش میزان تخصیص حافظهی در حین کار با عبارات lambda ای که از متغیرهای محلی استفاده میکنند، اینبار در C# 9.0 میتوان این عبارات را static تعریف کرد. برای نمونه مثال زیر را درنظر بگیرید:
در اینجا نحوهی فرمت نهایی اطلاعات نمایش داده شده، توسط یک عبارت lambda تامین میشود. اتفاقی که در اینجا رخ میدهد، اصطلاحا capture شدن یک متغیر (text در اینجا) توسط یک anonymous function است (همان عبارت lambda نوشته شده). حاصل این capture شدن، ایجاد یک شیء موقتی برای مدیریت آن است که تخصیص حافظه و همچنین سربار عملیاتی اضافهای را به همراه دارد. برای حذف این سربارها در C# 9.0 میتوان متغیر استفاده شده را const تعریف کرد و سپس عبارت lambda تعریف شده را به صورت static نوشت:
با تعریف شدن یک عبارت lambda و یا یک anonymous method به صورت static که از تخصیص حافظهی اضافی ایجاد شیء موقتی مدیریت کنندهی دسترسی به متغیر text جلوگیری میکند، اتفاقات زیر نیز رخ میدهند:
- این متد به عنوان static anonymous function شناخته میشود.
- دیگر نمیتواند دسترسی به حالت scope جاری را داشته باشد. بنابراین دیگر دسترسی به متغیرها، پارامترها و حتی شیء this را هم نخواهد داشت.
- میتواند با سایر اعضای استاتیک scope جاری کار کند.
- میتواند به تعاریف const مربوط به scope جاری، دسترسی داشته باشد.
- امکان عدم ذکر بعضی از پارامترهای Lambdas
- امکان تعریف متدهای static anonymous
امکان عدم ذکر بعضی از پارامترهای Lambdas در C# 9.0
مثال زیر را در نظر بگیرید:
Action<object, EventArgs> handler1 = (object obj, EventArgs args) => ShowDialog();
Action<object, EventArgs> handler2 = (object _, EventArgs _) => ShowDialog(); // OR Action<object, EventArgs> handler3 = (_, _) => ShowDialog();
نمونهی دیگر آن در حین تعریف رخدادگردانها است:
button1.Click += (s, e) => ShowDialog();
button1.Click += (_, _) => ShowDialog();
امکان تعریف Static anonymous functions در C# 9.0
برای کاهش میزان تخصیص حافظهی در حین کار با عبارات lambda ای که از متغیرهای محلی استفاده میکنند، اینبار در C# 9.0 میتوان این عبارات را static تعریف کرد. برای نمونه مثال زیر را درنظر بگیرید:
namespace CS9Features { public class LambdasTests { public void Test() { string text = "{0} is a good user !"; PrintItems(item => string.Format(text, item)); } private void PrintItems(Func<string, string> formatFunc) { foreach (var item in new[] { "User 1", "User 2" }) { Console.WriteLine(formatFunc(item)); } } } }
const string text = "{0} is a good user !"; PrintItems(static item => string.Format(text, item));
- این متد به عنوان static anonymous function شناخته میشود.
- دیگر نمیتواند دسترسی به حالت scope جاری را داشته باشد. بنابراین دیگر دسترسی به متغیرها، پارامترها و حتی شیء this را هم نخواهد داشت.
- میتواند با سایر اعضای استاتیک scope جاری کار کند.
- میتواند به تعاریف const مربوط به scope جاری، دسترسی داشته باشد.
پیشنیاز:
تعریف نوع جنریک به صورت متغیر
مطلبی را چندی قبل در مورد نحوه خودکار کردن افزودن کلاسهای EntityTypeConfiguration به modelBuilder در این سایت مطالعه کردید. در مطلب جاری به خودکار سازی تعاریف مرتبط با DbSetها خواهیم پرداخت.
ابتدا مثال کامل زیر را درنظر بگیرید:
توضیحات:
همانطور که ملاحظه میکنید در این مثال خبری از تعاریف DbSetها نیست. به کمک Reflection تمام مدلهای برنامه که از نوع کلاس پایه BaseEntity هستند (روشی مرسوم جهت مدیریت خواص تکراری مدلها) یافت شده (در متد loadEntities) و سپس نتیجه حاصل به صورت پویا به متد جنریک Entity ارسال میشود. حاصل، افزوده شدن خودکار کلاسهای مورد نظر به سیستم EF است.
البته در این حالت چون دیگر کلاسهای مدلها در MyContext به صورت صریح تعریف نمیشوند، نحوه استفاده از آنها را توسط متد Set، در متدهای RunTests و یا Seed، ملاحظه میکنید.
تعریف نوع جنریک به صورت متغیر
مطلبی را چندی قبل در مورد نحوه خودکار کردن افزودن کلاسهای EntityTypeConfiguration به modelBuilder در این سایت مطالعه کردید. در مطلب جاری به خودکار سازی تعاریف مرتبط با DbSetها خواهیم پرداخت.
ابتدا مثال کامل زیر را درنظر بگیرید:
using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; using System.Reflection; namespace MyNamespace { public abstract class BaseEntity { public int Id { set; get; } public string CreatedBy { set; get; } } public class User : BaseEntity { public string Name { get; set; } } public class MyContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { var asm = Assembly.GetExecutingAssembly(); loadEntities(asm, modelBuilder, "MyNamespace"); } void loadEntities(Assembly asm, DbModelBuilder modelBuilder, string nameSpace) { var entityTypes = asm.GetTypes() .Where(type => type.BaseType != null && type.Namespace == nameSpace && type.BaseType.IsAbstract && type.BaseType == typeof(BaseEntity)) .ToList(); var entityMethod = typeof(DbModelBuilder).GetMethod("Entity"); entityTypes.ForEach(type => { entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { }); }); } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { context.Set<User>().Add(new User { Name = "name-1" }); context.Set<User>().Add(new User { Name = "name-2" }); context.Set<User>().Add(new User { Name = "name-3" }); base.Seed(context); } } public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var context = new MyContext()) { var user1 = context.Set<User>().Find(1); if (user1 != null) Console.WriteLine(user1.Name); } } } }
همانطور که ملاحظه میکنید در این مثال خبری از تعاریف DbSetها نیست. به کمک Reflection تمام مدلهای برنامه که از نوع کلاس پایه BaseEntity هستند (روشی مرسوم جهت مدیریت خواص تکراری مدلها) یافت شده (در متد loadEntities) و سپس نتیجه حاصل به صورت پویا به متد جنریک Entity ارسال میشود. حاصل، افزوده شدن خودکار کلاسهای مورد نظر به سیستم EF است.
البته در این حالت چون دیگر کلاسهای مدلها در MyContext به صورت صریح تعریف نمیشوند، نحوه استفاده از آنها را توسط متد Set، در متدهای RunTests و یا Seed، ملاحظه میکنید.
توابع محلی، امکان تعریف یک تابع را درون یک متد، فراهم میکنند. هدف آنها تدارک توابعی کمکی است که به سایر قسمتهای کلاس مرتبط نمیشوند. برای مثال اگر متدی نیاز به کار با یک private method دیگر را دارد و این متد خصوصی در جای دیگری استفاده نمیشود، میتوان جهت بالابردن خوانایی برنامه و سهولت یافتن متد مرتبط، این متد خصوصی را تبدیل به یک تابع محلی، درون همان متد کرد.
بازنویسی کدهای C# 6 با توابع محلی C# 7
کلاس زیر را که بر اساس امکانات C# 6 تهیه شدهاست، در نظر بگیرید:
متد خصوصی همین کلاس را توسط Func delegates میتوان به صورت ذیل خلاصه کرد (باز هم بر اساس امکانات C# 6):
به این ترتیب نیاز به تعریف یک متد private دیگر کمتر خواهد شد.
اکنون در C# 7 میتوان این Func delegate را به نحو ذیل تبدیل به یک local function کرد:
مزیت کار با local functions نسبت به Func delegates محلی
در قطعه کد فوق، کار انجام شده صرفا استفادهی از یک Syntax جدید نیست؛ بلکه از لحاظ کارآیی نیز سربار کمتری را به همراه دارد. زمانیکه Func Delegates تعریف میشوند، کار ایجاد یک anonymous type، وهله سازی و فراخوانی آنها توسط کامپایلر صورت میگیرد. اما حین کار با توابع محلی، کامپایلر با یک متد استاندارد سروکار دارد و هیچکدام از مراحل یاد شده و سربارهای آنها رخ نمیدهند (هیچگونه GC allocation ایی نخواهیم داشت). به علاوه اینبار کامپایلر فرصت in-line تعریف کردن متد را به نحو بهتری یافته و به این ترتیب کار سوئیچ بین متدهای مختلف کاهش پیدا میکند که در نهایت سرعت برنامه را افزایش میدهند.
میدان دید توابع محلی
البته با توجه به اینکه متد مثال فوق محلی است، به تمام متغیرها و پارامترهای متد دربرگیرندهی آن نیز دسترسی دارد. بنابراین میتوان پارامتر int age آنرا نیز حذف کرد:
به همین جهت نمیتوانید داخل یک تابع محلی، متغیری را تعریف کنید که همنام یکی از متغیرها یا پارامترهای متد دربرگیرندهی آن باشد.
خلاصه نویسی توابع محلی به کمک expression bodies
میتوان این متد محلی را به صورت یک expression body ارائه شدهی در C# 6 نیز بیان کرد:
روش ارسال یک local function به متدی دیگر
امکان ارسال یک تابع محلی به صورت یک Func delegate به متدی دیگر نیز وجود دارد:
در این مثال GenerateAgeSuffix یک Local function است که به صورت expression body نیز بیان شدهاست. برای ارسال آن به متد OutputSimplePerson، پارامتر دریافتی آن باید به صورت Func تعریف شود.
static void Main(string[] args) { int Add(int a, int b) { return a + b; } Console.WriteLine(Add(3, 4)); }
بازنویسی کدهای C# 6 با توابع محلی C# 7
کلاس زیر را که بر اساس امکانات C# 6 تهیه شدهاست، در نظر بگیرید:
public class PersonWithPrivateMethod { public string Name { get; set; } public int Age { get; set; } public override string ToString() { string ageSuffix = GenerateAgeSuffix(Age); return $"{Name} is {Age} year{ageSuffix} old"; } private string GenerateAgeSuffix(int age) { return age > 1 ? "s" : ""; } }
public class PersonWithLocalFuncDelegate { public string Name { get; set; } public int Age { get; set; } public override string ToString() { Func<int, string> generateAgeSuffix = age => age > 1 ? "s" : ""; return $"{Name} is {Age} year{generateAgeSuffix(Age)} old"; } }
اکنون در C# 7 میتوان این Func delegate را به نحو ذیل تبدیل به یک local function کرد:
public class PersonWithLocalFunction { public string Name { get; set; } public int Age { get; set; } public override string ToString() { return $"{Name} is {Age} year{GenerateAgeSuffix(Age)} old"; // Define a local function: string GenerateAgeSuffix(int age) { return age > 1 ? "s" : ""; } } }
مزیت کار با local functions نسبت به Func delegates محلی
در قطعه کد فوق، کار انجام شده صرفا استفادهی از یک Syntax جدید نیست؛ بلکه از لحاظ کارآیی نیز سربار کمتری را به همراه دارد. زمانیکه Func Delegates تعریف میشوند، کار ایجاد یک anonymous type، وهله سازی و فراخوانی آنها توسط کامپایلر صورت میگیرد. اما حین کار با توابع محلی، کامپایلر با یک متد استاندارد سروکار دارد و هیچکدام از مراحل یاد شده و سربارهای آنها رخ نمیدهند (هیچگونه GC allocation ایی نخواهیم داشت). به علاوه اینبار کامپایلر فرصت in-line تعریف کردن متد را به نحو بهتری یافته و به این ترتیب کار سوئیچ بین متدهای مختلف کاهش پیدا میکند که در نهایت سرعت برنامه را افزایش میدهند.
میدان دید توابع محلی
البته با توجه به اینکه متد مثال فوق محلی است، به تمام متغیرها و پارامترهای متد دربرگیرندهی آن نیز دسترسی دارد. بنابراین میتوان پارامتر int age آنرا نیز حذف کرد:
public class PersonWithLocalFunctionEnclosing { public string Name { get; set; } public int Age { get; set; } public override string ToString() { return $"{Name} is {Age} year{GenerateAgeSuffix()} old"; // Define a local function: string GenerateAgeSuffix() { return Age > 1 ? "s" : ""; } } }
خلاصه نویسی توابع محلی به کمک expression bodies
میتوان این متد محلی را به صورت یک expression body ارائه شدهی در C# 6 نیز بیان کرد:
public class PersonWithLocalFunctionExpressionBodied { public string Name { get; set; } public int Age { get; set; } public override string ToString() { return $"{Name} is {Age} year{GenerateAgeSuffix(Age)} old"; // Define a local function: string GenerateAgeSuffix(int age) => age > 1 ? "s" : ""; } }
روش ارسال یک local function به متدی دیگر
امکان ارسال یک تابع محلی به صورت یک Func delegate به متدی دیگر نیز وجود دارد:
public class LocalFunctionsTest { public void PassAnonFunctionToMethod() { var p = new SimplePerson { Name = "Name1", Age = 42 }; OutputSimplePerson(p, GenerateAgeSuffix); string GenerateAgeSuffix(int age) => age > 1 ? "s" : ""; } private void OutputSimplePerson(SimplePerson person, Func<int, string> suffixFunction) { Output.WriteLine( $"{person.Name} is {person.Age} year{suffixFunction(person.Age)} old"); } }
در این مطلب میخواهم شما را با نحوه بار گزاری ساعت و تاریخ سیستم سرور با استفاده از JQuery Ajax آشنا کنم.
در بعضی از سایتها با استفاده از جاوا اسکریپت تاریخ و ساعت جاری سیستم کلاینت به او نشان داه میشود.
این روش یک مزیت دارد: اول اینکه این کدها سمت کلاینت اجرا میشن و برای سرور بار اضافی ایجاد نمیکنن.
و یک عیب هم دارد: در صورتی که ساعت و تاریخ روی سیستم کلاینت تنظیم نباشد، همین ساعت و تاریخ نادرست برای او نمایش داده میشود. همین عیب میتواند باعث افت کیفیت وب سایت شود.
اما راهی هست که تاریخ و ساعت سیستم سرور برای کاربر نشان داده شود و آن هم استفاده از JQuery Ajax هست. به صورتی که هر ثانیه درخواستی برای یک handler فرستاده میشود و آن handler نیز ساعت و تاریخ روی سرور را باز میگرداند و این مقدار بازگشته شده را میتوان در تگی از صفحه وب نمایش داد.
مثال: ابتدا یک صفحه aspx میسازیم و تگ زیر را در آن قرار میدهیم:
ساعت و تاریخ بار شده از سرور در این تگ باید نشان داده شود.
سپس کدهای اسکریپت زیر را مینویسیم:
با نوشتن این کدها هر ثانیه یک بار، بوسیله Ajax درخواستی برای یک handler به اسم GetDateTime.ashx فرستاده میشود. وظیفه این handler برگرداندن تاریخ و ساعت فعلی سیستم سرور است. بعد از دریافت مقدار این مقدار از این handler، آنرا در تگ با شناسه datetime قرار میدهیم.
کد استفاده شده در handler هم به این صورت است:
در انتها فایل ضمیمه این مثال را از این لینک دریافت کنید:
AjaxDateTime.zip
در بعضی از سایتها با استفاده از جاوا اسکریپت تاریخ و ساعت جاری سیستم کلاینت به او نشان داه میشود.
این روش یک مزیت دارد: اول اینکه این کدها سمت کلاینت اجرا میشن و برای سرور بار اضافی ایجاد نمیکنن.
و یک عیب هم دارد: در صورتی که ساعت و تاریخ روی سیستم کلاینت تنظیم نباشد، همین ساعت و تاریخ نادرست برای او نمایش داده میشود. همین عیب میتواند باعث افت کیفیت وب سایت شود.
اما راهی هست که تاریخ و ساعت سیستم سرور برای کاربر نشان داده شود و آن هم استفاده از JQuery Ajax هست. به صورتی که هر ثانیه درخواستی برای یک handler فرستاده میشود و آن handler نیز ساعت و تاریخ روی سرور را باز میگرداند و این مقدار بازگشته شده را میتوان در تگی از صفحه وب نمایش داد.
مثال: ابتدا یک صفحه aspx میسازیم و تگ زیر را در آن قرار میدهیم:
<p id="datetime"></p>
سپس کدهای اسکریپت زیر را مینویسیم:
var auto_referesh = setInterval ( function() { $.post ( "GetDateTime.ashx", function (result) { $('#datetime').html(result); } ); }, 1000
);
کد استفاده شده در handler هم به این صورت است:
<%@ WebHandler Language="C#" Class="GetDateTime" %> using System; using System.Web; public class GetDateTime : IHttpHandler { public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Write(DateTime.Now.ToString()); } public bool IsReusable { get { return false; } } }
AjaxDateTime.zip
در این قسمت، شیوهنامههایی را بررسی میکنیم که به المانهای پر کاربردی مانند دکمهها، لیستها و نشانها اعمال میشوند.
شیوهنامههای کار با دکمهها در بوت استرپ 4
کلاس پایه ایجاد دکمههای بوت استرپی، کلاس btn است. البته آنرا میتوان با کلاسهای دیگری نیز ترکیب کرد:
- کلاس btn را میتوان بر روی المانهایی مانند anchor، button و input نیز اعمال کرد:
در این حالت تمام این المانها یکسان به نظر میرسند:
- برای تعیین رنگ اجباری دکمههای بوت استرپ، از فرمول زیر استفاده میشود؛ مانند btn-primary:
با این خروجی:
همانطور که ملاحظه میکنید، رنگ این دکمهها نیز نسبت به نگارش سوم آن، به روز رسانی شدهاست.
همچنین نگارش outline آنها نیز قابل تعریف است؛ مانند btn-outline-primary:
با این خروجی:
- کلاس btn-size که در اینجا size میتواند sm یا lg باشد و سبب ایجاد دکمههایی کوچک یا بزرگ میشوند.
- کلاس btn-block سبب میشود تا دکمهای کل عرض container را پر کند.
با این خروجی:
- امکان اعمال کلاسهای active و disabled نیز در اینجا میسر است:
با این خروجی:
تشکیل گروهی از دکمهها در بوت استرپ 4
برای تبدیل تعدادی از دکمهها به یک گروه، از کلاس btn-group استفاده میشود. همچنین امکان تشکیل گزینهی عمودی آن، با کلاس btn-group-vertical نیز وجود دارد. در این حالت دکمهها بجای نمایش افقی، به صورت یک ستون نمایش داده میشوند. کلاس btn-toolbar نیز برای تشکیل نوار ابزاری از دکمهها در نظر گرفته شدهاست. توسط کلاسهای btn-group-sm و یا btn-group-lg میتوان اندازهی این گروهها را تغییر داد.
با این خروجی:
در اینجا دو گروه از دکمهها تشکیل شدهاند که اینها را داخل یک btn-toolbar قرار دادهایم. همچنین تعریف aria-labelها به screen readers و معلولین کمک میکند.
به علاوه با استفاده از کلاسهای mb-2 و mr-2، سبب ایجاد margin بین این نوار ابزار با متن زیر آن و همچنین بین خود آنها شدهایم.
و حالت عمودی آن:
چنین شکلی را پیدا میکند:
کلاسهای جدید تشکیل Badges در بوت استرپ 4
برای تشکیل نشان/آرم از کلاس badge استفاده میشود و برای تغییر شکل آن میتوان از کلاس badge-pill کمک گرفت. برای تغییر رنگ آن نیز فرمول زیر وجود دارد:
یک مثال:
با این خروجی:
همانطور که مشاهده میکنید، یک badge همواره به اندازهی container آن در آمده و نمایش داده میشود.
ایجاد لیستی از آیتمها در بوت استرپ 4
بوت استرپ، کلاسهایی را برای ایجاد لیستهایی با ظاهر لیستهای اندرویدی به همراه دارد که در ادامه با مثالهایی آنها را بررسی خواهیم کرد.
با این خروجی:
این list-group را بر روی لینکها و دکمهها نیز میتوان همانند قبل اعمال کرد:
با این خروجی:
در اینجا از کلاسهایی مانند list-group-item-warning برای تغییر رنگ پس زمینهی هر آیتم میتوان کمک گرفت.
برای تعریف badge برای هر آیتم، میتوان به صورت زیر عمل کرد:
با این خروجی:
با اعمال کلاسهای badge، این نشان نمایش داده میشود؛ اما دقیقا در کنار متن Nutrition در اینجا. برای اینکه آنرا به سمت دیگر این ردیف منتقل و همچنین تراز عمودی آنرا نیز به میانهی صفحه تنظیم کنیم، میتوان از Flexbox کمک گرفت. با اعمال d-flex، این ردیف تبدیل به یک Flexbox container میشود و سپس میتوان کلاسهای مخصوص Flexbox مانند justify-content-between و align-items-center را به این ردیف اعمال کرد تا ترازهای عمودی و افقی آیتمهای قرار گرفتهی درون آن تغییر کنند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_08.zip
شیوهنامههای کار با دکمهها در بوت استرپ 4
کلاس پایه ایجاد دکمههای بوت استرپی، کلاس btn است. البته آنرا میتوان با کلاسهای دیگری نیز ترکیب کرد:
- کلاس btn را میتوان بر روی المانهایی مانند anchor، button و input نیز اعمال کرد:
<a class="btn btn-primary" href="#" role="button">Link</a> <button class="btn btn-primary" type="submit">Button</button> <input class="btn btn-primary" type="submit" value="Input">
- برای تعیین رنگ اجباری دکمههای بوت استرپ، از فرمول زیر استفاده میشود؛ مانند btn-primary:
<button class="btn btn-primary">Primary</button> <button class="btn btn-secondary">Secondary</button> <button class="btn btn-success">Success</button> <button class="btn btn-danger">Danger</button> <button class="btn btn-warning">Warning</button> <button class="btn btn-info">Info</button> <button class="btn btn-light">Light</button> <button class="btn btn-dark">Dark</button>
همانطور که ملاحظه میکنید، رنگ این دکمهها نیز نسبت به نگارش سوم آن، به روز رسانی شدهاست.
همچنین نگارش outline آنها نیز قابل تعریف است؛ مانند btn-outline-primary:
<button class="btn btn-outline-primary">Primary</button> <button class="btn btn-outline-secondary">Secondary</button> <button class="btn btn-outline-success">Success</button> <button class="btn btn-outline-danger">Danger</button> <button class="btn btn-outline-warning">Warning</button> <button class="btn btn-outline-info">Info</button> <button class="btn btn-outline-light">Light</button> <button class="btn btn-outline-dark">Dark</button>
- کلاس btn-size که در اینجا size میتواند sm یا lg باشد و سبب ایجاد دکمههایی کوچک یا بزرگ میشوند.
- کلاس btn-block سبب میشود تا دکمهای کل عرض container را پر کند.
<button class="btn btn-primary">Default</button> <button class="btn btn-primary btn-lg">Large</button> <button class="btn btn-primary btn-sm">Small</button> <button class="btn btn-primary btn-block">Block</button>
- امکان اعمال کلاسهای active و disabled نیز در اینجا میسر است:
<h2>States</h2> <h3>Active</h3> <button class="btn btn-primary active">Active Button</button> <h3>Disabled</h3> <button class="btn btn-primary disabled">Disabled Button</button> <a class="btn btn-primary disabled" href="#">Disabled Link Button</a>
تشکیل گروهی از دکمهها در بوت استرپ 4
برای تبدیل تعدادی از دکمهها به یک گروه، از کلاس btn-group استفاده میشود. همچنین امکان تشکیل گزینهی عمودی آن، با کلاس btn-group-vertical نیز وجود دارد. در این حالت دکمهها بجای نمایش افقی، به صورت یک ستون نمایش داده میشوند. کلاس btn-toolbar نیز برای تشکیل نوار ابزاری از دکمهها در نظر گرفته شدهاست. توسط کلاسهای btn-group-sm و یا btn-group-lg میتوان اندازهی این گروهها را تغییر داد.
<div class="btn-toolbar" aria-label="All pets"> <div class="btn-group mb-2 mr-2" aria-label="Common pets"> <button type="button" class="btn btn-primary active">Cat</button> <button type="button" class="btn btn-primary">Dog</button> <button type="button" class="btn btn-primary">Fish</button> <button type="button" class="btn btn-primary">Bird</button> </div> <div class="btn-group mb-2" aria-label="Exotic pets"> <button type="button" class="btn btn-primary">Amphibian</button> <button type="button" class="btn btn-primary active">Reptile</button> <button type="button" class="btn btn-primary">Other</button> </div> </div>
در اینجا دو گروه از دکمهها تشکیل شدهاند که اینها را داخل یک btn-toolbar قرار دادهایم. همچنین تعریف aria-labelها به screen readers و معلولین کمک میکند.
به علاوه با استفاده از کلاسهای mb-2 و mr-2، سبب ایجاد margin بین این نوار ابزار با متن زیر آن و همچنین بین خود آنها شدهایم.
و حالت عمودی آن:
<div class="btn-group-vertical mb-2" aria-label="Exotic pets"> <button type="button" class="btn btn-primary">Amphibian</button> <button type="button" class="btn btn-primary active">Reptile</button> <button type="button" class="btn btn-primary">Other</button> </div>
کلاسهای جدید تشکیل Badges در بوت استرپ 4
برای تشکیل نشان/آرم از کلاس badge استفاده میشود و برای تغییر شکل آن میتوان از کلاس badge-pill کمک گرفت. برای تغییر رنگ آن نیز فرمول زیر وجود دارد:
یک مثال:
<div class="container"> <div class="row"> <section class="col-12"> <h1>Our Commitment <span class="badge badge-info">to you</span></h1> <h3>Grooming <span class="badge badge-danger badge-pill">new</span></h3> </section> </div><!-- row --> </div><!-- content container -->
همانطور که مشاهده میکنید، یک badge همواره به اندازهی container آن در آمده و نمایش داده میشود.
ایجاد لیستی از آیتمها در بوت استرپ 4
بوت استرپ، کلاسهایی را برای ایجاد لیستهایی با ظاهر لیستهای اندرویدی به همراه دارد که در ادامه با مثالهایی آنها را بررسی خواهیم کرد.
<ul class="list-group mb-3"> <li class="list-group-item active">Grooming</li> <li class="list-group-item list-group-item-action"> General Health </li> <li class="list-group-item list-group-item-action">Nutrition</li> <li class="list-group-item list-group-item-action"> Pest Control </li> <li class="list-group-item list-group-item-action">Vaccinations</li> </ul>
در اینجا برای تشکیل لیستی با ظاهری شکیلتر، میتوان ابتدا کلاس list-group را به ul انتساب داد و سپس به هر کدام از liهای آن کلاس list-group-item انتساب داده میشود. با افزودن کلاس active به اولین مورد، ظاهر آن با رنگی متمایز نمایان میشود. همچنین اگر علاقمند بودیم تا هر کدام از آیتمها با عبور ماوس بر روی آنها، با رنگ ملایمی مشخص شوند، میتوان از کلاس list-group-item-action استفاده کرد.
این list-group را بر روی لینکها و دکمهها نیز میتوان همانند قبل اعمال کرد:
<div class="list-group mb-3"> <a class="list-group-item active" href="#">Grooming</a> <a class="list-group-item list-group-item-action list-group-item-success" href="#">Nutrition</a> <a class="list-group-item list-group-item-action list-group-item-info" href="#"> Pest Control </a> <a class="list-group-item list-group-item-action list-group-item-warning" href="#">Vaccinations</a> </div>
در اینجا از کلاسهایی مانند list-group-item-warning برای تغییر رنگ پس زمینهی هر آیتم میتوان کمک گرفت.
برای تعریف badge برای هر آیتم، میتوان به صورت زیر عمل کرد:
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"> Nutrition <span class="badge badge-info badge-pill">12</span> </li>
با اعمال کلاسهای badge، این نشان نمایش داده میشود؛ اما دقیقا در کنار متن Nutrition در اینجا. برای اینکه آنرا به سمت دیگر این ردیف منتقل و همچنین تراز عمودی آنرا نیز به میانهی صفحه تنظیم کنیم، میتوان از Flexbox کمک گرفت. با اعمال d-flex، این ردیف تبدیل به یک Flexbox container میشود و سپس میتوان کلاسهای مخصوص Flexbox مانند justify-content-between و align-items-center را به این ردیف اعمال کرد تا ترازهای عمودی و افقی آیتمهای قرار گرفتهی درون آن تغییر کنند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_08.zip