تا اینجا کامپوننت صفحه بندی را به همراه اعمال آن به لیست نمایش داده شده، پیاده سازی کردیم. در ادامه میخواهیم لیست ژانرهای سینمایی را که در فایل fakeGenreService.js تعریف شدهاند:
توسط list-groupهای بوت استرپی، در کنار صفحه نمایش داده و سپس به ازای هر گروه انتخابی توسط کاربر، فیلمهای مرتبط با آن گروه را فیلتر کرده و نمایش دهیم.
بررسی ساختار کامپوننت ListGroup
شبیه به کامپوننت صفحه بندی که در قسمت قبل ایجاد کردیم، میخواهیم کامپوننت ListGroup نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\listGroup.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد میکنیم. هرچند میتوان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت میکند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import میکنیم:
پس از آن به متد رندر کامپوننت movies مراجعه کرده و با اضافه کردن یک row بوت استرپی دو ستونی، قصد داریم کامپوننت لیست فیلمها را در ستون اول این ردیف نمایش دهیم. به همین جهت المان آنرا در این محل قرار میدهیم تا بتوانیم اینترفیس ابتدایی آنرا پیش از پیاده سازی آن، طراحی کنیم.
برای این منظور ابتدا React.Fragment موجود را با یک div با "className="row جایگزین میکنیم. سپس داخل این row، دو ستون را تعریف خواهیم کرد که در اولی، المان جدید ListGroup قرار میگیرد و در دومی، مابقی عناصری که تاکنون اضافه کردهایم؛ مانند جدول، صفحه بندی و نمایش تعداد آیتمها:
این listGroup، حداقل نیاز به لیست آیتمهایی را دارد که باید نمایش دهد. این لیست نیز از fakeGenreService و متد getGenres آن تامین میشود که به صورت یک خاصیت جدید در state به نحو زیر درج خواهد شد:
همانطور که در قسمت 9 این سری نیز بررسی کردیم، اگر getGenres قرار است از سمت سرور و توسط یک درخواست Ajax ای تامین شود، محل صحیح قرارگیری آن در متد lifecycle hook ویژهای به نام componentDidMount است. اما در اینجا چون genres یک لیست درون حافظهای است، مقدار دهی فوق، مشکلی را ایجاد نمیکند. هرچند میتوان هم اکنون نیز تعریف فوق را کمی اصولیتر نوشت. برای اینکار متد componentDidMount را اضافه کرده و به نحو زیر تنظیم میکنیم:
ابتدا آرایههای مورد نیاز movies و genres را در state تعریف کرده و آنها را با یک آرایهی خالی، مقدار دهی اولیه میکنیم. از این جهت که تا رسیدن به مرحلهی componentDidMount که اندکی طول میکشد، خطاهای زمان اجرای عدم دسترسی به این آرایهها در برنامه رخ ندهد. سپس زمانیکه وهلهای از این کامپوننت در DOM رندر شد، متد componentDidMount فراخوانی شده و دو خاصیت state را با مقادیر دریافتی، به روز رسانی میکند.
پس از آن میتوان ویژگی جدید items این کامپوننت را به آرایهی genres دریافتی از state، تنظیم کرد:
در این مرحله، ورودی دیگری به نظر نمیرسد که مورد نیاز باشد. اکنون این سؤال مطرح میشود که چه رخدادهایی را قرار است از این کامپوننت دریافت کنیم یا به عبارتی خروجی آن چیست؟
بهتر است هر زمانیکه کاربر، آیتمی را از این لیست انتخاب کرد، توسط بروز رخدادی مانند onItemSelect از وقوع آن مطلع شد و سپس نسبت به آن توسط متد handleGenreSelect، واکنش نشان داد؛ مانند فیلتر کردن لیست فیلمها بر اساس آیتم انتخابی و نمایش آن. به همین جهت ویژگی onItemSelect را به تعریف المان ListGroup اضافه میکنیم:
و سپس متد handleGenreSelect متصل به آنرا به نحو زیر تعریف خواهیم کرد:
تا اینجا اینترفیس کامپوننت ListGroup را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی و خروجی آن). در مرحلهی بعد، این کامپوننت را تکمیل میکنیم.
پیاده سازی نمایش آیتمها در کامپوننت ListGroup
پیاده سازی ابتدایی کامپوننت ListGroup را در اینجا مشاهده میکنید:
کار با درج یک ul که با کلاس list-group مزین شدهاست، شروع میشود. سپس باید liهای آنرا که نمایانگر آیتمهای این لیست است، به صورت پویا با کلاسهای list-group-item رندر کرد. برای اینکار از آرایهی دریافتی this.props.items و فراخوانی متد map بر روی آن کمک میگیریم. در اینجا key هر ردیف با استفاده از خاصیت id هر آیتم و برچسب هر کدام از طریق خاصیت name هر شیء دریافتی، تامین میشود.
تا اینجا اگر برنامه را ذخیره کرده و در مرورگر نمایش دهیم، به خروجی زیر میرسیم:
البته به نظر عرض ستون آن نامناسب است. به همین جهت به کامپوننت movies مراجعه کرده و col-2 ستون آنرا به col-3 تبدیل میکنیم.
پویا سازی انتخاب نام خواص شیء دریافتی، در کامپوننت ListGroup
در حال حاضر پیاده سازی کامپوننت ListGroup، به شیءای دقیقا با خواص id_ و name وابستهاست و اگر شیء دیگری را که دارای خواصی معادل این نامها نیست، به آن ارسال کنیم، دیگر کار نخواهد کرد. به همین جهت در محل تعریف المان این کامپوننت در کامپوننت movies، دو ویژگی دیگر نام خواص شیء مدنظر را تنظیم میکنیم تا بتوانیم با هر نوع شیءای در اینجا کار کنیم:
پس از این تغییر و افزودن textProperty و valueProperty، برای پویا سازی نامهای خواص دریافتی در کامپوننت ListGroup، از روش کار با []، جهت دسترسی پویای به خواص یک شیء، استفاده میکنیم تا دیگر این کامپوننت به شیء خاص genre، وابستگی نداشته باشد و قابلیت استفادهی مجدد از آن افزایش یابد:
تعیین مقادیر پیشفرضی برای خواص props
با زیاد شدن تعداد خواص props، اینترفیس کامپوننتها پیچیدهتر میشوند. در یک چنین حالتی میتوان در کامپوننتها defaultProps را تعریف کرد و توسط آن مقادیر پیشفرضی را برای خواص props درنظر گرفت. به این صورت در حین تعریف المان این کامپوننت، اگر مقادیر مدنظر با مقادیر پیشفرض تعیین شده یکی باشند، دیگر نیازی به ذکر این پارامترها نخواهد بود. برای مثال در انتهای کامپوننت ListGroup، خاصیت جدید defaultProps را تعریف میکنیم (املای آن باید دقیقا به همین شکل باشد؛ و گرنه شناخته نخواهد شد). سپس در اینجا خواصی را که میخواهیم مقادیر پیشفرضی را برای آنها تعیین کنیم، ذکر خواهیم کرد:
برای نمونه در اینجا دو خاصیت جدید textProperty و valueProperty را به همان مقادیر name و id_ مورد استفادهی در این مثال تنظیم کردهایم. پس از این تعریف، میتوان به کامپوننت movies که از این ویژگیها استفاده میکند مراجعه کرده و آنهایی را که با defaultProps تطابق دارند، از لیست ویژگیهای ذکر شده حذف کرد؛ یعنی تعریف المان ListGroup به صورت زیر ساده میشود:
بدیهی است اگر در آینده با اشیاء دیگری سر و کار داشتیم، میتوان مجددا این خواص پیشفرض را بر اساس ساختار این اشیاء، مقدار دهی و تعیین کرد.
مدیریت انتخاب گروههای فیلمها
در ادامه میخواهیم رخداد onClick بر روی هر li این لیست را مدیریت کنیم و سبب بروز رخدادی به نام onItemSelect شویم که در ابتدای بحث، آنرا به عنوان خروجی این کامپوننت تعریف کردیم. این رخداد نیز در کامپوننت movies به متد handleGenreSelect متصل است. به همین جهت تعریف ویژگی onClick را که سبب انتقال شیء جاری رندر شده، توسط رویداد onItemSelect به خارج از آن میشود، به المان li کامپوننت ListGroup اضافه میکنیم:
پس از این تغییرات و ذخیرهی برنامه، اگر به خروجی برنامه در مرورگر مراجعه کرده و بر روی هر کدام از آیتمهای لیست گروههای فیلمها کلیک کنیم، شیء مرتبط با آن آیتم در کنسول توسعه دهندههای مرورگر، لاگ میشود که نشان از برقراری صحیح ارتباطات این قسمت را دارد.
پس از فعالسازی امکان کلیک بر روی هر آیتم لیست رندر شده، اکنون میخواهیم با انتخاب هر گروه، این گروه در این لیست، به صورت انتخاب شده، همانند شماره صفحهی انتخاب شدهی در کامپوننت صفحه بندی، تغییر رنگ دهد و متمایز نمایش داده شود تا مشخص باشد که هم اکنون با کدام آیتم در حال کار هستیم. برای اینکار تنها کافی است کلاس active را به صورت پویا به className هر li، اضافه یا کم کنیم. البته برای این منظور این کامپوننت باید از آیتم انتخاب شده مطلع باشد؛ به همین جهت selectedItem را در لیست ویژگیهای اینترفیس تعریف این المان اضافه میکنیم. برای اینکار ابتدا selectedGenre را با هربار فراخوانی handleGenreSelect که به onItemSelect کامپوننت متصل است، با فراخوانی متد setState به روز رسانی میکنیم:
در یک چنین حالتی الزامی به تعریف selectedGenre در خاصیت state ابتدای کامپوننت نیست. چون با فراخوانی متد setState اگر یکی از خواص منتسب به شیء state به روز شده باشد، آن خاصیت نیز به روز میشود و یا اگر این خاصیت جدید باشد، با state موجود یکی خواهد شد؛ هرچند آنرا به صورت زیر نیز میتوان تعریف کرد که با یک شیء خالی مقدار دهی شدهاست:
سپس ویژگی selectedItem کامپوننت را به این مقدار تغییر یافتهی this.state.selectedGenre تنظیم میکنیم تا با هر بار فراخوانی setState که سبب رندر مجدد کامپوننت Movies در DOM مجازی React میشود، کامپوننت از selectedItem تغییر یافته مطلع شده و با افزودن کلاس active به آن آیتم، واکنش نشان دهد:
اکنون به کامپوننت ListGroup مراجعه کرده و بر اساس ویژگی جدید selectedItem، تغییرات زیر را به className اعمال میکنیم:
در اینجا اگر item در حال رندر با this.props.selectedItem دریافتی یکی باشد، کلاس active به کلاس list-group-item اضافه میشود و برعکس.
مدیریت فیلتر کردن اطلاعات گروه فیلم انتخابی
در قسمت قبل، در ابتدای متد رندر کامپوننت movies، از متد paginate برای صفحه بندی اطلاعات استفاده کردیم. فیلتر گروه جاری انتخاب شده را باید پیش از این متد قرار دارد؛ چون تعداد صفحات و اطلاعات نمایش داده شدهی در هر کدام باید بر اساس لیست فیلمهای فیلتر شده باشد.
برای انجام اینکار تغییرات زیر را اعمال خواهیم کرد:
الف) بجای متد paginate، از متد getPagedData زیر استفاده میکنیم:
- در اینجا بجای اینکه مدام this.statها را جهت دریافت خواص آن تکرار کنیم، با استفاده از ویژگی Object Destructuring، خواصی را که نیاز داریم یکبار انتخاب کرده و سپس به دفعات از آنها استفاده میکنیم. به همین جهت در این قطعه کد، فقط یکبار this.state را مشاهده میکنید که بسیار تمیزتر است و همچنین کارآیی آن نیز به علت عدم انتخاب مداوم مقدار خاصیتی از یک شیء، بالاتر از حالت قبل است.
- در حین Object Destructuring، نام خاصیت movies را نیز به allMovies تغییر دادهایم تا واضحتر باشد.
- در ادامه با استفاده از متد filter جاوااسکریپت، بر اساس id هر گروه انتخاب شده، اشیاء مرتبط با آن، از allMovies جدا شده و بازگشت داده میشود. البته اگر id هم انتخاب نشده باشد (اولین بار نمایش صفحه)، تمام رکوردها یعنی allMovies، مورد استفاده قرار میگیرد.
- پس از آن، همان کدهای صفحه بندی اطلاعات را که در قسمت قبل بررسی کردیم، مشاهده میکنید که اینبار بجای allMovies قسمت قبل، بر روی filteredMovies اعمال شدهاست.
- در آخر، این متد، یک شیء را با دو خاصیت که بیانگر تعداد کل رکوردهای انتخاب شده و دادههای فیلتر شدهی صفحه بندی شدهاست، بازگشت میدهد.
ب) تغییرات متد رندر کامپوننت movies به صورت زیر است:
- ابتدا متد getPagedData فوق، فراخوانی شده و شیء دریافتی از آن با استفاده از ویژگی Object Destructuring، به دو خاصیت totalCount و movies انتساب داده میشود:
- از آرایهی movies، در قسمت قبل برای رندر لیست فیلمها استفاده شد. به همین جهت در اینجا تغییر نام data به movies را مشاهده میکنید.
- همچنین کامپوننت صفحه بندی، اینبار باید totalCount آیتمهای فیلتر شده را نمایش دهد و نه totalCount تمام فیلمهای موجود را:
در اینجا برچسب نمایش تعداد آیتمهای موجود نیز باید تغییر کند:
ج) ممکن است در اولین بار مشاهدهی صفحه، کاربر صفحهی شمارهی 3 را انتخاب کند که سبب تغییر currentPage موجود در state، به عدد 3 میشود. اکنون اگر کاربر نمایش فیلتر شدهی فیلمهای یک گروه خاص را انتخاب کند، باید این شماره، به عدد 1 مجددا تنظیم شود:
افزودن گزینهی نمایش تمام اطلاعات به لیست گروههای فیلمها
در ادامه قصد داریم به بالای لیست گروههای موجود، گزینهی All Genres را نیز اضافه کنیم تا با کلیک بر روی آن، مجددا بتوان لیست تمام فیلمهای موجود را مشاهده کرد.
برای این منظور در جائیکه لیست getGenres را دریافت و نمایش میدهیم، یعنی متد componentDidMount، اندکی تغییر ایجاد کرده و یک آرایهی جدید را ایجاد میکنیم؛ بطوریکه اولین عنصر آن، گزینهی جدید All Genres باشد و سپس توسط spread operator، مابقی عناصر آرایهی گروهها را به این آرایهی جدید اضافه میکنیم:
همین اندازه تغییر برای فعالسازی این گزینه کفایت میکند؛ از این جهت که در متد getPagedData، ابتدا بررسی میشود که اگر آیتمی انتخاب شده بود و همچنین دارای id نیز بود، آنگاه کار فیلتر کردن صورت گیرد، درغیراینصورت، تمام رکوردها را بازگشت دهد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-12.zip
export const genres = [ { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" }, { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" }, { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" } ]; export function getGenres() { return genres.filter(g => g); }
بررسی ساختار کامپوننت ListGroup
شبیه به کامپوننت صفحه بندی که در قسمت قبل ایجاد کردیم، میخواهیم کامپوننت ListGroup نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\listGroup.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد میکنیم. هرچند میتوان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت میکند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import میکنیم:
import ListGroup from "./common/listGroup";
برای این منظور ابتدا React.Fragment موجود را با یک div با "className="row جایگزین میکنیم. سپس داخل این row، دو ستون را تعریف خواهیم کرد که در اولی، المان جدید ListGroup قرار میگیرد و در دومی، مابقی عناصری که تاکنون اضافه کردهایم؛ مانند جدول، صفحه بندی و نمایش تعداد آیتمها:
return ( <div className="row"> <div className="col-2"> <ListGroup /> </div> <div className="col"> ... </div> </div> );
import { getGenres } from "../services/fakeGenreService"; // ... class Movies extends Component { state = { // ... genres: getGenres() };
class Movies extends Component { state = { movies: [], pageSize: 4, currentPage: 1, genres: [] }; componentDidMount() { this.setState({ movies: getMovies(), genres: getGenres() }); }
پس از آن میتوان ویژگی جدید items این کامپوننت را به آرایهی genres دریافتی از state، تنظیم کرد:
<ListGroup items={this.state.genres} />
بهتر است هر زمانیکه کاربر، آیتمی را از این لیست انتخاب کرد، توسط بروز رخدادی مانند onItemSelect از وقوع آن مطلع شد و سپس نسبت به آن توسط متد handleGenreSelect، واکنش نشان داد؛ مانند فیلتر کردن لیست فیلمها بر اساس آیتم انتخابی و نمایش آن. به همین جهت ویژگی onItemSelect را به تعریف المان ListGroup اضافه میکنیم:
<ListGroup items={this.state.genres} onItemSelect={this.handleGenreSelect} />
handleGenreSelect = genre => { console.log("handleGenreSelect", genre); };
پیاده سازی نمایش آیتمها در کامپوننت ListGroup
پیاده سازی ابتدایی کامپوننت ListGroup را در اینجا مشاهده میکنید:
import React, { Component } from "react"; class ListGroup extends Component { render() { return ( <ul className="list-group"> {this.props.items.map(item => ( <li key={item._id} className="list-group-item"> {item.name} </li> ))} </ul> ); } } export default ListGroup;
تا اینجا اگر برنامه را ذخیره کرده و در مرورگر نمایش دهیم، به خروجی زیر میرسیم:
البته به نظر عرض ستون آن نامناسب است. به همین جهت به کامپوننت movies مراجعه کرده و col-2 ستون آنرا به col-3 تبدیل میکنیم.
پویا سازی انتخاب نام خواص شیء دریافتی، در کامپوننت ListGroup
در حال حاضر پیاده سازی کامپوننت ListGroup، به شیءای دقیقا با خواص id_ و name وابستهاست و اگر شیء دیگری را که دارای خواصی معادل این نامها نیست، به آن ارسال کنیم، دیگر کار نخواهد کرد. به همین جهت در محل تعریف المان این کامپوننت در کامپوننت movies، دو ویژگی دیگر نام خواص شیء مدنظر را تنظیم میکنیم تا بتوانیم با هر نوع شیءای در اینجا کار کنیم:
<ListGroup items={this.state.genres} textProperty="name" valueProperty="_id" onItemSelect={this.handleGenreSelect} />
import React, { Component } from "react"; class ListGroup extends Component { render() { return ( <ul className="list-group"> {this.props.items.map(item => ( <li key={item[this.props.valueProperty]} className="list-group-item"> {item[this.props.textProperty]} </li> ))} </ul> ); } } export default ListGroup;
تعیین مقادیر پیشفرضی برای خواص props
با زیاد شدن تعداد خواص props، اینترفیس کامپوننتها پیچیدهتر میشوند. در یک چنین حالتی میتوان در کامپوننتها defaultProps را تعریف کرد و توسط آن مقادیر پیشفرضی را برای خواص props درنظر گرفت. به این صورت در حین تعریف المان این کامپوننت، اگر مقادیر مدنظر با مقادیر پیشفرض تعیین شده یکی باشند، دیگر نیازی به ذکر این پارامترها نخواهد بود. برای مثال در انتهای کامپوننت ListGroup، خاصیت جدید defaultProps را تعریف میکنیم (املای آن باید دقیقا به همین شکل باشد؛ و گرنه شناخته نخواهد شد). سپس در اینجا خواصی را که میخواهیم مقادیر پیشفرضی را برای آنها تعیین کنیم، ذکر خواهیم کرد:
ListGroup.defaultProps = { textProperty: "name", valueProperty: "_id" }; export default ListGroup;
<ListGroup items={this.state.genres} onItemSelect={this.handleGenreSelect} />
مدیریت انتخاب گروههای فیلمها
در ادامه میخواهیم رخداد onClick بر روی هر li این لیست را مدیریت کنیم و سبب بروز رخدادی به نام onItemSelect شویم که در ابتدای بحث، آنرا به عنوان خروجی این کامپوننت تعریف کردیم. این رخداد نیز در کامپوننت movies به متد handleGenreSelect متصل است. به همین جهت تعریف ویژگی onClick را که سبب انتقال شیء جاری رندر شده، توسط رویداد onItemSelect به خارج از آن میشود، به المان li کامپوننت ListGroup اضافه میکنیم:
<li key={item[this.props.valueProperty]} className="list-group-item" onClick={() => this.props.onItemSelect(item)} style={{ cursor: "pointer" }} > {item[this.props.textProperty]} </li>
پس از فعالسازی امکان کلیک بر روی هر آیتم لیست رندر شده، اکنون میخواهیم با انتخاب هر گروه، این گروه در این لیست، به صورت انتخاب شده، همانند شماره صفحهی انتخاب شدهی در کامپوننت صفحه بندی، تغییر رنگ دهد و متمایز نمایش داده شود تا مشخص باشد که هم اکنون با کدام آیتم در حال کار هستیم. برای اینکار تنها کافی است کلاس active را به صورت پویا به className هر li، اضافه یا کم کنیم. البته برای این منظور این کامپوننت باید از آیتم انتخاب شده مطلع باشد؛ به همین جهت selectedItem را در لیست ویژگیهای اینترفیس تعریف این المان اضافه میکنیم. برای اینکار ابتدا selectedGenre را با هربار فراخوانی handleGenreSelect که به onItemSelect کامپوننت متصل است، با فراخوانی متد setState به روز رسانی میکنیم:
handleGenreSelect = genre => { console.log("handleGenreSelect", genre); this.setState({selectedGenre: genre}); };
class Movies extends Component { state = { // ... selectedGenre: {} };
<ListGroup items={this.state.genres} onItemSelect={this.handleGenreSelect} selectedItem={this.state.selectedGenre} />
<li key={item[this.props.valueProperty]} className={ item === this.props.selectedItem ? "list-group-item active" : "list-group-item" } style={{ cursor: "pointer" }} onClick={() => this.props.onItemSelect(item)} > {item[this.props.textProperty]} </li>
مدیریت فیلتر کردن اطلاعات گروه فیلم انتخابی
در قسمت قبل، در ابتدای متد رندر کامپوننت movies، از متد paginate برای صفحه بندی اطلاعات استفاده کردیم. فیلتر گروه جاری انتخاب شده را باید پیش از این متد قرار دارد؛ چون تعداد صفحات و اطلاعات نمایش داده شدهی در هر کدام باید بر اساس لیست فیلمهای فیلتر شده باشد.
برای انجام اینکار تغییرات زیر را اعمال خواهیم کرد:
الف) بجای متد paginate، از متد getPagedData زیر استفاده میکنیم:
getPagedData() { const { pageSize, currentPage, selectedGenre, movies: allMovies } = this.state; const filteredMovies = selectedGenre && selectedGenre._id ? allMovies.filter(m => m.genre._id === selectedGenre._id) : allMovies; const first = (currentPage - 1) * pageSize; const last = first + pageSize; const pagedMovies = filteredMovies.slice(first, last); return { totalCount: filteredMovies.length, data: pagedMovies }; }
- در حین Object Destructuring، نام خاصیت movies را نیز به allMovies تغییر دادهایم تا واضحتر باشد.
- در ادامه با استفاده از متد filter جاوااسکریپت، بر اساس id هر گروه انتخاب شده، اشیاء مرتبط با آن، از allMovies جدا شده و بازگشت داده میشود. البته اگر id هم انتخاب نشده باشد (اولین بار نمایش صفحه)، تمام رکوردها یعنی allMovies، مورد استفاده قرار میگیرد.
- پس از آن، همان کدهای صفحه بندی اطلاعات را که در قسمت قبل بررسی کردیم، مشاهده میکنید که اینبار بجای allMovies قسمت قبل، بر روی filteredMovies اعمال شدهاست.
- در آخر، این متد، یک شیء را با دو خاصیت که بیانگر تعداد کل رکوردهای انتخاب شده و دادههای فیلتر شدهی صفحه بندی شدهاست، بازگشت میدهد.
ب) تغییرات متد رندر کامپوننت movies به صورت زیر است:
- ابتدا متد getPagedData فوق، فراخوانی شده و شیء دریافتی از آن با استفاده از ویژگی Object Destructuring، به دو خاصیت totalCount و movies انتساب داده میشود:
render() { const { length: count } = this.state.movies; if (count === 0) return <p>There are no movies in the database.</p>; const { totalCount, data: movies } = this.getPagedData();
- همچنین کامپوننت صفحه بندی، اینبار باید totalCount آیتمهای فیلتر شده را نمایش دهد و نه totalCount تمام فیلمهای موجود را:
<Pagination itemsCount={totalCount}
<p>Showing {totalCount} movies in the database.</p>
handleGenreSelect = genre => { console.log("handleGenreSelect", genre); this.setState({ selectedGenre: genre, currentPage: 1 }); };
افزودن گزینهی نمایش تمام اطلاعات به لیست گروههای فیلمها
در ادامه قصد داریم به بالای لیست گروههای موجود، گزینهی All Genres را نیز اضافه کنیم تا با کلیک بر روی آن، مجددا بتوان لیست تمام فیلمهای موجود را مشاهده کرد.
برای این منظور در جائیکه لیست getGenres را دریافت و نمایش میدهیم، یعنی متد componentDidMount، اندکی تغییر ایجاد کرده و یک آرایهی جدید را ایجاد میکنیم؛ بطوریکه اولین عنصر آن، گزینهی جدید All Genres باشد و سپس توسط spread operator، مابقی عناصر آرایهی گروهها را به این آرایهی جدید اضافه میکنیم:
componentDidMount() { const genres = [{ _id: "", name: "All Genres" }, ...getGenres()]; this.setState({ movies: getMovies(), genres }); }
const filteredMovies = selectedGenre && selectedGenre._id ? allMovies.filter(m => m.genre._id === selectedGenre._id) : allMovies;
در کد فوق که از T4MVC استفاده نشده ولی نحوه تعریف Url شما اشتباه است. قبلا هم در مورد نحوه تعریف صحیح Url در ASP.NET MVC مطلب نوشتم. حتما باید از Url.Action استفاده کنید.
برای اینکه بتونید از Url.Action در اسکریپتهای خودتون استفاده کنید پایین View جاری یک section به نام مثلا JavaScript درست کنید. در اینجا میشود داخل کدهای جاوا اسکریپتی هم از Razor استفاده کرد. سپس این section را در layout در قسمت header صفحه include کنید (در قسمت 14 سری MVC این سایت در مورد این تکنیک کاملا توضیح دادم).
برای اینکه بتونید از Url.Action در اسکریپتهای خودتون استفاده کنید پایین View جاری یک section به نام مثلا JavaScript درست کنید. در اینجا میشود داخل کدهای جاوا اسکریپتی هم از Razor استفاده کرد. سپس این section را در layout در قسمت header صفحه include کنید (در قسمت 14 سری MVC این سایت در مورد این تکنیک کاملا توضیح دادم).
مطالب
ASP.NET MVC #12
تولید خودکار فرمهای ورود و نمایش اطلاعات در ASP.NET MVC بر اساس اطلاعات مدلها
در الگوی MVC، قسمت M یا مدل آن یک سری ویژگیهای خاص خودش را دارد:
شما را وادار نمیکند که مدل را به نحو خاصی طراحی کنید. شما را مجبور نمیکند که کلاسهای مدل را برای نمونه همانند کلاسهای کنترلرها، از کلاس خاصی به ارث ببرید. یا حتی در مورد نحوهی دسترسی به دادهها نیز، نظری ندارد. به عبارتی برنامه نویس است که میتواند بر اساس امکانات مهیای در کل اکوسیستم دات نت، در این مورد آزادانه تصمیم گیری کند.
بر همین اساس ASP.NET MVC یک سری قرارداد را برای سهولت اعتبار سنجی یا تولید بهتر رابط کاربری بر اساس اطلاعات مدلها، فراهم آورده است. این قراردادها هم چیزی نیستند جز یک سری metadata که نحوهی دربرگیری اطلاعات را در مدلها توضیح میدهند. برای دسترسی به آنها پروژه جاری باید ارجاعی را به اسمبلیهای System.ComponentModel.DataAnnotations.dll و System.Web.Mvc.dll داشته باشد (که VS.NET به صورت خودکار در ابتدای ایجاد پروژه اینکار را انجام میدهد).
یک مثال کاربردی
یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. در پوشه مدلهای آن، مدل اولیهای را با محتوای زیر ایجاد نمائید:
using System;
namespace MvcApplication8.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
public decimal Salary { set; get; }
public string Address { set; get; }
public bool IsMale { set; get; }
public DateTime AddDate { set; get; }
}
}
سپس یک کنترلر جدید را هم به نام EmployeeController با محتوای زیر به پروژه اضافه نمائید:
using System;
using System.Web.Mvc;
using MvcApplication8.Models;
namespace MvcApplication8.Controllers
{
public class EmployeeController : Controller
{
public ActionResult Create()
{
var employee = new Employee { AddDate = DateTime.Now };
return View(employee);
}
}
}
بر روی متد Create کلیک راست کرده و یک View ساده را برای آن ایجاد نمائید. سپس محتوای این View را به صورت زیر تغییر دهید:
@model MvcApplication8.Models.Employee
@{
ViewBag.Title = "Create";
}
<h2>Create An Employee</h2>
@using (Html.BeginForm(actionName: "Create", controllerName: "Employee"))
{
@Html.EditorForModel()
<input type="submit" value="Save" />
}
اکنون اگر پروژه را اجرا کرده و مسیر http://localhost/employee/create را وارد نمائید، یک صفحه ورود اطلاعات تولید شده به صورت خودکار را مشاهده خواهید کرد. متد Html.EditorForModel بر اساس اطلاعات خواص عمومی مدل، یک فرم خودکار را تشکیل میدهد.
البته فرم تولیدی به این شکل شاید آنچنان مطلوب نباشد، از این جهت که برای مثال Id را هم لحاظ کرده، در صورتیکه قرار است این Id توسط بانک اطلاعاتی انتساب داده شود و نیازی نیست تا کاربر آنرا وارد نماید. یا مثلا برچسب AddDate نباید به این شکل صرفا بر اساس نام خاصیت متناظر با آن تولید شود و مواردی از این دست. به عبارتی نیاز به سفارشی سازی کار این فرم ساز توکار ASP.NET MVC وجود دارد که ادامه بحث جاری را تشکیل خواهد داد.
سفارشی سازی فرم ساز توکار ASP.NET MVC با کمک Metadata خواص
برای اینکه بتوان نحوه نمایش فرم خودکار تولید شده را سفارشی کرد، میتوان از یک سری attribute و data annotations توکار دات نت و ASP.NET MVC استفاده کرد و نهایتا این metadata توسط فریم ورک، مورد استفاده قرار خواهند گرفت. برای مثال:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace MvcApplication8.Models
{
public class Employee
{
//[ScaffoldColumn(false)]
[HiddenInput(DisplayValue=false)]
public int Id { set; get; }
public string Name { set; get; }
[DisplayName("Annual Salary ($)")]
public decimal Salary { set; get; }
public string Address { set; get; }
[DisplayName("Is Male?")]
public bool IsMale { set; get; }
[DisplayName("Start Date")]
[DataType(DataType.Date)]
public DateTime AddDate { set; get; }
}
}
در اینجا به کمک ویژگی HiddenInput از نمایش عمومی خاصیت Id جلوگیری خواهیم کرد یا توسط ویژگی DisplayName، برچسب دلخواه خود را به عناصر فرم تشکیل شده، انتساب خواهیم داد. اگر نیاز باشد تا خاصیتی کلا از رابط کاربری حذف شود میتوان از ویژگی ScaffoldColumn با مقدار false استفاده کرد. یا توسط DataType، مشخص کردهایم که نوع ورودی فقط قرار است Date باشد و نیازی به قسمت Time آن نداریم.
DataType شامل نوعهای از پیش تعریف شده دیگری نیز هست. برای مثال اگر نیاز به نمایش TextArea بود از مقدار MultilineText، استفاده کنید:
[DataType(DataType.MultilineText)]
یا برای نمایش PasswordBox از مقدار Password میتوان کمک گرفت. اگر نیاز دارید تا آدرس ایمیلی به شکل یک لینک mailto نمایش داده شود از مقدار EmailAddress استفاده کنید. به کمک مقدار Url، متن خروجی به صورت خودکار تبدیل به یک آدرس قابل کلیک خواهد شد.
اکنون اگر پروژه را مجددا کامپایل کنیم و به آدرس ایجاد یک کارمند جدید مراجعه نمائیم، با رابط کاربری بهتری مواجه خواهیم شد.
سفارشی سازی ظاهر فرم ساز توکار ASP.NET MVC
در ادامه اگر بخواهیم ظاهر این فرم را اندکی سفارشیتر کنیم، بهتر است به سورس صفحه تولیدی در مرورگر مراجعه کنیم. در اینجا یک سری عناصر HTML محصور شده با div را خواهیم یافت. هر کدام از اینها هم با classهای css خاص خود تعریف شدهاند. بنابراین اگر علاقمند باشیم که رنگ و قلم و غیره این موارد تغییر دهیم، تنها کافی است فایل css برنامه را ویرایش کنیم و نیازی به دستکاری مستقیم کدهای برنامه نیست.
انتساب قالبهای سفارشی به خواص یک شیء
تا اینجا در مورد نحوه سفارشی سازی رنگ، قلم، برچسب و نوع دادههای هر کدام از عناصر نهایی نمایش داده شده، توضیحاتی را ملاحظه نمودید.
در فرم تولیدی نهایی، خاصیت bool تعریف شده به صورت خودکار به یک checkbox تبدیل شده است. چقدر خوب میشد اگر امکان تبدیل آن مثلا به RadioButton انتخاب مرد یا زن بودن کارمند ثبت شده در سیستم وجود داشت. برای اصلاح یا تغییر این مورد، باز هم میتوان از متادیتای خواص، جهت تعریف قالبی خاص برای هر کدام از خواص مدل استفاده کرد.
به پوشه Views/Shared مراجعه کرده و یک پوشه جدید به نام EditorTemplates را ایجاد نمائید. بر روی این پوشه کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه باز شده، گزینه «Create as a partial view» را انتخاب نمائید و نام آنرا هم مثلا GenderOptions وارد کنید. همچنین گزینه «Create a strongly typed view» را نیز انتخاب کنید. مقدار Model class را مساوی bool وارد نمائید. فعلا یک hello داخل این صفحه جدید وارد کرده و سپس خاصیت IsMale را به نحو زیر تغییر دهید:
[DisplayName("Gender")]
[UIHint("GenderOptions")]
public bool IsMale { set; get; }
توسط ویژگی UIHint، میتوان یک خاصیت را به یک partial view متصل کرد. در اینجا خاصیت IsMale به partial view ایی به نام GenderOptions متصل شده است. اکنون اگر برنامه را کامپایل و اجرا کرده و آدرس ایجاد یک کارمند جدید را ملاحظه کنید، بجای Checkbox باید یک hello نمایش داده شود.
محتویات این Partial view هم نهایتا به شکل زیر خواهند بود:
@model bool
<p>@Html.RadioButton("", false, !Model) Female</p>
<p>@Html.RadioButton("", true, Model) Male</p>
در اینجا Model که از نوع bool تعریف شده، به خاصیت IsMale اشاره خواهد کرد. دو RadioButton هم برای انتخاب بین حالت زن و مرد تعریف شدهاند.
یا یک مثال جالب دیگر در این زمینه میتواند تبدیل enum به یک Dropdownlist باشد. در این حالت partial view ما شکل زیر را خواهد یافت:
@model Enum
@Html.DropDownListFor(m => m, Enum.GetValues(Model.GetType())
.Cast<Enum>()
.Select(m => {
string enumVal = Enum.GetName(Model.GetType(), m);
return new SelectListItem() {
Selected = (Model.ToString() == enumVal),
Text = enumVal,
Value = enumVal
};
}))
و برای استفاده از آن، از ویژگی زیر میتوان کمک گرفت (مزین کردن خاصیتی از نوع یک enum دلخواه، جهت تبدیل خودکار آن به یک دراپ داون لیست):
[UIHint("Enum")]
سایر متدهای کمکی تولید و نمایش خودکار اطلاعات از روی اطلاعات مدلهای برنامه
متدهای دیگری نیز در ردهی Templated helpers قرار میگیرند. اگر از متد Html.EditorFor استفاده کنیم، از تمام این اطلاعات متادیتای تعریف شده نیز استفاده خواهد کرد. همانطور که در قسمت قبل (قسمت 11) نیز توضیح داده شد، صفحه استاندارد Add view در VS.NET به همراه یک سری قالب تولید فرمهای Create و Edit هم هست که دقیقا کد نهایی تولیدی را بر اساس همین متد تولید میکند.
استفاده از Html.EditorFor انعطاف پذیری بیشتری را به همراه دارد. برای مثال اگر یک طراح وب، طرح ویژهای را در مورد ظاهر فرمهای سایت به شما ارائه دهد، بهتر است از این روش استفاده کنید. اما خروجی نهایی Html.EditorForModel به کمک تعدادی متادیتا و اندکی دستکاری CSS، از دیدگاه یک برنامه نویس بی نقص است!
به علاوه، متد Html.DisplayForModel نیز مهیا است. بجای اینکه کار تولید رابط کاربری اطلاعات نمایش جزئیات یک شیء را انجام دهید، اجازه دهید تا متد Html.DisplayForModel اینکار را انجام دهد. سفارشی سازی آن نیز همانند قبل است و بر اساس متادیتای خواص انجام میشود. در این حالت، مسیر پیش فرض جستجوی قالبهای UIHint آن، Views/Shared/DisplayTemplates میباشد. همچنین Html.DisplayFor نیز جهت کار با یک خاصیت مدل تدارک دیده شده است. البته باید درنظر داشت که استفاده از پوشه Views/Shared اجباری نیست. برای مثال اگر از پوشه Views/Home/DisplayTemplates استفاده کنیم، قالبهای سفارشی تهیه شده تنها جهت Viewهای کنترلر home قابل استفاده خواهند بود.
یکی دیگر از ویژگیهایی که جهت سفارشی سازی نحوه نمایش خودکار اطلاعات میتواند مورد استفاده قرار گیرد، DisplayFormat است. برای مثال اگر مقدار خاصیت در حال نمایش نال بود، میتوان مقدار دیگری را نمایش داد:
[DisplayFormat(NullDisplayText = "-")]
یا اگر علاقمند بودیم که فرمت اطلاعات در حال نمایش را تغییر دهیم، به نحو زیر میتوان عمل کرد:
[DisplayFormat(DataFormatString = "{0:n}")]
مقدار DataFormatString در پشت صحنه در متد string.Format مورد استفاده قرار میگیرد.
و اگر بخواهیم که این ویژگی در حالت تولید فرم ویرایش نیز درنظر گرفته شود، میتوان خاصیت ApplyFormatInEditMode را نیز مقدار دهی کرد:
[DisplayFormat(DataFormatString = "{0:n}", ApplyFormatInEditMode = true)]
بازنویسی قالبهای پیش فرض تولید فرم یا نمایش اطلاعات خودکار ASP.NET MVC
یکی دیگر از قرارداهای بکارگرفته شده در حین استفاده از قالبهای سفارشی، استفاده از نام اشیاء میباشد. مثلا در پوشه Views/Shared/DisplayTemplates، اگر یک Partial view به نام String.cshtml وجود داشته باشد، از این پس نحوه رندر کلیه خواص رشتهای تمام مدلها، بر اساس محتوای فایل String.cshtml مشخص میشود؛ به همین ترتیب در مورد datetime و سایر انواع مهیا.
برای مثال اگر خواستید تمام تاریخهای میلادی دریافتی از بانک اطلاعاتی را شمسی نمایش دهید، فقط کافی است یک فایل datetime.cshtml سفارشی را تولید کنید که Model آن تاریخ میلادی دریافتی است و نهایتا کار این Partial view، رندر تاریخ تبدیل شده به همراه تگهای سفارشی مورد نظر میباشد. در این حالت نیازی به ذکر ویژگی UIHint نیز نخواهد بود و همه چیز خودکار است.
به همین ترتیب اگر نام مدل ما Employee باشد و فایل Partial view ایی به نام Employee.cshtml در پوشه Views/Shared/DisplayTemplates قرار گیرد، متد Html.DisplayForModel به صورت پیش فرض از محتوای این فایل جهت رندر اطلاعات نمایش جزئیات شیء Employee استفاده خواهد کرد.
داخل Partial viewهای سفارشی تعریف شده به کمک خاصیت ViewData.TemplateInfo.FormattedModelValue مقدار نهایی فرمت شده قابل استفاده را فراهم میکند. این مورد هم از این جهت حائز اهمیت است که نیازی نباشد تا ویژگی DisplayFormat را به صورت دستی پردازش کنیم. همچنین اطلاعات ViewData.ModelMetadata نیز دراینجا قابل دسترسی هستند.
سؤال: Partial View چیست؟
همانطور که از نام Partial view برمیآید، هدف آن رندر کردن قسمتی از صفحه است به همراه استفاده مجدد از کدهای تولید رابط کاربری در چندین و چند View؛ چیزی شبیه به User controls در ASP.NET Web forms البته با این تفاوت که Page life cycle و Code behind و سایر موارد مشابه آن در اینجا حذف شدهاند. همچنین از Partial viewها برای به روز رسانی قسمتی از صفحه حین فراخوانیهای Ajaxایی نیز استفاده میشود. مهمترین کاربرد Partial views علاوه بر استفاده مجدد از کدها، خلوت کردن Viewهای شلوغ است جهت سادهتر سازی نگهداری آنها در طول زمان (یک نوع Refactoring فایلهای View محسوب میشوند).
پسوند این فایلها نیز بسته به موتور View مورد استفاده تعیین میشود. برای مثال حین استفاده از Razor، پسوند Partial views همان cshtml یا vbhtml میباشد. یا اگر از web forms view engine استفاده شود، پسوند آنها ascx است (همانند User controls در وب فرمها).
البته چون در حالت استفاده از موتور Razor، پسوند View و Partial viewها یکی است، مرسوم شده است که نام Partial viewها را با یک underline شروع کنیم تا بتوان بین این دو تمایز قائل شد.
اگر این فایلها را در پوشه Views/Shared تعریف کنیم، در تمام Viewها قابل استفاده خواهند بود. اما اگر مثلا در پوشه Views/Home آنهارا قرار دهیم، تنها در Viewهای متعلق به کنترلر Home، قابل بکارگیری میباشند.
Partial views را نیز میتوان strongly typed تعریف کرد و به این ترتیب با مشخص سازی دقیق نوع model آن، علاوه بر بهرهمندی از Intellisense خودکار، رندر آنرا نیز تحت کنترل کامپایلر قرار داد.
مقدار Model در یک View بر اساس اطلاعات مدلی که به آن ارسال شده است تعیین میگردد. اما در یک Partial view که جزئی از یک View را نهایتا تشکیل خواهد داد، بر اساس مقدار ارسالی از طریق View معین میگردد.
یک مثال
در ادامه قصد داریم کد حلقه نمایش لیستی از عناصر تولید شده توسط VS.NET را به یک Partial view منتقل و Refactor کنیم.
ابتدا یک منبع داده فرضی زیر را در نظر بگیرید:
using System;
using System.Collections.Generic;
namespace MvcApplication8.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Id = 1, AddDate = DateTime.Now.AddYears(-3), Name = "Emp-01", Salary = 3000},
new Employee { Id = 2, AddDate = DateTime.Now.AddYears(-2), Name = "Emp-02", Salary = 2000},
new Employee { Id = 3, AddDate = DateTime.Now.AddYears(-1), Name = "Emp-03", Salary = 1000}
};
}
}
}
سپس از آن در یک کنترلر برای بازگشت لیستی از کارکنان استفاده خواهیم کرد:
public ActionResult EmployeeList()
{
var list = new Employees().CreateEmployees();
return View(list);
}
View متناظر با این متد را هم با کلیک راست بر روی متد، انتخاب گزینه Add view و سپس ایجاد یک strongly typed view از نوع کلاس Employee، ایجاد خواهیم کرد.
در ادامه قصد داریم بدنه حلقه زیر را refactor کنیم و آنرا به یک Parial view منتقل نمائیم تا View ما اندکی خلوتتر و مفهومتر شود:
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsMale)
</td>
<td>
@Html.DisplayFor(modelItem => item.AddDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
سپس بر روی پوشه Views/Employee کلیک راست کرده و گزینه Add|View را انتخاب کنید. در اینجا نام _EmployeeItem را وارد کرده و همچنین گزینه Create as a partial view و create a strongly typed view را نیز انتخاب کنید. نوع مدل هم Employee خواهد بود. به این ترتیب فایل زیر تشکیل خواهد شد:
\Views\Employee\_EmployeeItem.cshtml
ابتدای نام فایلرا با underline شروع کردهایم تا بتوان بین Viewها و Partial views تفاوت قائل شد. همچنین این Partial view چون داخل پوشه Employee تعریف شده، فقط در Viewهای کنترلر Employee در دسترس خواهد بود.
در ادامه کل بدنه حلقه فوق را cut کرده و در این فایل جدید paste نمائید. مرحله اول refactoring یک view به همین نحو آغاز میشود. البته در این حالت قادر به استفاده از Partial view نخواهیم بود چون اطلاعاتی که به این فایل ارسال میگردد و مدلی که در دسترس آن است از نوع Employee است و نه لیستی از کارمندان. به همین جهت باید item را با Model جایگزین کرد:
@model MvcApplication8.Models.Employee
<tr>
<td>
@Html.DisplayFor(x => x.Name)
</td>
<td>
@Html.DisplayFor(x => x.Salary)
</td>
<td>
@Html.DisplayFor(x => x.Address)
</td>
<td>
@Html.DisplayFor(x => x.IsMale)
</td>
<td>
@Html.DisplayFor(x => x.AddDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Details", "Details", new { id = Model.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = Model.Id })
</td>
</tr>
سپس برای استفاده از این Partial view در صفحه نمایش لیست کارمندان خواهیم داشت:
@foreach (var item in Model) {
@Html.Partial("_EmployeeItem", item)
}
متد Html.Partial، اطلاعات یک Partial view را پردازش و تبدیل به یک رشته کرده و در اختیار Razor قرار میدهد تا در صفحه نمایش داده شود. پارامتر اول آن نام Partial view مورد نظر است (نیازی به ذکر پسوند فایل نیست) و پارامتر دوم، اطلاعاتی است که به آن ارسال خواهد شد.
متد دیگری هم وجود دارد به نام Html.RenderPartial. کار این متد نوشتن مستقیم در Response است، برخلاف Html.Partial که فقط یک رشته را بر میگرداند.
نمایش اطلاعات از کنترلرهای مختلف در یک صفحه
Html.Partial بر اساس اطلاعات مدل ارسالی از یک کنترلر، کار رندر قسمتی از آنرا در یک View خاص عهده دار خواهد شد. اما اگر بخواهیم مثلا در یک صفحه یک قسمت را به نمایش آخرین اخبار و یک قسمت را به نمایش آخرین وضعیت آب و هوا اختصاص دهیم، از روش دیگری به نام RenderAction میتوان کمک گرفت. در اینجا هم دو متد Html.Action و Html.RenderAction وجود دارند. اولی یک رشته را بر میگرداند و دومی اطلاعات را مستقیما در Response درج میکند.
یک مثال:
کنترلر جدیدی را به نام MenuController به پروژه اضافه کنید:
using System.Web.Mvc;
namespace MvcApplication8.Controllers
{
public class MenuController : Controller
{
[ChildActionOnly]
public ActionResult ShowMenu(string options)
{
return PartialView(viewName: "_ShowMenu", model: options);
}
}
}
سپس بر روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در اینجا قصد داریم یک partial view که نامش با underline شروع میشود را اضافه کنیم. مثلا با محتوای زیر ( با توجه به اینکه مدل ارسالی از نوع رشتهای است):
@model string
<ul>
<li>
@Model
</li>
</ul>
حین فراخوانی متد Html.Action، یک متد در یک کنترلر فراخوانی خواهد شد (که شامل ارائه درخواست و طی سیکل کامل پردازشی آن کنترلر نیز خواهد بود). سپس آن متد با بازگشت دادن یک PartialView، اطلاعات پردازش شده یک partial view را به فراخوان بازگشت میدهد. اگر نامی ذکر نشود، همان نام متد در نظر گرفته خواهد شد. البته از آنجائیکه در این مثال در ابتدای نام Partial view یک underline قرار دادیم، نیاز خواهد بود تا این نام صریحا ذکر گردد (چون دیگر هم نام متد یا ActionName آن نیست). ویژگی ChildActionOnly سبب میشود تا این متد ویژه تنها از طریق فراخوانی Html.Action در دسترس باشد.
برای استفاده از آن هم در Viewایی دیگر خواهیم داشت:
@Html.Action(actionName: "ShowMenu", controllerName: "Menu",
routeValues: new { options = "some data..." })
در اینجا هم پارامتر ارسالی به کمک anonymously typed objects مشخص و مقدار دهی شده است.
سؤال مهم: چه تفاوتی بین RenderPartial و RenderAction وجود دارد؟ به نظر هر دو یک کار را انجام میدهند، هر دو مقداری HTML را پس از پرداش به صفحه تزریق میکنند.
پاسخ: اگر View والد، دارای کلیه اطلاعات لازم جهت نمایش اطلاعات Partial view است، از RenderPartial استفاده کنید. به این ترتیب برخلاف حالت RenderAction درخواست جدیدی به ASP.NET Pipeline صادر نشده و کارآیی نهایی بهتر خواهد بود. صرفا یک الحاق ساده به صفحه انجام خواهد شد.
اما اگر برای رندر کردن این قسمت از صفحه که قرار است اضافه شود، نیاز به دریافت اطلاعات دیگری خارج از اطلاعات مهیا میباشد، از روش RenderAction استفاده کنید. برای مثال اگر در صفحه جاری قرار است لیست پروژهها نمایش داده شود و در کنار صفحه مثلا منوی خاصی باید قرار گیرد، اطلاعات این منو در View جاری فراهم نیست (و همچنین مرتبط به آن هم نیست). بنابراین از روش RenderAction برای حل این مساله میتوان کمک گرفت.
به صورت خلاصه برای نمایش اطلاعات تکراری در صفحات مختلف سایت در حالتیکه این اطلاعات از قسمتهای دیگر صفحه ایزوله است (مثلا نمایش چند ویجت مختلف در صفحه)، روش RenderAction ارجحیت دارد.
یک نکته
فراخوانی متدهای RenderAction و RenderPartial در حین کار با Razor باید به شکل فراخوانی یک متد داخل {} باشند:
@{ Html.RenderAction("About"); }
And not @Html.RenderAction("About")
علت این است که @ به تنهایی به معنای نوشتن در Response است. متد RenderAction هم خروجی ندارد و مستقیما در Response اطلاعات خودش را درج میکند. بنابراین این دو با هم همخوانی ندارند و باید به شکل یک متد معمولی با آن رفتار کرد.
اگر حجم اطلاعاتی که قرار است در صفحه درج شود بالا است، متدهای RenderAction و RenderPartial نسبت به Html.Action و Html.Partial کارآیی بهتری دارند؛ چون یک مرحله تبدیل کل اطلاعات به رشته و سپس درج نتیجه در Response، در آنها حذف شده است.
گروه بندی دکمهها در Twitter bootstrap
در این مثال دو دکمه را ملاحظه میکنید که در یک div با کلاس btn-group محصور شدهاند. به این ترتیب این دو دکمه در کنار هم، همانند دکمههای یک toolbar قرار خواهند گرفت. همچنین در بوتاسترپ امکان انتساب ویژگی data-toggle=buttons-radio نیز به این div وجود دارد. در این حالت، این دکمهها همانند دکمههای رادیویی رفتار خواهند کرد:
در ادامه قصد داریم یک Editor template و یک Display template مخصوص را جهت تدارک یک چنین دکمههایی، برای مدیریت خواص Boolean ایجاد کنیم. به عبارتی اگر مدل برنامه چنین تعاریفی را داشت:
فیلد nullable bool آن به صورت خودکار به شکل زیر رندر شود:
تهیه قالب ادیتور Views\Shared\EditorTemplates\BootstrapBoolean.cshtml
سورس کامل فایل BootstrapBoolean.cshtml را که در مسیر Views\Shared\EditorTemplates باید کپی شود، در اینجا ملاحظه میکنید.
نوع اطلاعاتی که این قالب ادیتور پردازش خواهد کرد از نوع nullable bool است. البته مشکلی هم با نوعهای bool معمولی ندارد. در حالت nullable، دکمه سومی را به نام «نامشخص» به مجموعه دکمههای «بلی» و «خیر» اضافه میکند. گاهی از اوقات در فرمهای دریافت اطلاعات نیاز است بررسی کنیم آیا واقعا کاربر اطلاعاتی را انتخاب کرده یا اینکه بدون توجه به فیلدها، بر روی دکمه ارسال کلیک کرده است. در یک چنین حالتی تعریف دکمههای سه وضعیتی Boolean میتواند مفهوم پیدا کند.
در مورد اصول تهیه این قالب در ابتدای مطلب، با کلاسهای btn-group و ویژگی data-toggle آشنا شدید. دقیقا این سه دکمه نیز در اینجا به همین نحو تعریف شدهاند.
در ابتدای نمایش یک View، خصوصا در حالت ویرایش اطلاعات، نیاز است اطلاعات موجود، به دکمههای تعریف شده اعمال شوند. در اینجا برای انتخاب یک دکمه، باید کلاس active به آن نسبت داده شود، که نحوه تدارک آنرا در سه متغیر yesIsSelected، noIsSelected و isIndeterminate ابتدای تعاریف قالب مشاهده میکنید.
سپس یک فیلد مخفی به صفحه اضافه شده است. از این جهت که به کمک jQuery، در حین کلیک بر روی یکی از دکمهها، مقدار آنرا به این فیلد که نهایتا به سرور ارسال خواهد شد، اعمال خواهیم کرد.
تهیه قالب نمایشی Views\Shared\DisplayTemplates\BootstrapBoolean.cshtml
در حالت صرفا نمایشی، فایل قالب BootstrapBoolean.cshtml قرار گرفته در مسیر Views\Shared\DisplayTemplates، یک چنین تعاریفی را خواهد داشت.
و نهایتا برای استفاده از آن تنها کافی است توسط ویژگی UIHint، نام این قالب، به خاصیت Boolean مدنظر اعمال شود:
<div class="btn-group" data-toggle="buttons-radio"> <button class="btn" type="button">بلی</button> <button class="btn" type="button">خیر</button> </div>
در ادامه قصد داریم یک Editor template و یک Display template مخصوص را جهت تدارک یک چنین دکمههایی، برای مدیریت خواص Boolean ایجاد کنیم. به عبارتی اگر مدل برنامه چنین تعاریفی را داشت:
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Mvc4TwitterBootStrapTest.Models { public class User { public int Id { set; get; } [DisplayName("نام")] [Required(ErrorMessage="لطفا نام را تکمیل کنید")] public string Name { set; get; } [DisplayName("نام خانوادگی")] [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")] public string LastName { set; get; } [DisplayName("فعال است؟")] [UIHint("BootstrapBoolean")] public bool? IsActive { set; get; } } }
تهیه قالب ادیتور Views\Shared\EditorTemplates\BootstrapBoolean.cshtml
@model bool? @{ var yesIsSelected = Model.HasValue && Model.Value ? "active" : null; var noIsSelected = Model.HasValue && !Model.Value ? "active" : null; var isIndeterminate = !Model.HasValue ? "active" : null; var htmlField = ViewData.TemplateInfo.HtmlFieldPrefix; } @Html.HiddenFor(model => model) <div class="btn-group" data-toggle="buttons-radio"> <button type="button" class="btn btn-info @yesIsSelected bool-@htmlField" onclick="$('#@htmlField').val(true);"> بلی</button> <button type="button" class="btn btn-info @noIsSelected bool-@htmlField" onclick="$('#@htmlField').val(false);"> خیر</button> @if (ViewData.ModelMetadata.IsNullableValueType) { <button type="button" class="btn btn-info @isIndeterminate bool-@htmlField" onclick="$('#@htmlField').val('');"> نامشخص</button> } </div>
نوع اطلاعاتی که این قالب ادیتور پردازش خواهد کرد از نوع nullable bool است. البته مشکلی هم با نوعهای bool معمولی ندارد. در حالت nullable، دکمه سومی را به نام «نامشخص» به مجموعه دکمههای «بلی» و «خیر» اضافه میکند. گاهی از اوقات در فرمهای دریافت اطلاعات نیاز است بررسی کنیم آیا واقعا کاربر اطلاعاتی را انتخاب کرده یا اینکه بدون توجه به فیلدها، بر روی دکمه ارسال کلیک کرده است. در یک چنین حالتی تعریف دکمههای سه وضعیتی Boolean میتواند مفهوم پیدا کند.
در مورد اصول تهیه این قالب در ابتدای مطلب، با کلاسهای btn-group و ویژگی data-toggle آشنا شدید. دقیقا این سه دکمه نیز در اینجا به همین نحو تعریف شدهاند.
در ابتدای نمایش یک View، خصوصا در حالت ویرایش اطلاعات، نیاز است اطلاعات موجود، به دکمههای تعریف شده اعمال شوند. در اینجا برای انتخاب یک دکمه، باید کلاس active به آن نسبت داده شود، که نحوه تدارک آنرا در سه متغیر yesIsSelected، noIsSelected و isIndeterminate ابتدای تعاریف قالب مشاهده میکنید.
سپس یک فیلد مخفی به صفحه اضافه شده است. از این جهت که به کمک jQuery، در حین کلیک بر روی یکی از دکمهها، مقدار آنرا به این فیلد که نهایتا به سرور ارسال خواهد شد، اعمال خواهیم کرد.
تهیه قالب نمایشی Views\Shared\DisplayTemplates\BootstrapBoolean.cshtml
@model bool? @if (Model.HasValue) { if (Model.Value) { <span class="label label-success">بلی</span> } else { <span class="label label-important">خیر</span> } } else { <span class="label label-inverse">نامشخص</span> }
و نهایتا برای استفاده از آن تنها کافی است توسط ویژگی UIHint، نام این قالب، به خاصیت Boolean مدنظر اعمال شود:
[UIHint("BootstrapBoolean")] public bool? IsActive { set; get; }
فایلهای آن، در قسمت فایلهای پروژه یاد شده، قرار داده شدهاند. در اینجا
نظرات مطالب
لیست به روز شدهی بلاگهای IT
OPML به روز شده تا آخر دی در قسمت گزیدههای سمت راست سایت قرار گرفت.
نظرات اشتراکها
نحوه انتقال از Gmail به Outlook.com
سلام
در شرایط فعلی که قرار است گوگل فیلتر شود، گزینه بسیار مناسبیست.
در این شرایط برای فید خوانی چه گزینه جایگزینی وجود دارد؟(شما از چی استفاده میکنید)
امیدوارم سوالم زیاد نامربوط نباشد
مطالب
آموزش فایرباگ - #1
فایرباگ نام ابزاری است که همه ما نام آن را شنیده ایم یا با آن کار کرده ایم .
این ابزار امکانات و قابلیتهای فراوانی دارد که فراگیری آنها برای یک توسعه گر یا طراح وب سایت ضروری به نظر میرسد .
در سری مقالات آموزش فایرباگ ، با این ابزار محبوب و پرکاربرد بیشتر آشنا میشویم .
نصب و اجرای فایرباگ :
ابتدا وارد این صفحه شوید و ورژن مناسب مرورگرتان را نصب نمایید .
پس از نصب و دوباره باز کردن مرورگر ، میتوانید با کلیک کردن بروی آیکون فایرباگ یا فشردن کلید F12 ، فایرباگ را اجرا کنید .
تبها :
فایرباگ در حالت پیشفرض دارای 6 تب میباشد .
در قسمتهای بعدی هر تب را به صورت جداگانه بررسی میکنیم .
این ابزار امکانات و قابلیتهای فراوانی دارد که فراگیری آنها برای یک توسعه گر یا طراح وب سایت ضروری به نظر میرسد .
در سری مقالات آموزش فایرباگ ، با این ابزار محبوب و پرکاربرد بیشتر آشنا میشویم .
نصب و اجرای فایرباگ :
ابتدا وارد این صفحه شوید و ورژن مناسب مرورگرتان را نصب نمایید .
پس از نصب و دوباره باز کردن مرورگر ، میتوانید با کلیک کردن بروی آیکون فایرباگ یا فشردن کلید F12 ، فایرباگ را اجرا کنید .
تبها :
فایرباگ در حالت پیشفرض دارای 6 تب میباشد .
- Console : در این تب دو بخش وجود دارد :
در بخش Log هشدارها ، پیغامها ، درخواستهای XHR و ... نمایش داده میشوند .
بخش دیگر هم که در سمت راست قرار دارد ، مخصوص اجرای کدهای جاوا اسکریپت میباشد .
-
HTML : در این تب HTML صفحه را بصورت درختی و بصورت Live مشاهده میکنید .
Live بودن به این معنی است دقیقا چیزی را مشاهده میکنید که مرورگر در حال تفسیر هست و نه چیزی که به مرورگر ارسال شده .
همچنین در این تب میتوانید محتوای HTML قسمت Body صفحه را ویرایش کنید .
- CSS : در این تب میتوانید تعاریف CSS موجود در صفحه را مشاهده/ویرایش/حذف/فعال-غیرفعال نمایید .
همچنین میتوانید تمام فایلهای استایلی که به صفحه اضافه شده اند را مشاهده و تعیین کنید که تعاریف موجود در کدام فایلها در این محیط نمایش داده بشوند .
- Script : در این تب میتوانید به خطایابی کدهای جاوا اسکریپت بپردازید . همچنین مانند تب CSS میتوانید یکی از فایلهای اضافه شده به صفحه برای نمایش در این بخش ، چند مورد را انتخاب کنید .
- DOM : در این تب میتوانید ساختار DOM صفحه جاری را مشاهده نمایید .
- Net : در این تب میتوانید درخواست هایی که از صفحه ارسال میشوند را زیر نظر بگیرید .
این تب اطلاعات کاملی دربارهی هر درخواست ارائه میکند .
از جمله : نوع درخواست ( Post/Get ) ، حجم درخواست و پاسخ ، مدت زمان درخواست ، تعداد درخواستهای ارسال شده ، وضعیت درخواستها و ...
در قسمتهای بعدی هر تب را به صورت جداگانه بررسی میکنیم .
سلام و تشکر از آموزش خوبتون
من به خوبی از این مورد استفاده میکنم فقط من از روش شما برای ۴ عدد dropdownlist استفاده میکنم ( استان > شهر > منطقه > محله ) مشکلم این هست که بعد از انتخاب استان تمامی شهرها رو واسه من میآره ولی مثلا اولین شهرم که تهران هست داخل استان تهران مناطق رو نمیآره و حق هم داره چون ما در رویداد change گفتیم که این کارو بکنه اگر بخوام اولین گزینه لیست بعد از این انتخاب مثلا بعد از انتخاب استان که لیست شهرها میآن اولین گزینه --- انتخاب کنید --- باشه باید چکار کنم ؟
با تشکر
من به خوبی از این مورد استفاده میکنم فقط من از روش شما برای ۴ عدد dropdownlist استفاده میکنم ( استان > شهر > منطقه > محله ) مشکلم این هست که بعد از انتخاب استان تمامی شهرها رو واسه من میآره ولی مثلا اولین شهرم که تهران هست داخل استان تهران مناطق رو نمیآره و حق هم داره چون ما در رویداد change گفتیم که این کارو بکنه اگر بخوام اولین گزینه لیست بعد از این انتخاب مثلا بعد از انتخاب استان که لیست شهرها میآن اولین گزینه --- انتخاب کنید --- باشه باید چکار کنم ؟
با تشکر