کتابخانه Waves
پروژه Materialize
<Style TargetType="Label"> <Setter Property="FontFamily" Value="Some font..." /> </Style>
<Style TargetType="ContentPage" ApplyToDerivedTypes="True" > <Setter Property="BackgroundColor" Value="Blue" /> </Style>
<Style x:Key="DangerButton" TargetType="Button"> <Setter Property="BackgroundColor" Value="Red" /> <Setter Property="FontAttributes" Value="Bold" /> </Style>
<Button Style="{StaticResource DangerButton}" />
<Style TargetType="Entry"> <Style.Triggers> <Trigger TargetType="Entry" Property="IsFocused" Value="True"> <Setter Property="FontAttributes" Value="Bold" /> </Trigger> </Style.Triggers> </Style>
<StyleSheet> <![CDATA[ button { font-style: bold; } ]]> </StyleSheet>
<key>UIAppFonts</key> <array> <string>Fonts/OpenSansItalic.ttf</string> <string>Fonts/OpenSansRegular.ttf</string> <string>Fonts/OpenSansBold.ttf</string> </array>
سپس کدهای زیر را استفاده کنید:
<bitView:OnPlatform x:Key="OpenSansRegular" x:TypeArguments="x:String" Value="{OnPlatform Android='Fonts/OpenSansRegular.ttf#Open Sans', iOS='OpenSans-Regular', UWP='Assets/Fonts/OpenSansRegular.ttf#Open Sans'}" /> <!-- Italic مشابه کد بالا برای --> <!-- Bold مشابه کد بالا برای -->
چون آدرس و نحوه نام دهی FontFamily در سه پلتفرم متفاوت است، با استفاده از OnPlatform، یک String میسازیم با x:Key برابر با OpenSansRegular که در هر پلتفرم مقدار خود را دارد. سپس از این نام برای مقدار دهی FontFamily در کنترلهای Label/Entry/Button و ... در حالتهای None/Italic/Bold استفاده میکنیم. برای مثال:
<Style TargetType="Label"> <Style.Triggers> <Trigger TargetType="Label" Property="FontAttributes" Value="Bold"> <Setter Property="FontFamily" Value="{StaticResource OpenSansBold}" /> </Trigger> <Trigger TargetType="Label" Property="FontAttributes" Value="Italic"> <Setter Property="FontFamily" Value="{StaticResource OpenSansItalic}" /> </Trigger> <Trigger TargetType="Label" Property="FontAttributes" Value="None"> <Setter Property="FontFamily" Value="{StaticResource OpenSansRegular}" /> </Trigger> </Style.Triggers> </Style>
این کد میگوید زمانیکه FontAttributes یک Label برابر با Bold است، از OpenSansBold برای FontFamily اش استفاده شود و همینطور برای Italic و None (یا همان Regular)
در قسمتیکه داشتیم برای اندروید و ویندوز، مسیر فایل فونت را مشخص میکردیم، از مقدار OpenSansRegular.ttf#Open Sans استفاده کردیم که OpenSansRegular.ttf نام فیزیکی فایل و Open Sans نام خود فایل است که با دو بار کلیک کردن روی فایل آن در ویندوز از طریق برنامه Font ویندوز قابل مشاهده است:
همچنین برای اینکه این سه فایل، سه بار برای سه پلتفرم در سورس کنترلر کپی نشوند، از روش Add as link در Visual Studio بهره گرفتهایم و فایل فیزیکی فونتها فقط در پروژه UWP وجود دارند. البته این به معنای این نیست که در Apk نهایی Android و ipa نهایی iOS این فایلها وجود نخواهند داشت؛ بلکه به خاطر ماهیت Add as link، انگار که این فایلها در هر سه پروژه هستند و پشت صحنه کپی میشوند.
در ویژوال استودیو به صورت پیشفرض این امکان وجود ندارد که برای کدهای خود، حداکثر تعداد کاراکتر در یک خط را مشخص کنید؛ اما با استفاده از افزونهی Editor Guidelines میتوانید برای خود حداکثر کاراکترهای نوشته شده را مشخص کنید و در زمان توسعه برای شما مشخص میکند که باید در کدام قسمت، به خط بعدی بروید و مشکل اسکرول کردن به صورت افقی را در زمان مرور و مرج کردن برنچها نداشته باشید. برای اینکار ابتدا باید افزونه را از قسمت Extensions نصب نمایید.
در ادامه باید بر روی Solution یک فایل جدید را به نام editorconfig. ایجاد کنید و تنظیمات مربوط به حداکثر کاراکترهای یک خط را وارد نمایید.
[*.{cs}] guidelines = 80
در تنظیمات بالا مشخص شدهاست که در فایلهایی با پسوند cs. که همان فایلهای سیشارپ هستند، در کاراکتر 80، یک راهنما ایجاد شود تا زمانیکه توسعه دهنده به کاراکتر 80 رسید، متوجه شود باید در خط بعد ادامه کدهای خود را بنویسد. به صورت پیشفرض رنگ راهنمای ایجاد شده قرمز میباشد و برای تغییر رنگ آن باید در مسیر زیر
Tools > Options > Environment > Font and Colors > Guideline
مقدار رنگ قرمز را به رنگ مورد نظر خود تغییر دهید. سپس اگر یک فایل با پسوند cs را باز کنید، در کاراکتر 80، نقطه چین ایجاد شده به صورت عمودی را مشاهده میکنید که برای راهنمایی شما ایجاد شده است تا کدهای شما بیشتر از 80 کاراکتر در یک خط نشود.
منابع مرتبط:
در پست قبل به بررسی انتخاب عناصر بر اساس موقعیت پرداختیم، در این پست به بحث "استفاده از انتخاب کنندههای سفارشی 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 گزینههای بسیار متعددی را به منظور انتخاب عناصر موجود در صفحه برای ما مهیا کرده است که میتوانیم هر عنصری از صفحه را انتخاب و سپس تغییر دهیم که تغییر این عناصر در پستهای آینده بحث خواهد شد.
موفق و موید باشید.
ng new angular-apps --create-application=false
ng generate application cac-web
{ "compilerOptions": { "baseUrl": "src", "paths": { "@app/*": [ "app/*" ], "@app/core/*": [ "app/core/*" ], "@app/shared/*": [ "app/shared/*" ], "@env/*": [ "environments/*" ] } } }
|node_modules |projects--------------cac-web|e2e-----------------|src | | |protractor.conf.js | | |tsconfig.json | |src-----------------|app-----------------|client(Module) | | | |shared(Module) | | | |core(Module) | | | |app-routing.module.ts | | | |app.component.html | | | |app.component.ts | | | |app.module.ts | | |assets | | |environments | | |index.html | | |main.ts | | |polyfills.ts | | |styles.css | | |test.ts | |browserslist | |karma.conf.js | |tsconfig.app.json | |tsconfig.spec.json | |tslint.json |.editorconfig |.gitignore |angular.json |package-lock.json |package.json |README.md |tsconfig.json |tslint.json
در اینجا React عنوان میکند که هر عنصر از لیستی را که در حال نمایش آن هستید، باید به همراه یک key، ارائه دهید. اما ... این key چیست؟
زمانیکه حالت کامپوننتی تغییر میکند (شیء یا اشیایی که به عناصر UI متصل هستند، تغییر میکنند)، React، درخت جدیدی از اشیایی را که باید رندر شوند، تولید میکند. اکنون React باید محاسبه کند که چه عناصری نسبت به درخت فعلی که در حال نمایش است، تغییر کردهاند تا فقط آنها را به DOM اصلی اعمال کند؛ تا این تغییرات به کاربر نمایش داده شوند و ... این کار را هم به خوبی انجام میدهد. پس مشکل با لیستها چیست که نیاز به key دارند؟
فرض کنید رندر لیستی، خروجی زیر را تولید میکند:
<li>Item 1</li> <li>Item 2</li>
<li>Item 1</li> <li>Item 2</li> <li>Item 3</li>
<li>Item 0</li> // <= New item <li>Item 1</li> <li>Item 2</li> <li>Item 3</li>
راهحل React برای تشخیص منحصربفرد بودن المانهای یک لیست و یا آرایه، استفاده از خاصیت key است:
<li key={0}>Item 0</li> // <= New item <li key={1}>Item 1</li> <li key={2}>Item 2</li> <li key={3}>Item 3</li>
این نکات چه ربطی به Blazor دارند؟!
واقعیت این است که Blazor، همان نسخهی مایکروسافتی React است (!) و این خاصیت key، در Blazor نیز تحت عنوان key directive@ وجود دارد و دقیقا مفهوم آن نیز با توضیحاتی که در مورد React داده شد، یکی است.
زمانیکه Blazor صفحهای را رندر میکند، ابتدا یک DOM مشخصی را تولید خواهد کد. سپس با تغییر State یک کامپوننت، DOM جدیدی را محاسبه کرده و آنرا با DOM فعلی مقایسه میکند و در نهایت diff تولیدی را به DOM موجود، در جهت نمایش تغییرات، اعمال خواهد کرد.
بنابراین الگوریتم diff باید اضافات، به روز رسانیها و حذفهای صورت گرفتهی در UI را تشخیص داده و فقط قسمتهای تغییر یافته را جهت به روز رسانی بهینهی UI، به آن اعمال کند. این الگوریتم diff به صورت پیشفرض از ایندکس المانها برای مقایسهی آنها استفاده میکند. هرچند این روش در بسیاری از حالات، بهینهترین روش است، اما در مورد لیستها خیر؛ که توضیحات آنرا با مثالی در مورد React، در ابتدای بحث بررسی کردیم. برای مثال اگر شیءای به انتهای لیست اضافه شود، هر المانی را که پس از این ایندکس قرار گرفته باشد، تغییر یافته درنظر گرفته و آنرا مجددا رندر میکند. به همین جهت است که اگر المانی به ابتدای یک لیست اضافه شود، اینبار کل لیست را مجددا رندر میکند (چون تمام ایندکسهای اشیاء موجود در لیست، تغییر کردهاند)؛ صرفنظر از اینکه عناصری از این لیست، پیشتر در UI رندر شدهاند و نیازی به رندر مجدد، ندارند.
یک مثال: بررسی نحوهی رندر لیستی از اشیاء در Blazor
در اینجا کدهای کامل کامپوننتی را مشاهده میکنید که یک لیست ساده را در ابتدا رندر کرده و هر بار که بر روی دکمهی افزودن یک شخص کلیک میشود، شخص جدیدی را به ابتدای لیست اضافه میکند:
@page "/" <button class="btn btn-primary" @onclick="addPerson">Add Person</button> <ul class="mt-5"> @foreach (var person in People) { <li>@person.Id, @person.Name</li> } </ul> @code{ List<Person> People = new() { new() { Id = 1, Name = "User 1" }, new() { Id = 2, Name = "User 2" }, }; int LastId = 2; void addPerson() { People.Insert(0, new() { Id = ++LastId, Name = $"User {LastId}" }); } class Person { public int Id { set; get; } public string Name { set; get; } } }
همانطور که مشاهده میکنید، در مثال فوق هر بار کلیک بر روی دکمهی افزودن یک شخص جدید، به همراه رندر تمام المانهای لیست است (محتوای تمام liها یکبار صورتی شده و بعد به حالت عادی در میآیند).
تغییر الگوریتم diff محاسبهی تغییرات UI
الگوریتم پیشفرض diff، از ایندکسهای عناصر برای یافتن تغییرات، استفاده میکند. با استفاده از دایرکتیو ویژهی key@ میتوان ایندکسهای پیشفرض را با مقادیری منحصربفرد، بازنویسی کرد، تا اینبار Blazor دقیقا بداند که کدام آیتم، جدید است و کدامها نیازی به رندر مجدد ندارند:
<ul class="mt-5"> @foreach (var person in People) { <li @key="person.Id">@person.Id, @person.Name</li> } </ul>
اگر به تصویر فوق دقت کنید، اینبار فقط li جدیدی که اضافه شدهاست، ابتدا با رنگ صورتی نمایش داده میشود و محتوای داخل سایر li ها، دست نخورده باقی ماندهاست؛ یعنی مجددا رندر نشدهاند.