مطالب
قابلیت های جدید VisualStudio.NET 2012 - قسمت سوم
اگر شما در زمینه طراحی وب سایت و برنامه‌های کاربردی تحت وب فعالیت دارید حتماً با ابزارهایی مانند Firebug آشنا هستید. معمولاً فرآیند بررسی مشکلات رابط کاربری و موضوعات مشابه آن بصورت زیر بوده است:
 
 

توجه داشته باشید که یک صفحه وب که در مرورگر به نمایش در می‌آید، برآیند فایل‌های جاوا اسکریپت، شیوه نامه ها، User control ها، صفحات ASPX و Master Page‌ها و ... است. افزون بر این آنچه در مرورگر نمایش داده می‌شود با چیزی که ما در محیط طراحی (Visual Studio.NET (Design View  می‌بینیم متفاوت است.
تمام مشکلات و سختی‌های بالا دست به دست هم دادند تا در نسخه جدید نرم افزار Visual Studio.NET شاهد ابزار جادویی با عنوان Page Inspector باشیم.
این ابزار بصورت Real-time امکان نگاشت (mapping) عناصر موجود در نتیجه نهایی برنامه وب را با سورس کد مهیا می‌کند. بدین معنا توسط Page Inspector با حرکت ماوس روی عناصر صفحه در مرورگر، Visual Studio.NET بخشی را که آن عنصر را تولید کرده است (User Control, Master Page, View, و ...) نمایش می‌دهد و شما می‌توانید بلافاصله پس از اعمال تغییرات جدید بر روی سورس کد، نتیجه را روی مرورگر ببنید. البته عکس این موضوع نیز صادق است و شما می‌توانید با حرکت در سورس کد، نتیجه بصری و عناصر HTML ی که در نتیجه تولید می‌شوند را مشاهده کنید. (عناصر متناظر به حالت Select در می‌آیند.)
 

 
از دیگر قابلیت‌های این ابزار نمایش CSS‌های متناظر هر عنصر است. شما می‌توانید هر یک از قوانینی که در Style هر عنصر تعریف کرده اید را فعال و یا غیر فعال کنید. همچنین امکان ویرایش آن‌ها وحود دارد.
همچنین از طریق گزینه File می‌توان لیست تمام فایل‌های سورس صفحه را مشاهده کرد.
 

 
با وجود چنین ابزاری یقیناً داشتن دو مانیتور برای برنامه نویسان و توسعه دهندگان وب کاملاً حیاتی است. چراکه Visual Studio.NET به شما این امکان را می‌دهد تا Page Inspector را در یک مانیتور و نمای سورس را در مانیتور دیگر به نمایش در آورید.
 
نکته:
جهت استفاده از تمام امکانات این ابزار باید دستور زیر را در تگ appsettings فایل web.config اضافه کنید:
 <add key="VisualStudioDesignTime:Enabled" value="true" />
پیشنهاد می‌کنم برای درک بهتر این ابزار و آشنایی با آن ویدئو مربوطه در کانال 9  را از دست ندهید.
 
مطالب
Angular Material 6x - قسمت دوم - معرفی Angular Flex layout
در این سری قصد داریم یک برنامه‌ی ساده‌ی دفترچه تلفن را توسط Angular 6x و کامپوننت‌های متریال آن ایجاد کنیم؛ اما Grid جزئی از بسته‌ی Angular Material نیست. بنابراین برای طرحبندی برنامه و قرار دادن المان‌های مختلف در مکان‌های تعیین شده‌ی صفحه، از Angular FlexBox Module استفاده خواهیم کرد که محصور کننده‌ی CSS 3 FlexBox است.


آشنایی با Flex Layout Box Model

برای طراحی ظاهر یک برنامه‌ی وب نیاز است عناصر آن‌را در مکان‌های مختلفی از صفحه قرار داد که به آن Layout گفته می‌شود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
1. Table
2. Float, position, clear
3. CSS Grids
4. FlexBox CSS

امروزه دیگر آنچنان روش‌های 1 و 2 به صورت مستقیم مورد استفاده قرار نمی‌گیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی می‌کنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی می‌شود.


FlexBox CSS، سیلان المان‌های قرار گرفته‌ی در داخل آن‌را سبب می‌شود. در اینجا یک container اصلی وجود دارد که در برگیرنده‌ی المان‌ها است. در تصویر فوق دو محور را مشاهده می‌کنید. محور افقی از چپ به راست ادامه پیدا می‌کند. محور عمودی نحوه‌ی ارتباط عناصر را مشخص می‌کند.
اکنون این سؤال مطرح می‌شود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلول‌ها نگاه کرد. برای مثال عناصر قرار گرفته‌ی درون Container یا به صورت افقی درون آن گسترده شده و قرار می‌گیرند و یا به صورت عمودی.


نحوه‌ی تفکر و کارکرد با FlexBox چگونه است؟

در اینجا باید به دو مفهوم دقت داشت:
الف) سیلان عناصر درون Container که می‌تواند افقی و یا عمودی باشد.
ب) اندازه‌ی المان‌ها که می‌تواند ثابت و یا نسبی باشد.

یک Container، جهت سیلان عناصر درون آن‌را مشخص می‌کند. المان‌های آن، اندازه، فاصله‌ی از یکدیگر و ترتیب قرارگیری را ارائه می‌دهند. یک flex container می‌تواند شامل چندین flex container تو در تو نیز باشد.


نحوه‌ی سیلان عناصر در FlexBox چگونه است؟

برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:


نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:


سپس نحوه‌ی سیلان عناصر درون Containerهای تعریف شده را مشخص می‌کنیم. برای مثال اولین Container دارای سیلان افقی از چپ به راست خواهد بود که عنصر سوم آن به دلیل اندازه‌های مشخص دو عنصر قبلی، به سطر دوم منتقل شده‌است.


در ادامه به قسمت میانی می‌رسیم که آن نیز دارای سیلان افقی از چپ به راست است:


در اینجا نیز می‌توان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازه‌ی آن‌ها در یک سطر جا نشده‌اند، به سطر بعدی منتقل خواهند شد:


و تمام این سیلان‌ها و انتقال به سطرهای بعدی بر اساس اندازه‌ی المان‌ها صورت می‌گیرد:


البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیه‌ی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار می‌گیرد، دارای سیلان خاص خودش است و اندازه‌های آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیه‌ی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، این‌ها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.


معرفی کتابخانه‌ی Angular Flex Layout

برای کار با Flex CSS نیاز است:
- مقدار زیادی کد CSS نوشت.
- نیاز به درک عمیقی از Flex Box دارد.
- نیاز است با باگ‌های مرورگرها و تفاوت‌های پیاده سازی‌های آن‌ها در مورد FlexBox آشنا بود.
- نیاز به Prefixing دارد.
- برای Angular طراحی نشده‌است.

جهت رفع این مشکلات و محدودیت‌ها، تیم Angular کتابخانه‌ای را به نام Angular Flex Layout مخصوص نگارش‌های جدید Angular طراحی کرده‌است. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده می‌شود.

مزایای کار با کتابخانه‌ی Angular flex layout
- یک کتابخانه‌ی متکی به خود و مستقل است و برای کار با آن الزامی به استفاده‌ی از Angular Material نیست.
- به همراه هیچ فایل CSS جانبی ارائه نمی‌شود.
- پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شده‌است.
- به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق می‌کند.
- به همراه یک API استاتیک است و همچنین یک API واکنشگرا
- با Angular CLI نیز یکپارچه شده‌است.


نصب و تنظیم کتابخانه‌ی Angular Flex layout

برای نصب این کتابخانه، در ریشه‌ی پروژه دستور زیر را صادر کنید:
 npm install @angular/flex-layout --save
سپس ماژول آن‌را باید به shared.module.ts اضافه کرد:
import { FlexLayoutModule } from "@angular/flex-layout";

@NgModule({
  imports: [
    FlexLayoutModule
  ],
  exports: [
    FlexLayoutModule
  ]
})
export class SharedModule {
}


کار با API استاتیک Angular Flex layout

API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
- به صورت یکسری دایرکتیو Angular طراحی شده‌است که به HTML قالب کامپوننت‌ها اضافه می‌شود.
- از data binding پشتیبانی می‌کند.
- CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق می‌کند. Inline CSS تزریق شده به ویژگی‌های styles هر المان تزریق می‌شوند و موارد مشابه را در صورت وجود بازنویسی می‌کنند.
- از تشخیص تغییرات پشتیبانی می‌کند.
- به همراه ویژگی‌های fxHide و fxShow است.
- کارآیی مطلوبی دارد.

در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده می‌شود:
- fxLayout جهت‌های flex را مشخص می‌کند.
<div fxLayout="row" 
     fxLayout.xs="column"></div>
- fxLayout می‌تواند دارای مقداری مانند row، column و row-reverse و column-reverse باشد. برای مثال مقدار row-reverse‌، نمایش از راست به چپ را سبب می‌شود.
- fxLayoutWrap مشخص می‌کند که آیا المان‌ها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
<div fxLayoutWrap></div>
- fxLayoutGap فاصله‌ی بین المان‌ها را مشخص می‌کند.
<div fxLayoutGap="10px"></div>
- fxLayoutAlign نحوه‌ی چیدمان المان را تعیین می‌کند.
<div fxLayoutAlign="start stretch"></div>

چند مثال:


و یا حالت راست به چپ آن به صورت زیر است:


و برای تعریف آیتم‌های قرار گرفته‌ی درون containers می‌توان از دایرکتیوهای زیر استفاده کرد:
- fxflex برای تعیین اندازه و flex المان‌ها
<div fxFlex="1 2 calc(15em + 20px)"></div>
در اینجا سه مقداری که ذکر می‌شوند (و یا تنها یک مقدار) چنین معنایی را به همراه دارند:
 fxFlex="grow shrink basis"
و یا
 fxFlex="basis"
- grow به این معنا است که آیتم جاری در صورت وجود فضا (طراحی واکنشگرا و واکنش نشان دادن به اندازه‌ی صفحه)، نسبت به سایر المان‌ها تا چه اندازه‌ای می‌تواند بزرگ شود.
- shrink به این معنا است که اگر به اندازه‌ی کافی فضا وجود نداشت، این المان نسبت به سایر المان‌های دیگر تا چه اندازه‌ای می‌تواند کوچک شود.
- basis به معنای اندازه‌ی پیش‌فرض المان است.

در اینجا اندازه‌ها برحسب پیکسل، درصد و یا calcs, em, cw, vh می‌توانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.

- fxflexorder برای تعیین ترتیب قرارگیری یک المان
<div fxFlexOrder="2"></div>
-  fxflexoffset برای تعیین فاصله یک المان از container آن
 <div fxFlexOffset="20px"></div>
-  fxflexAlign برای تعیین محل قرارگیری المان
 <div fxFlexAlign="center"></div>
- fxflexfill برای تعیین اینکه این المان کل ردیف یا ستون را پر خواهد کرد
 <div fxFlexFill></div>

چند مثال:


در اینجا سه نمایشی را که در ذیل تعریف div‌ها مشاهده می‌کنید بر اساس تغییر اندازه‌ی صفحه حاصل شده‌اند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازه‌ی صفحه و دسترسی به مقدار فضای بیشتر، بزرگ‌تر شده‌است.

یک مثال کامل
اگر علاقمند باشید تا توانمندی‌های angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
https://tburleson-layouts-demos.firebaseapp.com/#/docs
در این مثال با تغییر گزینه‌‌های مختلف، کد معادل angular flex layout آن نیز تولید می‌شود.
همچنین wiki خود پروژه نیز به همراه مثال‌های بیشتری است:
https://github.com/angular/flex-layout/wiki



کار با API واکنشگرای Angular Flex layout


در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازه‌ی صفحه و یا اندازه‌ی وسیله‌ی نمایشی، تغییر اندازه و همچنین موقعیت می‌دهند و این تغییرات بر اساس انطباق با viewport وسیله‌ی نمایشی صورت می‌گیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار می‌دهد.
اگر علاقمند هستید تا اندازه‌های واکنشگرای استاندارد متریال را ملاحظه کنید، می‌توانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
https://material.io/design/layout/responsive-layout-grid.html#breakpoints
برای مثال هر اندازه‌ای کمتر از 600px در گروه extra small قرار می‌گیرد (با مخفف xs). از 600px تا 1024px در بازه‌ی small (با مخفف sm)، از 1024px تا 1440px در بازه‌ی medium (با مخفف md) و از 1440px تا 1920px در بازه‌ی large (با مخفف lg) و بیشتر از آن در بازه‌ی xlrage قرار می‌گیرند (با مخفف xl). این اعداد و بازه‌ها، پایه‌ی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازه‌ها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شده‌اند و مورد استفاده قرار می‌گیرند. همچنین اگر اندازه‌های مدنظر از این بازه‌ها کمتر باشند، می‌توان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازه‌های مورد نیاز بیش از این اندازه‌ها باشند می‌توان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
به این مخفف‌ها «media query alias» هم گفته می‌شود و اکنون که لیست آن‌ها مشخص است، تنها کافی است آن‌ها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
fxLayout.sm = "..."
fxLayoutAlign.md = "..."
fxHide.gt-sm = "..."
برای نمونه فرض کنید یک چنین طرحبندی دسکتاپی را داریم:


معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:

که در اینجا دو container را ملاحظه می‌کنید. ابتدا Container بیرونی جهت ارائه‌ی ستونی از سه المان اضافه شده‌است. سپس یک Container میانی برای  تعریف ردیفی از سه المان تعریف شده‌است. توسط روش "fxFlex="grow shrink basis نیز اندازه‌های آن‌ها مشخص شده‌اند.

اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آن‌را تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟


برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه می‌کنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازه‌ی xs، محل قرارگیری المان‌های این ستون را هم مشخص می‌کنیم:


همانطور که ملاحظه می‌کنید کار کردن با این API بسیار ساده‌است و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
یک نکته: مثال کاملی که پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهده‌ی اثر آن‌ها بهتر است اندازه‌ی مرورگر را کوچک و بزرگ کنید.


مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازه‌ی آن

علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمت‌های مختلف صفحه بر اساس اندازه‌ی صفحه‌ی نمایشی نیز هست:
 <div fxShow fxHide.xs="false" fxHide.lg="true"></div>
در اینجا fxShow سبب نمایش این div در حالت عادی می‌شود (پیش‌فرض آن xl، md و sm است). اما اگر اندازه‌ی صفحه lg باشد، fxHide.lg تنظیم شده‌ی به true سبب مخفی سازی آن خواهد شد و در اندازه‌ی xs مجددا نمایش داده می‌شود.


تغییر اندازه‌ی قسمتی از صفحه بر اساس اندازه‌ی آن

در مثال زیر اگر اندازه‌ی صفحه gt-sm باشد (بیشتر از small)، اندازه‌ی این div به 100 درصد بجای 50 درصد حالت‌های دیگر، تنظیم می‌شود:
 <div fxFlex="50%" fxFlex.gt-sm="100%"></div>

حالت‌های ویژه‌ی طراحی واکنشگرا در Angular Flex Layout

در API واکنشگرای آن حالت‌های ویژه‌ی fxshow, fxhide, ngclass  و  ngstyle نیز درنظر گرفته شده‌اند که امکان فعالسازی آن‌ها در اندازه‌های مختلف صفحه مسیر است:
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div>
<div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div>
<div [ngClass.sm]="{'fxClass-sm': hasStyle}" ></div>
<div [ngStyle.xs]="{color: 'blue'}"></div>


امکان کار با API واکنشگرا از طریق برنامه نویسی

برای این منظور می‌توان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:


در اینجا به فعالسازی یک بازه‌ی خاص گوش فرا خواهیم داد. برای مثال اگر اندازه‌ی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.



برای مطالعه‌ی بیشتر
قسمت‌های عمده‌ای از مطلب جاری، از ویدیوی زیر که توسط نویسنده‌ی اصلی angular flex layout تهیه شده‌است، گردآوری شدند.
 
مطالب
بررسی تغییرات Blazor 8x - قسمت پنجم - امکان تعریف جزیره‌های تعاملی Blazor Server
در Blazor 8x می‌توان صفحات SSR ای را به همراه Blazor server islands و یا Blazor WASM islands داشت؛ یعنی یک کامپوننت Blazor Server که داخل یک صفحه‌ی معمولی SSR قرار گرفته و با سرور، ارتباط SiganlR برقرار می‌کند و یا یک کامپوننت Blazor WASM که در قسمتی از صفحه‌ی SSR درج شده و درون مرورگر کاربر اجرا می‌شود. به هر کدام از این‌ها یک «جزیره‌ی تعاملی» گفته می‌شود (interactive island). در این قسمت، نکات مرتبط با جزایر تعاملی Blazor Server را بررسی می‌کنیم.


بررسی یک مثال: تهیه یک برنامه‌ی Blazor 8x برای نمایش لیست محصولات، به همراه جزئیات آن‌ها

به لطف وجود SSR در Blazor 8x، می‌توان HTML نهایی کامپوننت‌ها و صفحات Blazor را همانند صفحات MVC و یا Razor pages، در سمت سرور تهیه و بازگشت داد. این خروجی در نهایت یک static HTML بیشتر نیست و گاهی از اوقات ما به بیش از یک خروجی ساده HTML ای نیاز داریم.
در این مثال که بر اساس قالب dotnet new blazor --interactivity Server تهیه می‌شود، قصد داریم موارد زیر را پیاده سازی کنیم:
- صفحه‌ای که یک لیست محصولات فرضی را نمایش می‌دهد : بر اساس SSR
- صفحه‌ای که جزئیات یک محصول را نمایش می‌دهد: بر اساس SSR
- دکمه‌ای در ذیل قسمت نمایش جزئیات یک محصول، برای دریافت و نمایش لیست محصولات مشابه و مرتبط: بر اساس Blazor server islands

یعنی تا جائیکه ممکن است قصد نداریم تمام صفحات و تمام قسمت‌های برنامه را با فعالسازی سراسری حالت تعاملی Blazor server که در قسمت‌های قبل در مورد آن توضیح داده شد، پیاده سازی کنیم. می‌خواهیم فقط قسمت کوچکی از این سناریو را که واقعا نیاز به یک چنین قابلیتی را دارد، توسط یک جزیره‌ی تعاملی Blazor server واقع شده‌ی در قسمتی از یک صفحه‌ی استاتیک SSR، مدیریت کنیم.


مدل برنامه: رکوردی برای ذخیره سازی اطلاعات یک محصول

namespace BlazorDemoApp.Models;

public record Product
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public required string Description { get; set; }
    public decimal Price { get; set; }

    public List<int> Related { get; set; } = new();
}
در اینجا، هدف تعریف لیستی از محصولات فرضی است؛ به همراه خاصیتی که Id محصولات مشابه را نگهداری می‌کند (خاصیت Related).


سرویس برنامه: سرویسی برای بازگشت لیست محصولات

چون Blazor Server و SSR هر دو بر روی سرور اجرا می‌شوند، از لحاظ دسترسی به اطلاعات و کار با سرویس‌ها، هماهنگی کاملی وجود داشته و می‌توان کدهای یکسان و یکدستی را در اینجا بکار گرفت.
در ادامه کدهای کامل سرویس Services\ProductStore.cs را مشاهده می‌کنید:
using BlazorDemoApp.Models;

namespace BlazorDemoApp.Services;

public interface IProductStore
{
    IList<Product> GetAllProducts();
    Product GetProduct(int id);
    IList<Product> GetRelatedProducts(int productId);
}

public class ProductStore : IProductStore
{
    private static readonly List<Product> ProductsDataSource =
        new()
        {
            new Product
            {
                Id = 1, Title = "Smart speaker", Price = 22m,
                Description =
                    "This smart speaker delivers excellent sound quality and comes with built-in voice control, offering an impressive music listening experience.",
                Related = new List<int> { 2, 3 },
            },
            new Product
            {
                Id = 2, Title = "Regular speaker", Price = 89m,
                Description =
                    "Enjoy room-filling sound with this regular speaker. With its slick design, it perfectly fits into any room in your house.",
                Related = new List<int> { 1, 3 },
            },
            new Product
            {
                Id = 3, Title = "Speaker cable", Price = 12m,
                Description =
                    "This high-quality speaker cable ensures a reliable and clear audio connection for your sound system.",
            },
        };

    public IList<Product> GetAllProducts() => ProductsDataSource;

    public Product GetProduct(int id) => ProductsDataSource.Single(p => p.Id == id);

    public IList<Product> GetRelatedProducts(int productId)
    {
        var product = ProductsDataSource.Single(x => x.Id == productId);
        return ProductsDataSource.Where(p => product.Related.Contains(p.Id)).ToList();
    }
}
هدف از این سرویس، ارائه‌ی لیست تمام محصولات، دریافت اطلاعات یک محصول و همچنین یافتن لیست محصولات مشابه یک محصول خاص است.
این سرویس را باید در فایل Program.cs برنامه به صورت زیر معرفی کرد تا در فایل‌های razor برنامه‌ی جاری قابل دسترسی شود:
builder.Services.AddScoped<IProductStore, ProductStore>();


تکمیل صفحه‌ی نمایش لیست محصولات

قصد داریم زمانیکه کاربر برای مثال به آدرس فرضی http://localhost:5136/products مراجعه کرد، با تصویر لیستی از محصولات مواجه شود:


کدهای این صفحه را که در فایل Components\Pages\Store\ProductsList.razor قرار می‌گیرند، در ادامه مشاهده می‌کنید:

@page "/Products"
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services

@inject IProductStore Store

@attribute [StreamRendering]

<h3>Products</h3>

@if (_products == null)
{
    <p>Loading...</p>
}
else
{
    @foreach (var item in _products)
    {
        <a href="/ProductDetails/@item.Id">
            <div>
                <div>
                    <h5>@item.Title</h5>
                </div>
                <div>
                    <h5>@item.Price.ToString("c")</h5>
                </div>
            </div>
        </a>
    }
}

@code {
    private IList<Product>? _products;

    protected override Task OnInitializedAsync() => GetProductsAsync();

    private async Task GetProductsAsync()
    {
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering
        _products = Store.GetAllProducts();
    }

}
توضیحات:
- جهت دسترسی به سرویس لیست محصولات، ابتدا سرویس IProductStore به این صفحه تزریق شده‌است.
- سپس در روال رویدادگردان آغازین OnInitializedAsync، کار دریافت اطلاعات و انتساب آن به لیستی، صورت گرفته‌است.
- در این متد جهت شبیه سازی یک عملیات async از یک Task.Delay استفاده شده‌است.
- چون این صفحه، یک صفحه‌ی SSR عادی است، بدون تعریف ویژگی StreamRendering در آن، پس از اجرای برنامه، هیچگاه قسمت loading که در حالت products == null_ قرار است ظاهر شود، نمایش داده نمی‌شود؛ چون در این حالت (حذف نوع رندر)، صفحه‌ی نهایی که به کاربر ارائه خواهد شد، یک صفحه‌ی استاتیک کاملا رندر شده‌ی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحه‌ی نهایی را دریافت و مشاهده کند. در حالت Streaming rendering، ابتدا می‌توان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آن‌را به محض آماده شدن در طی چند مرحله بازگشت داد.
- لینک‌های نمایش داده شده‌ی در اینجا، به صفحه‌ی ProductDetails اشاره می‌کنند که در آن، جزئیات محصول انتخابی نمایش داده می‌شوند.


تکمیل صفحه‌ی نمایش جزئیات یک محصول


در صفحه‌ی کامپوننت Components\Pages\Store\ProductDetails.razor، کار نمایش جزئیات محصول انتخابی صورت می‌گیرد:

@page "/ProductDetails/{ProductId}"
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services

@inject IProductStore Store

@attribute [StreamRendering]

@if (_product == null)
{
    <p>Loading...</p>
}
else
{
    <div>
        <div>
            <h5>
                @_product.Title (@_product.Price.ToString("C"))
            </h5>
            <p>
                @_product.Description
            </p>
        </div>
        @if (_product.Related.Count > 0)
        {
            <div>
                <RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
            </div>
        }
    </div>
    <NavLink href="/Products">Back</NavLink>
}

@code {
    private Product? _product;

    [Parameter]
    public string? ProductId { get; set; }

    protected override Task OnInitializedAsync() => GetProductAsync();

    private async Task GetProductAsync()
    {
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering
        _product = Store.GetProduct(Convert.ToInt32(ProductId));
    }

}
توضیحات:
- باتوجه به نحوه‌ی تعریف مسیریابی این صفحه، پارامتر ProductId از طریق آدرسی مانند http://localhost:5136/ProductDetails/1 دریافت می‌شود.
- سپس این ProductId را در روال رخ‌دادگردان OnInitializedAsync، برای یافتن جزئیات محصول انتخابی از سرویس تزریقی IProductStore، بکار می‌گیریم.
- در اینجا نیز از Task.Delay برای شبیه سازی یک عملیات طولانی async مانند دریافت اطلاعات از یک بانک اطلاعاتی، کمک گرفته شده‌است.
- همچنین برای نمایش قسمت loading صفحه در حالت SSR، بازهم از StreamRendering استفاده کرده‌ایم.
- اگر دقت کرده باشید، ذیل تصویر اطلاعات محصول، دکمه‌ای نیز جهت بارگذاری اطلاعات محصولات مشابه، قرار دارد که ProductId محصول انتخابی را دریافت می‌کند:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
بنابراین در ادامه کامپوننت RelatedProducts فوق را تکمیل می‌کنیم.


تکمیل کامپوننت نمایش لیست محصولات مشابه و مرتبط

در فایل Components\Pages\Store\RelatedProducts.razor، کار نمایش یک دکمه و سپس نمایش لیستی از محصولات مشابه، صورت می‌گیرد:
@using BlazorDemoApp.Models
@using BlazorDemoApp.Services
@inject IProductStore Store

<button @onclick="LoadRelatedProducts">Related products</button>

@if (_loadRelatedProducts)
{
    @if (_relatedProducts == null)
    {
        <p>Loading...</p>
    }
    else
    {
        <div>
            @foreach (var item in _relatedProducts)
            {
                <a href="/ProductDetails/@item.Id">
                    <div>
                        <h5>@item.Title (@item.Price.ToString("C"))</h5>
                    </div>
                </a>
            }
        </div>
    }
}

@code{

    private IList<Product>? _relatedProducts;
    private bool _loadRelatedProducts;

    [Parameter]
    public int ProductId { get; set; }

    private async Task LoadRelatedProducts()
    {
        _loadRelatedProducts = true;
        await Task.Delay(1000); // Simulates asynchronous loading to demonstrate InteractiveServer mode
        _relatedProducts = Store.GetRelatedProducts(ProductId);
    }

}

تعاملی کردن کامپوننت نمایش لیست محصولات مشابه

مشکل! اگر در این حالت برنامه را اجرا کرده و بر روی دکمه‌ی related products کلیک کنیم، هیچ اتفاقی رخ نمی‌دهد! یعنی روال رویدادگران LoadRelatedProducts اصلا اجرا نمی‌شود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیت‌های تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند.
محدودیتی که به همراه صفحات SSR وجود دارد این است: این نوع کامپوننت‌ها و صفحات فقط یکبار رندر می‌شوند و نه بیشتر. بله می‌توان بر روی آن‌ها ده‌ها دکمه، نوارهای لغزان، دراپ‌داون و غیره را قرار داد، اما ... نمی‌توان هیچگونه تعاملی را با آن‌ها داشت. کامپوننت نهایی رندر شده و نمایش داده شده، دیگر در هیچ‌جائی اجرا نمی‌شود. در این حالت است که می‌توان تصمیم گرفت که نیاز است قسمتی از این صفحه، تعاملی شود.
به همین جهت باید نحوه‌ی رندر کامپوننت RelatedProducts را به صورت یک جزیره‌ی تعاملی Blazor server درآورد تا رویداد منتسب به دکمه‌ی related products موجود در آن، پردازش شود. بنابراین به صفحه‌ی ProductDetails.razor مراجعه کرده و rendermode@ این کامپوننت را به صورت زیر به حالت InteractiveServer تغییر می‌دهیم:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@InteractiveServer"/>
اکنون اگر برنامه را مجددا اجرا کرده و بر روی دکمه‌ی نمایش محصولات مشابه قرار گرفته در ذیل جزئیات یک محصول کلیک کنیم، بدون مشکل کار می‌کند:


نحوه‌ی پردازش پشت صحنه‌ی این نوع صفحات هم جالب است. برای اینکار به برگه‌ی network مخصوص developer tools مرورگر مراجعه کرده و مراحل رسیدن به صفحه‌ی نمایش جزئیات محصول را طی می‌کنیم:


- اگر دقت کنید، جابجایی بین صفحات، با استفاده از fetch انجام شده؛ یعنی با اینکه این صفحات در اصل static HTML خالص هستند، اما ... کار full reload صفحه مانند ASP.NET Web forms قدیمی انجام نمی‌شود (و یا حتی برنامه‌های MVC و Razor pages) و نمایش صفحات، Ajax ای است و با fetch استاندارد آن صورت می‌گیرد تا هنوز هم حس و حال SPA بودن برنامه حفظ شود. همچنین اطلاعات DOM کل صفحه را هم به‌روز رسانی نمی‌کند؛ فقط موارد تغییر یافته در اینجا به روز رسانی خواهند شد.
این موارد توسط فایل blazor.web.js درج شده‌ی در کامپوننت آغازین App.razor، به صورت خودکار مدیریت می‌شوند:
<script src="_framework/blazor.web.js"></script>

به علاوه در این حالت ای‌جکسی fetch، کار دریافت مجدد فایل‌های استاتیک مرتبط یک صفحه، مانند فایل‌های js.، css.، تصاویر و غیره، مجددا انجام نمی‌شود که این مورد خود مزیتی است نسبت به حالت متداول برنامه‌های ASP.NET Core MVC و یا Razor pages. در حالت Blazor 8x SSR، فقط یک partial update از نوع Ajax ای انجام می‌شود.
به این قابلیت، enhanced navigation هم گفته می‌شود. برای مثال زمانیکه یک فرم SSR را در Blazor 8x به سمت سرور ارسال می‌کنیم، موقعیت scroll به صورت خودکار ذخیره و بازیابی می‌شود تا کاربر با یک full post back مواجه نشده و موقعیت جاری خود را در صفحه از دست ندهد (چنین ایده‌ای، یک زمانی در برنامه‌های ASP.NET Web forms هم برقرار بود و هست! به نظر مایکروسافت هنوز دلتنگ طراحی قدیمی ASP.NET Web forms است!).

- همچنین به محض نمایش صفحه‌ی جزئیات محصول، پس از پایان کار نمایش آن، یک اتصال وب‌سوکت هم برقرار شده که مرتبط با جزیره‌ی تعاملی Blazor server تعریف شده، یا همان کامپوننت RelatedProducts است.

- یک disconnect را هم در اینجا مشاهده می‌کنید. اگر به یک صفحه‌ی تعاملی مراجعه کنیم، همانطور که مشخص است، یک اتصال SignalR برقرار می‌شود (که به آن در اینجا circuit هم می‌گویند). اما اگر از این صفحه به سمت یک صفحه‌ی SSR حرکت کنیم، پس از نمایش آن صفحه، اتصال SignalR قبلی که دیگر نیازی به آن نیست، بسته خواهد شد تا منابع سمت سرور، رها شوند.


در حین disconnect، شماره ID اتصال SignalR ای که دیگر به آن نیازی نیست، به برنامه ارسال می‌شود تا به صورت خودکار در سمت سرور بسته شود. تمام این موارد توسط blazor.web.js فریم‌ورک، مدیریت می‌شوند.
در این تصویر ابتدا به آدرس http://localhost:5136/ProductDetails/1 مراجعه کرده‌ایم که سبب برقراری اتصال یک وب‌سوکت شده‌است. سپس با کلیک بر روی دکمه‌ی back، به صفحه‌ی SSR مشاهده‌ی لیست محصولات برگشته‌ایم. در این حالت، دستور قطع اتصال SignalR قبلی صادر شده‌است.


نحوه‌ی مدیریت Pre-rendering در جزایر تعاملی Blazor 8x

به صورت پیش‌فرض زمانیکه از حالت رندر InteractiveServer استفاده می‌کنیم، قابلیت pre-rendering آن نیز فعال است. یعنی ابتدا حداقل قالب و قسمت‌های ثابت کامپوننت، در سمت سرور پردازش و رندر شده و سپس به سمت کلاینت ارسال می‌شوند. در این حالت کاربر، تجربه‌ی کاربری روان‌تری را شاهد خواهد بود؛ چون برای مدتی نباید منتظر آماده شدن کل UI مرتبط باشد و حداقل، قسمت‌هایی از صفحه که تعاملی نیستند، قابل دسترسی و مشاهده هستند.
اگر به هر دلیلی نیاز به غیرفعال کردن این قابلیت را دارید، باید به صورت زیر عمل کرد:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@(new InteractiveServerRenderMode(false))"/>
در این حالت اگر برنامه را اجرا کنید، در حین نمایش صفحه‌ی اصلی در برگیرنده‌ی از نوع SSR، فقط جای این کامپوننت در صفحه مشخص می‌شود و پس از برقراری اتصال با سرور از طریق اتصال SignalR، شاهد UI کامپوننت RelatedProducts خواهیم بود، که نسبت به قبل، وقفه‌ای را سبب خواهد شد.

نحوه‌ی تعریف خواص استاتیک InteractiveServer بکار گرفته شده و یا کلاس InteractiveServerRenderMode را در ادامه مشاهده می‌کنید. جهت سهولت تعریف این موارد، سطر زیر که یک using static است، به فایل Imports.razor_ اضافه شده‌است:
@using static Microsoft.AspNetCore.Components.Web.RenderMode

public static class RenderMode
  {
    public static InteractiveServerRenderMode InteractiveServer { get; } = new InteractiveServerRenderMode();

    public static InteractiveWebAssemblyRenderMode InteractiveWebAssembly { get; } = new InteractiveWebAssemblyRenderMode();

    public static InteractiveAutoRenderMode InteractiveAuto { get; } = new InteractiveAutoRenderMode();
  }


public class InteractiveServerRenderMode : IComponentRenderMode
  {
    public InteractiveServerRenderMode()
      : this(true)
    {
    }

    public InteractiveServerRenderMode(bool prerender) => this.Prerender = prerender;

    public bool Prerender { get; }
  }


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: Blazor8x-Server-Normal.zip  
اشتراک‌ها
بوی اسکات چیست؟
وقتی کدی را  برای اولین بار تحویل می‌دهید مثل این است که خود را بدهکار کرده باشید. هر بدهی کوچکی باعث افزایش سرعت توسعه می‌شود به شرطی که آن را در اسرع وقت و با بازنویسی کد، بازپرداخت کنید... زمانی این موضوع خطرناک می‌شود که بدهی بازپرداخت نشود. هر دقیقه‌ای که از عمر این کد که کاملا ًدرست نوشته نشده است می‌گذرد، مانند بهره‌ای است که به بدهی قبلی افزوده می‌شود. کل شرکت‌های مهندسی زیر فشار بدهی ناشی از بی‌توجهی به این‌گونه پیاده‌سازی‌های بازنویسی‌نشده ممکن است زمین‌گیر شوند و پیشرفت آنها متوقف شود. 
بوی اسکات چیست؟
مطالب
آشنایی با TransactionScope
TransactionScope روشی برای پیاده سازی تراکنش در .Net است که برای اولین بار در دات نت 2 معرفی شده است. روش پیاده سازی آن بسیار ساده است و همین سادگی و راحتی کار با اون باعث شده است که خیلی از برنامه نویس‌ها رو متمایل به خودش کنه.  در ادامه به روش استفاده و مزایا و معایب این روش برای پیاده سازی تراکنش‌ها می‌پردازیم.
این روش دارای تمام خواص یک تراکنش است(اصطلاحا به این خواص  ACID Properties گفته میشود)
1-Atomic : به این معناست که تمام دستورات بین بلاک (دستورات SQL و سایر عملیات) باید به صورت عملیات اتمی  کار کنند. یعنی یا تمام عملیات موفقیت آمیز است یا همه با شکست روبرو می‌شوند.
2 - Consistent: به این معناست که اگر تراکنش موفقیت آمیز بود پایگاه داده  باید در شروع تراکنش بعدی تغییرات لازم رو انجام داده باشد و در غیر این صورت پایگاه داده باید به حالت قبل از شروع تراکنش برگردد.
3- Isolated: اگر چند تا تراکنش هم زمان شروع شوند اجرای هیچ کدوم از اون‌ها نباید بر اجرای بقیه تاثیر بزاره.
4- Durable: یعنی تغییرات حاصل شده بعد از اتمام تراکنش باید دائمی باشند.

روش کار به این صورت است تمام کارهایی که قصد داریم در طی یک تراکنش انجام شوند باید در یک بلاک قرار بگیرند و تا زمانی که متد  Complete فراخوانی شود. در این بلاک شما هر عملیاتی رو که به عنوان جزئی از تراکنش می‌دونید قرار بدید. در صورتی که کنترل اجرا به فراخوانی دستور Complete برسه تمام موارد قبل از این دستور Commit  می‌شوند در غیر این صورت RollBack.
به مثال زیر دقت کنید.
ابتدا به پروژه مربوطه باید اسمبلی System.Transaction رو اضافه کنید.
using ( TransactionScope scope = new TransactionScope() )
  {
       //Statement1
       //Statement2
       //Statement3
         scope.Complete();
    }
تمام دستوراتی که در این بلاک نوشته شوند بعد از فراخوانی دستور scope.Complete اصطلاحا Commit می‌شوند. اگر به هر دلیلی فراخوانی دستورات به scope.Complete  نرسد عمل RollBack انجام می‌شود. در نتیجه برای این که عمل RollBack رو انجام دهید بهتره که قبل از دستور  Complete یک Exception رو پرتاب کنید که باعث فراخوانی Dispose می‌شود. کد زیر
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope() )
            {
                if ( result == 0 )
                {
                    throw new ApplicationException();
                }
                scope.Complete();
            }
نکته حائز اهمیت این است که اگر در هنگام اجرای برنامه به این روش به خطای
MSDTC on server {} is unavailable
برخوردید باید سرویس MSDTC رو Start کنید.برای این کار باید سرویس Distributed Transaction Coordinator رو از لیست سرویس‌های ویندوز پیدا کنید و بر روی اون راست کلیک کرده و دکمه Start رو بزنید.
نکته 1: میزان Timeout در این تراکنش‌ها چه قدر است؟
برای بدست آوردن مقدار Timeout در این گونه تراکنش‌ها می‌توانید از کلاس TransactionManager استفاده کنید. به صورت زیر :

var defaultTimeout = TransactionManager.DefaultTimeout
var maxTimeout = TransactionManager.MaximumTimeout
مقدار پیش فرض برای DefaultTimeout یک دقیقه است و برای MaximumTimeout ده دقیقه است. البته خاصیت‌های بالا به صورت فقط خواندنی هستند و نمی‌تونید از این راه مقدار Timeout هر تراکنش را افزایش یا کاهش دهیم. برای این کار بهتره از روش زیر استفاده کنیم.
TransactionOptions option = new TransactionOptions(); 
 option.Timeout = TimeSpan.MaxValue;

  using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required ,option) ) { scope.Complete(); }
توضیح درباره انواع TransactionScopeOption
1 - Required  : یعنی نیاز به تراکنش وجود دارد. در صورتی که تراکنش در یک تراکنش دیگر شروع شود نیاز به ساختن تراکنش جدید نیست و از همان تراکنش قبلی برای این کار استفاده می‌شود.
2 - RequiresNew: در هر صورت برای محدوده یک تراکنش تولید می‌شود.
3- Suppress : به عنوان محدوه تراکنش در نظر گرفته نمی‌شود.
using(TransactionScope scope1 = new TransactionScope())
{
     try
     {          
          using(TransactionScope scope2 = new  TransactionScope(TransactionScopeOption.Suppress))
          {
               //به دلیل استفاده از Suppress این محدوده خارج از تراکنش محسوب می‌شود
          }
          //شروع محدوده تراکنش
   }
     catch
     {}
   //Rest of scope1
}
مزایا استفاده از این روش
1-این روش از تراکنش‌های توزیع شده پشتیبانی می‌کند . یعنی می‌تونید از چند تا منبع داده استفاده کنید یا می‌تونید از یک تراکنش چند تا Connection به یک منبع داده باز کنید.(استفاده از چند تاconnection در طی یک تراکنش)
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
    string strCmd = "SQL to Execute";
    conn = new SqlClient.SqlConnection("Connection to DB1");
    conn.Open()
    objCmd = new SqlClient.SqlCommand(strCmd, conn);
    objCmd.ExecuteNonQuery();
    string strCmd2 = "SQL to Execute";
    conn2 = new SqlClient.SqlConnection("Connection to DB2");
    conn2.Open()
    objCmd2 = new SqlClient.SqlCommand(strCmd2, conn2);
    objCmd2.ExecuteNonQuery();
}
2- پیاده سازی این روش  واقعا راحت است .
3- با DataProvider‌های متفاوت نظیر Oracle و OleDb و ODBC سازگار است.
4- از تراکنش‌های تو در تو به خوبی پشتیبانی میکنه(Nested Transaction)
using(TransactionScope scope1 = new TransactionScope()) 
{ 
     using(TransactionScope scope2 = new  TransactionScope(TransactionScopeOption.Required)) 
     {
     ...
     } 

     using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) 
     {
     ...
     } 

     using(TransactionScope scope4 = new   TransactionScope(TransactionScopeOption.Suppress)) 
    {
     ...
    } 
}
5- به خوبی توسط سرویس‌های WCF پشتیبانی می‌شود و برای سیستم‌های SOA مبتنی بر WCF مناسب است.معایب :
*استفاده از این روش در سیستم هایی که تعداد کاربران آنلاین آن زیاد است و هم چنین تعداد تراکنش‌های موجود نیز در سطح سیستم خیلی زیاد باشه مناسب نیست.
*تراکنش‌های استفاده شده از این روش کند هستند.(مخصوصا که تراکنش در سطح دیتابیس با تعداد و حجم داده زیاد باشه)
امکان تغییر IsolationLevel در طی انجام یک تراکنش امکان پذیر نیست.
(به شخصه مواد * رو در سطح یک پروژه با شرایط کاربران  و حجم داده زیاد تست کردم و نتیجه مطلوب حاصل نشد)

موفق باشید.
اشتراک‌ها
نکاتی برای ساده کردن استفاده از یک رابط کاربری

چند مثال:
- پیشتر در این سایت از کلمه‌ی نامفهوم و غریبی به نام «برچسب» استفاده شده بود. عین عنوان را در قسمت برچسب مطلب کپی می‌کردند (حین ارسال لینک). از زمانیکه به «گروه» تغییر نام یافت، مشکل حل شد.
- پیام می‌دن، جستجوی سایت صفحه بندی نداره. عنوان می‌کنم اون مستطیل آبی برجسته مثل تمام قسمت‌های دیگر سایت یک دکمه است. قابل کلیک است. در کنارش علامت مراجعه به صفحه‌ی 2 هم قرار داده شد. (تصور کاربری رابط‌های تخت و مترو اینجا کلا زیر سؤال هست)
و ... 

نکاتی برای ساده کردن استفاده از یک رابط کاربری
اشتراک‌ها
یک ویرایشگر تمام عیار برای طراحان وب!

با کلیک کردن روی دکمه «Launch the Editor» یک محیط ساده و سریع برای ویرایش فایل‌های HTML، Js و Css به همراه امکان نمایش آنلاین نتایج برای شما فراهم می‌شود. نکته جالب دسترسی ساده به بسیاری از کتابخانه‌های معروف جاوااسکریپت است که به صورت خودکار به پروژه‌های شما اضافه می‌شوند. مدیریت نسخه‌های مختلف فایلها و ... 

یک ویرایشگر تمام عیار برای طراحان وب!
نظرات مطالب
استفاده از Froala WYSIWYG Editor در ASP.NET
از راهنمایی شما بسیار متشکرم. دو مورد برای من پیش اومد؛ یکی اینکه با استفاده از راهنمایی‌های شما دکمه کد اضافه شد، اما اول اینکه زمانی روی آن کلیک میکنم هیچ اتفاقی نمیافته. با دو browser چک کردم و هیچ تغییری نکرد. مطلب دوم: در local  آیکن‌ها نمایش داده می‌شوند اما روی هاست آیکن‌ها نمایش داده نمی‌شوند. در ابتدا از bundle استفاده می‌کردم اما با اینکه لینک مستقیم فایل‌ها را هم گذاشتم باز همان مشکل را داشتم. البته فایل اصلی jquery را هم به ابتدای صفحه انتقال دادم، اما باز هم تغییری نکرد.
نظرات مطالب
پلاگین جستجو با jquery و twitter bootstrap
سلام ممنون از مطلب مفیدتون. چند تا سوال از خدمتتون داشتم: 1- آیا دیگه نیاز به قراردادن input نخواهیم داشت ؟ 2- فرض کنین از این سیستم سرچ در بخشی از یک فرم استفاده کردیم و پس از کلیک روی دکمه submit نیاز داریم که به آی دی گزینه ای که توی بخش سرچ انتخاب شده ، دست پیدا کنیم ... چجوری باید به آی دی گزینه ای که توی کنترل سرچ انتخاب شده دست پیدا کنم ؟ با تشکر